@geraldmaron/construct 1.4.1 → 1.5.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/.env.example +36 -0
- package/README.md +41 -7
- package/bin/construct +1029 -65
- package/bin/construct-postinstall.mjs +27 -2
- package/config/tag-vocabulary.json +264 -0
- package/examples/distribution/sources/deck-one-pager.md +1 -1
- package/lib/acp/server.mjs +21 -7
- package/lib/adapters-sync.mjs +2 -1
- package/lib/audit-trail.mjs +185 -2
- package/lib/beads/auto-close.mjs +2 -1
- package/lib/beads/drift.mjs +2 -1
- package/lib/bridges/copilot-proxy.mjs +20 -5
- package/lib/certification/skill-inventory.mjs +10 -1
- package/lib/cli/approvals.mjs +147 -0
- package/lib/cli-commands.mjs +105 -14
- package/lib/comment-lint.mjs +127 -8
- package/lib/config/schema.mjs +7 -30
- package/lib/config/source-target-registry.mjs +70 -0
- package/lib/config/source-targets.mjs +179 -205
- package/lib/contracts/coverage.mjs +77 -0
- package/lib/contracts/validate.mjs +102 -13
- package/lib/contracts/violation-log.mjs +50 -4
- package/lib/db/migrate.mjs +69 -0
- package/lib/db/migrations/001_orchestration_runs.sql +9 -0
- package/lib/db/migrations/002_queue_provider.sql +50 -0
- package/lib/db/migrations/003_worker_registry.sql +17 -0
- package/lib/db/migrations/004_trace_events.sql +16 -0
- package/lib/db/migrations/005_shared_memory.sql +15 -0
- package/lib/db/migrations/006_orchestration_runs_tenant.sql +5 -0
- package/lib/decisions/enforced-baseline.json +0 -2
- package/lib/decisions/registry.mjs +3 -2
- package/lib/deployment/parity-contract.mjs +1 -1
- package/lib/deployment-mode.mjs +78 -2
- package/lib/diagram-export.mjs +10 -1
- package/lib/distill.mjs +5 -2
- package/lib/docs-verify.mjs +2 -1
- package/lib/doctor/cli.mjs +1 -0
- package/lib/doctor/command-on-path.mjs +24 -0
- package/lib/doctor/diagnosis.mjs +109 -0
- package/lib/doctor/embedding-health.mjs +50 -0
- package/lib/doctor/engine-health.mjs +93 -0
- package/lib/doctor/graph-validate.mjs +47 -0
- package/lib/doctor/index.mjs +23 -4
- package/lib/doctor/sidecar-providers.mjs +56 -0
- package/lib/doctor/watchers/consistency.mjs +93 -1
- package/lib/doctor/watchers/cx-budget.mjs +1 -1
- package/lib/doctor/watchers/graph-staleness.mjs +1 -1
- package/lib/doctor/watchers/mcp-protocol.mjs +17 -0
- package/lib/doctor/watchers/oracle-liveness.mjs +92 -0
- package/lib/doctor/watchers/orchestration-runs.mjs +108 -0
- package/lib/doctor/watchers/provider-breaker.mjs +78 -0
- package/lib/document-export.mjs +52 -27
- package/lib/document-extract/docling-client.mjs +9 -5
- package/lib/document-extract/docling-sidecar.py +30 -0
- package/lib/document-extract.mjs +1 -1
- package/lib/document-ingest.mjs +9 -0
- package/lib/embed/approval-queue.mjs +184 -88
- package/lib/embed/authority-guard.mjs +65 -2
- package/lib/embed/capability-jobs.mjs +365 -0
- package/lib/embed/capability-lifecycle.mjs +219 -0
- package/lib/embed/capability-loader.mjs +303 -0
- package/lib/embed/capability-runtime.mjs +58 -0
- package/lib/embed/cli.mjs +185 -8
- package/lib/embed/config.mjs +4 -0
- package/lib/embed/daemon.mjs +125 -6
- package/lib/embed/demand-fetch.mjs +190 -54
- package/lib/embed/inbox.mjs +12 -10
- package/lib/embed/presets/ops-triage.mjs +236 -0
- package/lib/embed/presets/pm-feedback.mjs +341 -0
- package/lib/embed/presets/tpm.mjs +401 -0
- package/lib/embed/providers/jira.mjs +72 -1
- package/lib/embed/providers/registry.mjs +140 -33
- package/lib/embed/worker.mjs +5 -0
- package/lib/embedded-contract/capability.mjs +4 -0
- package/lib/embedded-contract/execution.mjs +1 -1
- package/lib/embedded-contract/model-resolve.mjs +53 -3
- package/lib/embedded-contract/workflow-defs.mjs +48 -73
- package/lib/embedded-contract/workflow-invoke.mjs +144 -4
- package/lib/embedded-contract/workflows/architecture-review.manifest.json +15 -0
- package/lib/embedded-contract/workflows/data-structure.manifest.json +14 -0
- package/lib/embedded-contract/workflows/evidence-ingest.manifest.json +14 -0
- package/lib/embedded-contract/workflows/memo-draft.manifest.json +14 -0
- package/lib/embedded-contract/workflows/operations-triage.manifest.json +18 -0
- package/lib/embedded-contract/workflows/operations.manifest.json +18 -0
- package/lib/embedded-contract/workflows/pm-feedback.manifest.json +18 -0
- package/lib/embedded-contract/workflows/prd-draft.manifest.json +15 -0
- package/lib/embedded-contract/workflows/proposal-review.manifest.json +15 -0
- package/lib/embedded-contract/workflows/research-synthesis.manifest.json +14 -0
- package/lib/embedded-contract/workflows/risk-review.manifest.json +15 -0
- package/lib/embedded-contract/workflows/structure-notes.manifest.json +14 -0
- package/lib/embedded-contract/workflows/transcript-process.manifest.json +14 -0
- package/lib/embedded-contract/workflows/triage.manifest.json +14 -0
- package/lib/env-config.mjs +60 -11
- package/lib/export-validate.mjs +34 -2
- package/lib/extensions/index.mjs +27 -0
- package/lib/extensions/loader.mjs +120 -0
- package/lib/extensions/manifest-schema.mjs +141 -0
- package/lib/extensions/manifests/anthropic.manifest.json +12 -0
- package/lib/extensions/manifests/atlassian-confluence.manifest.json +12 -0
- package/lib/extensions/manifests/atlassian-jira.manifest.json +53 -0
- package/lib/extensions/manifests/directory.manifest.json +12 -0
- package/lib/extensions/manifests/docling.manifest.json +37 -0
- package/lib/extensions/manifests/echo.manifest.json +8 -0
- package/lib/extensions/manifests/feedback.manifest.json +12 -0
- package/lib/extensions/manifests/github-copilot.manifest.json +12 -0
- package/lib/extensions/manifests/github.manifest.json +52 -0
- package/lib/extensions/manifests/linear.manifest.json +50 -0
- package/lib/extensions/manifests/local.manifest.json +12 -0
- package/lib/extensions/manifests/ollama.manifest.json +12 -0
- package/lib/extensions/manifests/openai.manifest.json +12 -0
- package/lib/extensions/manifests/openrouter-anthropic.manifest.json +12 -0
- package/lib/extensions/manifests/openrouter-deepseek.manifest.json +12 -0
- package/lib/extensions/manifests/openrouter-google.manifest.json +12 -0
- package/lib/extensions/manifests/openrouter-llama.manifest.json +12 -0
- package/lib/extensions/manifests/openrouter-qwen.manifest.json +12 -0
- package/lib/extensions/manifests/openrouter.manifest.json +12 -0
- package/lib/extensions/manifests/postgres.manifest.json +12 -0
- package/lib/extensions/manifests/salesforce.manifest.json +12 -0
- package/lib/extensions/manifests/slack.manifest.json +58 -0
- package/lib/extensions/manifests/whisper.manifest.json +36 -0
- package/lib/extensions/validate.mjs +175 -0
- package/lib/features.mjs +13 -9
- package/lib/flows/checkpoint.mjs +184 -0
- package/lib/flows/constants.mjs +27 -0
- package/lib/flows/define.mjs +146 -0
- package/lib/flows/engine.mjs +249 -0
- package/lib/flows/errors.mjs +19 -0
- package/lib/flows/index.mjs +27 -0
- package/lib/flows/joins.mjs +18 -0
- package/lib/flows/schema.mjs +90 -0
- package/lib/flows/state.mjs +31 -0
- package/lib/frameworks/loader.mjs +99 -0
- package/lib/frameworks/schema.mjs +157 -0
- package/lib/graph/build-from-corpus.mjs +67 -0
- package/lib/graph/build-from-embed.mjs +82 -0
- package/lib/graph/build-from-registry.mjs +164 -3
- package/lib/graph/cli.mjs +456 -12
- package/lib/graph/gap-queries.mjs +156 -0
- package/lib/graph/gaps.mjs +41 -0
- package/lib/graph/impacted.mjs +129 -0
- package/lib/graph/runtime-evidence.mjs +177 -0
- package/lib/graph/security-coverage.mjs +113 -0
- package/lib/graph/staleness.mjs +241 -10
- package/lib/graph/store.mjs +24 -7
- package/lib/graph/validate.mjs +161 -0
- package/lib/headhunt.mjs +9 -8
- package/lib/health-check.mjs +15 -7
- package/lib/hook-health.mjs +1 -1
- package/lib/hooks/agent-tracker.mjs +10 -4
- package/lib/hooks/edit-guard.mjs +3 -3
- package/lib/hooks/graph-impact-advisory.mjs +2 -2
- package/lib/hooks/guard-bash.mjs +41 -15
- package/lib/hooks/mcp-health-check.mjs +11 -4
- package/lib/hooks/model-fallback.mjs +50 -6
- package/lib/hooks/orchestration-dispatch-guard.mjs +14 -3
- package/lib/hooks/pre-compact.mjs +181 -173
- package/lib/hooks/rule-verifier.mjs +2 -1
- package/lib/hooks/session-reflect.mjs +2 -1
- package/lib/hooks/session-start.mjs +3 -3
- package/lib/host/readiness.mjs +109 -0
- package/lib/host-capabilities.mjs +13 -2
- package/lib/host-disposition.mjs +29 -8
- package/lib/identity.mjs +92 -0
- package/lib/ingest/degraded-extract.mjs +96 -0
- package/lib/ingest/docling-remote.mjs +2 -1
- package/lib/ingest/provider-extract.mjs +7 -19
- package/lib/ingest/sidecar-providers.mjs +196 -0
- package/lib/ingest-tooling.mjs +11 -5
- package/lib/init-unified.mjs +55 -38
- package/lib/init-update.mjs +2 -1
- package/lib/install/legacy-global-cleanup.mjs +25 -0
- package/lib/install/stage-project.mjs +41 -4
- package/lib/intake/daemon.mjs +99 -8
- package/lib/intake/git-queue.mjs +110 -38
- package/lib/intake/prepare.mjs +2 -0
- package/lib/intake/queue-registry.mjs +50 -0
- package/lib/intake/queue.mjs +133 -17
- package/lib/intake/session-prelude.mjs +57 -8
- package/lib/integrations/intake-integrations.mjs +61 -111
- package/lib/intent-classifier.mjs +43 -5
- package/lib/libreoffice-export.mjs +8 -8
- package/lib/logging/rotate.mjs +5 -4
- package/lib/mcp/broker.mjs +332 -17
- package/lib/mcp/denied-store.mjs +95 -0
- package/lib/mcp/destructive-approval.mjs +57 -0
- package/lib/mcp/destructive-gate.mjs +30 -0
- package/lib/mcp/dispatch-envelope.mjs +199 -0
- package/lib/mcp/memory-bridge.mjs +2 -1
- package/lib/mcp/server.mjs +185 -1268
- package/lib/mcp/tool-definitions-memory.mjs +376 -0
- package/lib/mcp/tool-definitions-project.mjs +271 -0
- package/lib/mcp/tool-definitions-skills.mjs +311 -0
- package/lib/mcp/tool-definitions-workflow.mjs +398 -0
- package/lib/mcp/tool-definitions.mjs +25 -0
- package/lib/mcp/tool-rate-limit.mjs +47 -0
- package/lib/mcp/tool-registry.mjs +107 -0
- package/lib/mcp/tool-safety.mjs +95 -0
- package/lib/mcp/tool-surface-parity.mjs +60 -0
- package/lib/mcp/tools/orchestration-delegation-next.tool.mjs +68 -0
- package/lib/mcp/tools/orchestration-run.mjs +197 -19
- package/lib/mcp/tools/orchestration-task-result.tool.mjs +77 -0
- package/lib/mcp/tools/project.mjs +25 -8
- package/lib/mcp/tools/provider-write.mjs +187 -0
- package/lib/mcp/tools/scope.mjs +0 -3
- package/lib/mcp/tools/skills.mjs +1 -1
- package/lib/mcp/tools/storage.mjs +2 -2
- package/lib/mcp/tools/web-search-governance.mjs +96 -0
- package/lib/mcp/tools/web-search.mjs +5 -76
- package/lib/mcp/transport/auth.mjs +238 -0
- package/lib/mcp/transport/http.mjs +136 -0
- package/lib/mcp/transport/mode.mjs +31 -0
- package/lib/mcp/transport/stdio.mjs +22 -0
- package/lib/mcp-catalog.json +4 -4
- package/lib/mcp-manager.mjs +34 -23
- package/lib/mcp-platform-config.mjs +53 -20
- package/lib/mode-capabilities.mjs +109 -0
- package/lib/model-router.mjs +42 -9
- package/lib/models/catalog.mjs +40 -1
- package/lib/net-guard.mjs +247 -0
- package/lib/observation-store.mjs +6 -2
- package/lib/ollama/provision-context.mjs +2 -1
- package/lib/opencode-config.mjs +22 -3
- package/lib/opencode-runtime-plugin.mjs +120 -2
- package/lib/oracle/actions.mjs +204 -10
- package/lib/oracle/cli.mjs +24 -3
- package/lib/oracle/daemon-entry.mjs +6 -0
- package/lib/oracle/dispatch.mjs +2 -1
- package/lib/oracle/execute.mjs +2 -2
- package/lib/oracle/gaps.mjs +3 -3
- package/lib/oracle/heartbeat.mjs +53 -0
- package/lib/oracle/read-model.mjs +69 -13
- package/lib/oracle/reconcile.mjs +3 -46
- package/lib/oracle/routing.mjs +37 -31
- package/lib/oracle/synthesize.mjs +1 -1
- package/lib/orchestration/classification.mjs +434 -0
- package/lib/orchestration/delegation-flow.mjs +132 -0
- package/lib/orchestration/flow-selection.mjs +418 -0
- package/lib/orchestration/gates.mjs +244 -0
- package/lib/orchestration/host-sampling.mjs +121 -0
- package/lib/orchestration/policy-constants.mjs +48 -0
- package/lib/orchestration/provider-outcome.mjs +197 -0
- package/lib/orchestration/readiness.mjs +114 -13
- package/lib/orchestration/run-store-postgres.mjs +32 -26
- package/lib/orchestration/run-store-sqlite.mjs +8 -14
- package/lib/orchestration/run-store.mjs +25 -12
- package/lib/orchestration/runtime.mjs +508 -62
- package/lib/orchestration/store.mjs +87 -12
- package/lib/orchestration/trace-store.mjs +125 -0
- package/lib/orchestration/web-capability.mjs +60 -0
- package/lib/orchestration/worker-runtime.mjs +162 -0
- package/lib/orchestration/worker.mjs +763 -80
- package/lib/orchestration-policy.mjs +67 -1111
- package/lib/output-quality.mjs +61 -2
- package/lib/packs/cli.mjs +144 -0
- package/lib/packs/core-pack.mjs +112 -0
- package/lib/packs/enablement.mjs +187 -0
- package/lib/packs/index.mjs +23 -0
- package/lib/packs/loader.mjs +176 -0
- package/lib/packs/manifest-schema.mjs +58 -0
- package/lib/packs/prompts.mjs +120 -0
- package/lib/packs/validate.mjs +208 -0
- package/lib/path-policy.mjs +56 -0
- package/lib/plugin-registry.mjs +4 -5
- package/lib/policy/audit-gate.mjs +42 -0
- package/lib/policy/consumption-budget.mjs +149 -0
- package/lib/policy/engine.mjs +81 -6
- package/lib/policy/role-authority.mjs +89 -0
- package/lib/prompt-composer.js +19 -3
- package/lib/providers/contract/adapters/confluence/governed-write.mjs +215 -0
- package/lib/providers/contract/adapters/confluence/manifest.json +17 -0
- package/lib/providers/contract/adapters/confluence/transport.mjs +135 -0
- package/lib/providers/contract/adapters/github/governed-write.mjs +121 -0
- package/lib/providers/contract/adapters/github/index.mjs +38 -3
- package/lib/providers/contract/adapters/jira/adf.mjs +151 -0
- package/lib/providers/contract/adapters/jira/createmeta.mjs +143 -0
- package/lib/providers/contract/adapters/jira/governed-write.mjs +182 -0
- package/lib/providers/contract/adapters/jira/manifest.json +17 -0
- package/lib/providers/contract/adapters/jira/transport.mjs +119 -0
- package/lib/providers/contract.mjs +207 -0
- package/lib/providers/credential-bootstrap.mjs +7 -4
- package/lib/providers/credential-sources.mjs +14 -2
- package/lib/providers/directory/index.mjs +261 -0
- package/lib/providers/feedback/index.mjs +392 -0
- package/lib/providers/filter-audit.mjs +68 -0
- package/lib/providers/filter-schema.mjs +54 -0
- package/lib/providers/github/index.mjs +31 -0
- package/lib/providers/instance-config.mjs +215 -0
- package/lib/providers/op-locate.mjs +96 -0
- package/lib/providers/op-run.mjs +64 -9
- package/lib/providers/registry.mjs +26 -3
- package/lib/providers/secret-audit-wiring.mjs +41 -18
- package/lib/providers/secret-resolver.mjs +263 -31
- package/lib/publish-template.mjs +6 -3
- package/lib/publish-tooling.mjs +4 -0
- package/lib/publish.mjs +32 -4
- package/lib/queue/pg-queue.mjs +395 -0
- package/lib/reflect.mjs +2 -1
- package/lib/registry/assemble.mjs +28 -2
- package/lib/registry/catalog.mjs +6 -0
- package/lib/registry/consolidation.mjs +8 -1
- package/lib/registry/custom-scaffold.mjs +200 -0
- package/lib/registry/custom-schema.mjs +137 -0
- package/lib/registry/loader.mjs +15 -4
- package/lib/registry/manifests/format-engines.default.json +15 -0
- package/lib/registry/manifests/surface-map.default.json +46 -0
- package/lib/registry/retired-paths.mjs +1 -0
- package/lib/registry/surface-map.mjs +100 -50
- package/lib/registry/validate.mjs +3 -3
- package/lib/render-visual-check.mjs +240 -0
- package/lib/resources/budget.mjs +40 -17
- package/lib/roles/flavor-bindings.mjs +36 -14
- package/lib/roles/gateway.mjs +15 -8
- package/lib/roots.mjs +107 -0
- package/lib/runtime/uv-bootstrap.mjs +32 -6
- package/lib/runtime/whisper-bootstrap.mjs +11 -3
- package/lib/runtime-env.mjs +5 -2
- package/lib/sandbox.mjs +1 -1
- package/lib/scheduler/index.mjs +8 -20
- package/lib/schema-infer.mjs +31 -2
- package/lib/scopes/lifecycle.mjs +29 -25
- package/lib/scopes/loader.mjs +49 -12
- package/lib/scopes/teams.mjs +1 -1
- package/lib/security/ingest-boundary.mjs +80 -0
- package/lib/security/recall-wrapper.mjs +99 -0
- package/lib/security/trust.mjs +132 -0
- package/lib/service-manager.mjs +93 -24
- package/lib/setup.mjs +41 -23
- package/lib/skills-apply.mjs +4 -2
- package/lib/specialists/postconditions.mjs +2 -2
- package/lib/state-root.mjs +147 -0
- package/lib/status.mjs +597 -6
- package/lib/storage/admin.mjs +84 -7
- package/lib/storage/backend-registry.mjs +73 -0
- package/lib/storage/backend.mjs +63 -9
- package/lib/storage/embeddings-openai.mjs +12 -2
- package/lib/storage/hybrid-query.mjs +11 -3
- package/lib/storage/retrieval-hardening.mjs +384 -0
- package/lib/storage/shared-memory.mjs +158 -0
- package/lib/storage/vector-client.mjs +69 -2
- package/lib/team/health.mjs +72 -0
- package/lib/telemetry/backends/local.mjs +9 -3
- package/lib/telemetry/client.mjs +3 -7
- package/lib/template-registry.mjs +10 -12
- package/lib/tenant/context.mjs +82 -0
- package/lib/tenant/isolation.mjs +129 -0
- package/lib/test-corpus-inventory.mjs +103 -2
- package/lib/uninstall/uninstall.mjs +78 -12
- package/lib/validator.mjs +4 -6
- package/lib/worker/entrypoint.mjs +2 -1
- package/lib/worker/run.mjs +2 -2
- package/lib/worker/trace.mjs +5 -11
- package/lib/workflow-state.mjs +52 -8
- package/lib/workflows/liveness.mjs +203 -0
- package/lib/workflows/loader.mjs +177 -0
- package/lib/workflows/manifest-schema.mjs +71 -0
- package/lib/workflows/surface-parity.mjs +118 -0
- package/lib/workflows/validate.mjs +127 -0
- package/lib/writes/control-plane.mjs +140 -0
- package/lib/writes/envelope.mjs +206 -0
- package/lib/writes/sent-log.mjs +86 -0
- package/lib/writes/write-intent.mjs +109 -0
- package/package.json +20 -7
- package/personas/construct.md +12 -3
- package/registry/agent-manifest.json +117 -0
- package/registry/capabilities.json +1987 -0
- package/rules/common/comments.md +1 -0
- package/schemas/brand-voice.schema.json +24 -0
- package/schemas/capability-registry.schema.json +72 -0
- package/schemas/certification-run.schema.json +130 -0
- package/schemas/demo-recording.schema.json +46 -0
- package/schemas/eval-dataset.schema.json +79 -0
- package/schemas/execution-capability-profile.schema.json +46 -0
- package/schemas/execution-policy.schema.json +114 -0
- package/schemas/improvement-proposal.schema.json +65 -0
- package/schemas/mcp-tool-output.schema.json +61 -0
- package/schemas/platform-capabilities.schema.json +83 -0
- package/schemas/project-config.schema.json +215 -0
- package/schemas/project-demo.schema.json +60 -0
- package/schemas/provider-behavior-matrix.schema.json +91 -0
- package/schemas/scope.schema.json +197 -0
- package/schemas/specialist-trace.schema.json +107 -0
- package/schemas/team.schema.json +99 -0
- package/schemas/unified-registry.schema.json +546 -0
- package/scripts/sync-specialists.mjs +336 -106
- package/skills/docs/adr-workflow.md +1 -1
- package/skills/docs/codebase-research-workflow.md +3 -3
- package/skills/docs/prd-workflow.md +5 -6
- package/skills/docs/runbook-workflow.md +2 -2
- package/skills/docs/user-research-workflow.md +3 -3
- package/skills/operating/fleet-health-routing.md +77 -0
- package/skills/roles/ai-engineer.md +1 -1
- package/skills/roles/architect.md +0 -1
- package/skills/roles/business-strategist.md +1 -1
- package/skills/roles/data-analyst.telemetry.md +1 -1
- package/skills/roles/data-engineer.md +1 -1
- package/skills/roles/data-engineer.pipeline.md +1 -1
- package/skills/roles/data-engineer.vector-retrieval.md +1 -2
- package/skills/roles/data-engineer.warehouse.md +1 -1
- package/skills/roles/designer.accessibility.md +1 -1
- package/skills/roles/designer.md +0 -1
- package/skills/roles/devil-advocate.md +1 -1
- package/skills/roles/docs-keeper.md +1 -1
- package/skills/roles/evaluator.md +1 -1
- package/skills/roles/explorer.md +1 -1
- package/skills/roles/platform-engineer.md +1 -1
- package/skills/roles/qa.ai-eval.md +1 -2
- package/skills/roles/qa.api-contract.md +0 -1
- package/skills/roles/qa.data-pipeline.md +0 -1
- package/skills/roles/qa.md +0 -1
- package/skills/roles/qa.web-ui.md +0 -1
- package/skills/roles/release-manager.md +1 -1
- package/skills/roles/researcher.md +0 -2
- package/skills/roles/reviewer.md +0 -3
- package/skills/roles/security.ai.md +1 -1
- package/skills/roles/security.legal-compliance.md +1 -1
- package/skills/roles/security.md +0 -1
- package/skills/roles/security.privacy.md +0 -1
- package/skills/roles/security.supply-chain.md +1 -1
- package/skills/roles/sre.md +1 -1
- package/skills/roles/test-automation.md +1 -1
- package/skills/roles/trace-reviewer.md +1 -1
- package/skills/roles/ux-researcher.md +1 -1
- package/specialists/artifact-manifest.json +36 -36
- package/specialists/org/contracts/accessibility-to-qa.json +11 -3
- package/specialists/org/contracts/any-to-business-strategist.json +19 -7
- package/specialists/org/contracts/any-to-debugger.json +7 -1
- package/specialists/org/contracts/any-to-designer.json +7 -1
- package/specialists/org/contracts/any-to-docs-keeper.json +18 -4
- package/specialists/org/contracts/any-to-explorer.json +13 -4
- package/specialists/org/contracts/any-to-sre-incident.json +6 -2
- package/specialists/org/contracts/any-to-trace-reviewer.json +16 -4
- package/specialists/org/contracts/architect-to-ai-engineer.json +6 -2
- package/specialists/org/contracts/architect-to-data-engineer.json +17 -5
- package/specialists/org/contracts/architect-to-devil-advocate.json +11 -3
- package/specialists/org/contracts/architect-to-engineer.json +20 -4
- package/specialists/org/contracts/architect-to-evaluator.json +15 -5
- package/specialists/org/contracts/architect-to-legal-compliance.json +15 -5
- package/specialists/org/contracts/architect-to-operations.json +17 -4
- package/specialists/org/contracts/architect-to-platform-engineer.json +20 -4
- package/specialists/org/contracts/construct-to-orchestrator.json +10 -2
- package/specialists/org/contracts/data-analyst-to-product-manager.json +11 -2
- package/specialists/org/contracts/engineer-to-qa.json +20 -4
- package/specialists/org/contracts/engineer-to-reviewer.json +29 -5
- package/specialists/org/contracts/explorer-to-engineer.json +11 -3
- package/specialists/org/contracts/legal-compliance-to-release-manager.json +12 -4
- package/specialists/org/contracts/operations-to-user.json +53 -0
- package/specialists/org/contracts/operations-triage-output.json +53 -0
- package/specialists/org/contracts/pm-requirements-candidates.json +53 -0
- package/specialists/org/contracts/product-manager-to-architect.json +30 -10
- package/specialists/org/contracts/product-manager-to-data-analyst.json +13 -3
- package/specialists/org/contracts/product-manager-to-ux-researcher.json +15 -5
- package/specialists/org/contracts/qa-to-release-manager.json +11 -3
- package/specialists/org/contracts/researcher-to-architect.json +10 -2
- package/specialists/org/contracts/researcher-to-product-manager.json +16 -5
- package/specialists/org/contracts/reviewer-to-security.json +17 -3
- package/specialists/org/contracts/test-automation-to-engineer.json +11 -3
- package/specialists/org/contracts/trace-reviewer-to-sre.json +12 -4
- package/specialists/org/contracts/user-to-construct.json +11 -4
- package/specialists/org/frameworks/cx-architect-constraint-option-failure.md +66 -0
- package/specialists/org/frameworks/cx-engineer-feasibility-blast-radius.md +66 -0
- package/specialists/org/frameworks/cx-ops-dependency-sequencing.md +74 -0
- package/specialists/org/frameworks/cx-pm-value-tradeoff.md +66 -0
- package/specialists/org/frameworks/cx-qa-risk-based-coverage.md +69 -0
- package/specialists/org/groups/engineering-group.json +2 -6
- package/specialists/org/groups/governance-group.json +2 -3
- package/specialists/org/groups/operations-group.json +5 -8
- package/specialists/org/groups/product-group.json +4 -8
- package/specialists/org/groups/quality-group.json +3 -7
- package/specialists/org/groups/strategy-group.json +6 -9
- package/specialists/org/models.json.example +8 -0
- package/specialists/org/scopes/creative.json +6 -1
- package/specialists/org/scopes/operations.json +7 -1
- package/specialists/org/scopes/research.json +6 -1
- package/specialists/org/scopes/rnd.json +7 -1
- package/specialists/org/specialists/cx-architect.json +27 -8
- package/specialists/org/specialists/cx-designer.json +22 -8
- package/specialists/org/specialists/cx-engineer.json +71 -7
- package/specialists/org/specialists/cx-operations.json +89 -15
- package/specialists/org/specialists/cx-orchestrator.json +16 -4
- package/specialists/org/specialists/cx-product-manager.json +28 -9
- package/specialists/org/specialists/cx-qa.json +16 -6
- package/specialists/org/specialists/cx-researcher.json +41 -7
- package/specialists/org/specialists/cx-reviewer.json +61 -11
- package/specialists/org/specialists/cx-security.json +30 -10
- package/specialists/org/teams/design-team.json +3 -4
- package/specialists/org/teams/engineering-team.json +3 -10
- package/specialists/org/teams/governance-team.json +3 -5
- package/specialists/org/teams/operations-team.json +6 -12
- package/specialists/org/teams/product-management-team.json +2 -3
- package/specialists/org/teams/quality-team.json +4 -12
- package/specialists/org/teams/research-team.json +3 -3
- package/specialists/org/teams/strategy-team.json +7 -14
- package/specialists/prompts/cx-architect.md +20 -1
- package/specialists/prompts/cx-data-analyst.md +1 -1
- package/specialists/prompts/cx-designer.md +12 -4
- package/specialists/prompts/cx-engineer.md +15 -1
- package/specialists/prompts/cx-operations.md +14 -2
- package/specialists/prompts/cx-orchestrator.md +9 -5
- package/specialists/prompts/cx-product-manager.md +5 -1
- package/specialists/prompts/cx-qa.md +6 -2
- package/specialists/prompts/cx-researcher.md +27 -31
- package/specialists/prompts/cx-reviewer.md +19 -1
- package/specialists/prompts/cx-security.md +5 -1
- package/templates/distribution/construct-brand.typ +123 -59
- package/templates/distribution/construct-decision.typ +6 -3
- package/templates/distribution/construct-pdf.typ +11 -4
- package/templates/distribution/construct-prd.typ +6 -3
- package/templates/distribution/construct-research.typ +7 -4
- package/vendor/pandoc-ext/README.md +3 -0
- package/vendor/pandoc-ext/diagram.lua +687 -0
- package/lib/providers/contract/adapters/git/index.mjs +0 -115
- package/lib/providers/contract/adapters/slack/index.mjs +0 -175
- package/specialists/org/contracts/business-strategist-to-product-manager.json +0 -39
- package/specialists/org/contracts/construct-to-rd-lead.json +0 -53
- package/specialists/org/contracts/data-engineer-to-platform-engineer.json +0 -36
- package/specialists/org/contracts/designer-to-accessibility.json +0 -36
- package/specialists/org/contracts/platform-engineer-to-engineer.json +0 -31
- package/specialists/org/contracts/qa-to-test-automation.json +0 -42
- package/specialists/org/contracts/rd-lead-to-architect.json +0 -38
- package/specialists/org/contracts/sre-to-release-manager.json +0 -36
- package/specialists/org/specialists/cx-accessibility.json +0 -69
- package/specialists/org/specialists/cx-ai-engineer.json +0 -75
- package/specialists/org/specialists/cx-business-strategist.json +0 -72
- package/specialists/org/specialists/cx-data-engineer.json +0 -71
- package/specialists/org/specialists/cx-devil-advocate.json +0 -71
- package/specialists/org/specialists/cx-docs-keeper.json +0 -82
- package/specialists/org/specialists/cx-evaluator.json +0 -69
- package/specialists/org/specialists/cx-explorer.json +0 -69
- package/specialists/org/specialists/cx-legal-compliance.json +0 -74
- package/specialists/org/specialists/cx-oracle.json +0 -46
- package/specialists/org/specialists/cx-platform-engineer.json +0 -80
- package/specialists/org/specialists/cx-rd-lead.json +0 -73
- package/specialists/org/specialists/cx-release-manager.json +0 -77
- package/specialists/org/specialists/cx-sre.json +0 -94
- package/specialists/org/specialists/cx-test-automation.json +0 -69
- package/specialists/org/specialists/cx-trace-reviewer.json +0 -69
- package/specialists/org/specialists/cx-ux-researcher.json +0 -68
- package/specialists/org/teams/accessibility-team.json +0 -39
- package/specialists/org/teams/ux-research-team.json +0 -39
- package/specialists/prompts/cx-accessibility.md +0 -55
- package/specialists/prompts/cx-ai-engineer.md +0 -124
- package/specialists/prompts/cx-business-strategist.md +0 -58
- package/specialists/prompts/cx-data-engineer.md +0 -55
- package/specialists/prompts/cx-devil-advocate.md +0 -59
- package/specialists/prompts/cx-docs-keeper.md +0 -164
- package/specialists/prompts/cx-evaluator.md +0 -49
- package/specialists/prompts/cx-explorer.md +0 -71
- package/specialists/prompts/cx-legal-compliance.md +0 -58
- package/specialists/prompts/cx-oracle.md +0 -98
- package/specialists/prompts/cx-platform-engineer.md +0 -97
- package/specialists/prompts/cx-rd-lead.md +0 -59
- package/specialists/prompts/cx-release-manager.md +0 -54
- package/specialists/prompts/cx-sre.md +0 -111
- package/specialists/prompts/cx-test-automation.md +0 -55
- package/specialists/prompts/cx-trace-reviewer.md +0 -101
- package/specialists/prompts/cx-ux-researcher.md +0 -53
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/mcp/tool-safety.mjs — per-tool safety classification for the MCP catalog.
|
|
3
|
+
*
|
|
4
|
+
* One entry per tool name in lib/mcp/server.mjs's ALL_TOOL_DEFS (plus the `call`
|
|
5
|
+
* gateway). `class` is the host-facing risk tier; `filesystem`/`network`/`process`
|
|
6
|
+
* name which resource classes the tool can touch, derived from each tool's own
|
|
7
|
+
* description and input schema. withSafetyEnvelope (server.mjs) throws if a tool
|
|
8
|
+
* is missing here, so a newly added tool cannot ship without classification.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const READ = 'read';
|
|
12
|
+
const WRITE = 'write';
|
|
13
|
+
const DESTRUCTIVE = 'destructive';
|
|
14
|
+
|
|
15
|
+
export const DEFAULT_OUTPUT_SCHEMA = Object.freeze({ type: 'object' });
|
|
16
|
+
|
|
17
|
+
export const TOOL_SAFETY = Object.freeze({
|
|
18
|
+
agent_health: { class: READ, filesystem: 'read', network: 'none', process: 'none' },
|
|
19
|
+
summarize_diff: { class: READ, filesystem: 'read', network: 'none', process: 'spawn' },
|
|
20
|
+
scan_file: { class: READ, filesystem: 'read', network: 'none', process: 'none' },
|
|
21
|
+
extract_document_text: { class: READ, filesystem: 'read', network: 'none', process: 'spawn' },
|
|
22
|
+
ingest_document: { class: WRITE, filesystem: 'write', network: 'none', process: 'spawn' },
|
|
23
|
+
infer_document_schema: { class: WRITE, filesystem: 'write', network: 'fetch', process: 'spawn' },
|
|
24
|
+
list_schema_artifacts: { class: READ, filesystem: 'read', network: 'none', process: 'none' },
|
|
25
|
+
storage_status: { class: READ, filesystem: 'read', network: 'none', process: 'none' },
|
|
26
|
+
storage_sync: { class: WRITE, filesystem: 'write', network: 'none', process: 'none' },
|
|
27
|
+
storage_reset: { class: DESTRUCTIVE, filesystem: 'write', network: 'none', process: 'none' },
|
|
28
|
+
delete_ingested_artifacts: { class: DESTRUCTIVE, filesystem: 'write', network: 'none', process: 'none' },
|
|
29
|
+
project_context: { class: READ, filesystem: 'read', network: 'none', process: 'spawn' },
|
|
30
|
+
get_skill: { class: READ, filesystem: 'read', network: 'none', process: 'none' },
|
|
31
|
+
orchestration_policy: { class: WRITE, filesystem: 'write', network: 'none', process: 'none' },
|
|
32
|
+
list_skills: { class: READ, filesystem: 'read', network: 'none', process: 'none' },
|
|
33
|
+
worker_run: { class: WRITE, filesystem: 'write', network: 'fetch', process: 'spawn' },
|
|
34
|
+
broker_check: { class: READ, filesystem: 'read', network: 'none', process: 'none' },
|
|
35
|
+
agent_contract: { class: READ, filesystem: 'read', network: 'none', process: 'none' },
|
|
36
|
+
find_tool: { class: READ, filesystem: 'read', network: 'none', process: 'none' },
|
|
37
|
+
get_template: { class: READ, filesystem: 'read', network: 'none', process: 'none' },
|
|
38
|
+
list_templates: { class: READ, filesystem: 'read', network: 'none', process: 'none' },
|
|
39
|
+
search_skills: { class: READ, filesystem: 'read', network: 'none', process: 'none' },
|
|
40
|
+
list_teams: { class: READ, filesystem: 'read', network: 'none', process: 'none' },
|
|
41
|
+
suggest_skills: { class: READ, filesystem: 'read', network: 'none', process: 'none' },
|
|
42
|
+
cx_trace: { class: WRITE, filesystem: 'write', network: 'none', process: 'none' },
|
|
43
|
+
cx_score: { class: WRITE, filesystem: 'write', network: 'none', process: 'none' },
|
|
44
|
+
cx_trace_update: { class: WRITE, filesystem: 'write', network: 'none', process: 'none' },
|
|
45
|
+
session_list: { class: READ, filesystem: 'read', network: 'none', process: 'none' },
|
|
46
|
+
session_load: { class: READ, filesystem: 'read', network: 'none', process: 'none' },
|
|
47
|
+
session_search: { class: READ, filesystem: 'read', network: 'none', process: 'none' },
|
|
48
|
+
session_save: { class: WRITE, filesystem: 'write', network: 'none', process: 'none' },
|
|
49
|
+
memory_search: { class: READ, filesystem: 'read', network: 'none', process: 'none' },
|
|
50
|
+
memory_add_observations: { class: WRITE, filesystem: 'write', network: 'none', process: 'none' },
|
|
51
|
+
memory_create_entities: { class: WRITE, filesystem: 'write', network: 'none', process: 'none' },
|
|
52
|
+
memory_recent: { class: READ, filesystem: 'read', network: 'none', process: 'none' },
|
|
53
|
+
rovo_search: { class: READ, filesystem: 'none', network: 'fetch', process: 'none' },
|
|
54
|
+
efficiency_snapshot: { class: READ, filesystem: 'read', network: 'none', process: 'none' },
|
|
55
|
+
session_usage: { class: READ, filesystem: 'read', network: 'none', process: 'none' },
|
|
56
|
+
provider_fetch: { class: WRITE, filesystem: 'write', network: 'fetch', process: 'none' },
|
|
57
|
+
provider_write: { class: DESTRUCTIVE, filesystem: 'write', network: 'fetch', process: 'none' },
|
|
58
|
+
scope_show: { class: READ, filesystem: 'read', network: 'none', process: 'none' },
|
|
59
|
+
scope_list: { class: READ, filesystem: 'read', network: 'none', process: 'none' },
|
|
60
|
+
scope_drafts: { class: READ, filesystem: 'read', network: 'none', process: 'none' },
|
|
61
|
+
scope_health: { class: READ, filesystem: 'read', network: 'none', process: 'none' },
|
|
62
|
+
outcomes_summary: { class: WRITE, filesystem: 'write', network: 'none', process: 'none' },
|
|
63
|
+
outcomes_record: { class: WRITE, filesystem: 'write', network: 'none', process: 'none' },
|
|
64
|
+
knowledge_add: { class: WRITE, filesystem: 'write', network: 'none', process: 'none' },
|
|
65
|
+
scope_create: { class: WRITE, filesystem: 'write', network: 'none', process: 'none' },
|
|
66
|
+
scope_archive: { class: DESTRUCTIVE, filesystem: 'write', network: 'none', process: 'none' },
|
|
67
|
+
sandbox_list: { class: READ, filesystem: 'read', network: 'none', process: 'none' },
|
|
68
|
+
knowledge_graph_ask: { class: READ, filesystem: 'read', network: 'none', process: 'none' },
|
|
69
|
+
learning_status: { class: READ, filesystem: 'read', network: 'none', process: 'none' },
|
|
70
|
+
document_export: { class: WRITE, filesystem: 'write', network: 'none', process: 'spawn' },
|
|
71
|
+
publish_detect: { class: READ, filesystem: 'read', network: 'none', process: 'spawn' },
|
|
72
|
+
publish_run: { class: WRITE, filesystem: 'write', network: 'none', process: 'spawn' },
|
|
73
|
+
knowledge_search: { class: READ, filesystem: 'read', network: 'none', process: 'none' },
|
|
74
|
+
workflow_init: { class: WRITE, filesystem: 'write', network: 'none', process: 'none' },
|
|
75
|
+
workflow_add_task: { class: WRITE, filesystem: 'write', network: 'none', process: 'none' },
|
|
76
|
+
workflow_update_task: { class: WRITE, filesystem: 'write', network: 'none', process: 'none' },
|
|
77
|
+
workflow_needs_main_input: { class: WRITE, filesystem: 'write', network: 'none', process: 'none' },
|
|
78
|
+
workflow_validate: { class: READ, filesystem: 'read', network: 'none', process: 'none' },
|
|
79
|
+
workflow_status: { class: READ, filesystem: 'read', network: 'none', process: 'none' },
|
|
80
|
+
workflow_contract_validate: { class: READ, filesystem: 'read', network: 'none', process: 'none' },
|
|
81
|
+
workflow_import_plan: { class: WRITE, filesystem: 'write', network: 'none', process: 'none' },
|
|
82
|
+
cx_trace_telemetry: { class: WRITE, filesystem: 'write', network: 'none', process: 'none' },
|
|
83
|
+
artifact_workflow: { class: WRITE, filesystem: 'write', network: 'none', process: 'spawn' },
|
|
84
|
+
author_artifact: { class: WRITE, filesystem: 'write', network: 'none', process: 'none' },
|
|
85
|
+
model_resolve: { class: READ, filesystem: 'read', network: 'none', process: 'none' },
|
|
86
|
+
triage_recommend: { class: READ, filesystem: 'read', network: 'none', process: 'spawn' },
|
|
87
|
+
workflow_invoke: { class: WRITE, filesystem: 'write', network: 'none', process: 'spawn' },
|
|
88
|
+
capability_describe: { class: READ, filesystem: 'read', network: 'none', process: 'none' },
|
|
89
|
+
construct_execution_resolve: { class: READ, filesystem: 'read', network: 'none', process: 'none' },
|
|
90
|
+
orchestration_run: { class: WRITE, filesystem: 'write', network: 'fetch', process: 'none' },
|
|
91
|
+
web_search: { class: READ, filesystem: 'none', network: 'fetch', process: 'none' },
|
|
92
|
+
orchestration_status: { class: READ, filesystem: 'read', network: 'none', process: 'none' },
|
|
93
|
+
orchestration_readiness: { class: READ, filesystem: 'read', network: 'none', process: 'none' },
|
|
94
|
+
call: { class: DESTRUCTIVE, filesystem: 'write', network: 'fetch', process: 'spawn' },
|
|
95
|
+
});
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/mcp/tool-surface-parity.mjs — partition invariant for the MCP tool surface.
|
|
3
|
+
*
|
|
4
|
+
* The exposed surface is split into a flat core (CORE_TOOL_NAMES) and a long tail
|
|
5
|
+
* reached through the `call` gateway. Both are hand-maintained beside the catalog
|
|
6
|
+
* (ALL_TOOL_DEFS), so a typo in a core name silently drops a tool from BOTH the
|
|
7
|
+
* flat surface and the gateway enum, making it unreachable. These helpers enforce
|
|
8
|
+
* the partition from the catalog as the single source of truth: every declared
|
|
9
|
+
* core name must be a real catalog tool, and the flat core plus the gateway enum
|
|
10
|
+
* must together cover the catalog exactly once (no gap, no overlap, no phantom).
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
// A core name that is not in the catalog is a typo or a stale entry; it must be
|
|
14
|
+
// rejected loudly rather than silently dropping the tool from every surface.
|
|
15
|
+
|
|
16
|
+
export function assertCoreSubsetOfCatalog(coreNames, catalog) {
|
|
17
|
+
const catalogSet = catalog instanceof Set ? catalog : new Set(catalog);
|
|
18
|
+
const phantom = [...coreNames].filter((name) => !catalogSet.has(name));
|
|
19
|
+
if (phantom.length > 0) {
|
|
20
|
+
throw new Error(
|
|
21
|
+
`tool-surface-parity: ${phantom.length} core tool name(s) absent from the catalog: ${phantom.join(', ')} — `
|
|
22
|
+
+ 'a typo in CORE_TOOL_NAMES drops the tool from both the flat surface and the call enum.',
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// The flat core and the gateway enum must partition the catalog exactly once.
|
|
29
|
+
// Returns the partition diagnostics; assertToolSurfacePartition throws on any break.
|
|
30
|
+
|
|
31
|
+
export function checkToolSurfacePartition({ catalog, flat, enumNames }) {
|
|
32
|
+
const catalogSet = catalog instanceof Set ? catalog : new Set(catalog);
|
|
33
|
+
const surfaced = [...flat, ...enumNames];
|
|
34
|
+
|
|
35
|
+
const overlap = flat.filter((name) => enumNames.includes(name));
|
|
36
|
+
const duplicates = [...new Set(surfaced.filter((name, idx) => surfaced.indexOf(name) !== idx))];
|
|
37
|
+
const missing = [...catalogSet].filter((name) => !flat.includes(name) && !enumNames.includes(name));
|
|
38
|
+
const phantom = [...new Set(surfaced.filter((name) => !catalogSet.has(name)))];
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
ok: overlap.length === 0 && duplicates.length === 0 && missing.length === 0 && phantom.length === 0,
|
|
42
|
+
overlap: overlap.sort(),
|
|
43
|
+
duplicates: duplicates.sort(),
|
|
44
|
+
missing: missing.sort(),
|
|
45
|
+
phantom: phantom.sort(),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function assertToolSurfacePartition({ catalog, flat, enumNames }) {
|
|
50
|
+
const result = checkToolSurfacePartition({ catalog, flat, enumNames });
|
|
51
|
+
if (!result.ok) {
|
|
52
|
+
const parts = [];
|
|
53
|
+
if (result.overlap.length) parts.push(`flat AND enum: ${result.overlap.join(', ')}`);
|
|
54
|
+
if (result.duplicates.length) parts.push(`surfaced twice: ${result.duplicates.join(', ')}`);
|
|
55
|
+
if (result.missing.length) parts.push(`unreachable: ${result.missing.join(', ')}`);
|
|
56
|
+
if (result.phantom.length) parts.push(`not in catalog: ${result.phantom.join(', ')}`);
|
|
57
|
+
throw new Error(`tool-surface-parity: partition broken — ${parts.join('; ')}`);
|
|
58
|
+
}
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/mcp/tools/orchestration-delegation-next.tool.mjs — self-registered MCP
|
|
3
|
+
* tool (lib/mcp/tool-registry.mjs) that advances an orchestration-policy
|
|
4
|
+
* route's delegation chain by exactly one step (ADR-0067, construct-rf26.9).
|
|
5
|
+
*
|
|
6
|
+
* orchestration_policy classifies a request into intent/track/specialists/
|
|
7
|
+
* gates and — for a non-immediate track — the full contractChain, for a
|
|
8
|
+
* caller to read and self-sequence. This tool is the flow-engine-backed
|
|
9
|
+
* alternative for the SEQUENCING part specifically: it rebuilds the same
|
|
10
|
+
* deterministic route (routeRequest is a pure function of request/fileCount/
|
|
11
|
+
* moduleCount/introducesContract/explicitDrive) and drives its implied
|
|
12
|
+
* delegation flow (lib/orchestration/delegation-flow.mjs) one checkpointed
|
|
13
|
+
* step at a time, returning only the current specialist's delegation — never
|
|
14
|
+
* the rest of the chain. Call it once per specialist, with the same run_id,
|
|
15
|
+
* until `done` is true; state persists across calls (and across process
|
|
16
|
+
* restarts) via lib/flows/checkpoint.mjs under the machine-scoped state root.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { routeRequest } from '../../orchestration-policy.mjs';
|
|
20
|
+
import { advanceDelegation } from '../../orchestration/delegation-flow.mjs';
|
|
21
|
+
|
|
22
|
+
export const TOOL_DEFS = [
|
|
23
|
+
{
|
|
24
|
+
name: 'orchestration_delegation_next',
|
|
25
|
+
description:
|
|
26
|
+
'Advance a request\'s delegation chain by exactly one step. Pass the same classification inputs '
|
|
27
|
+
+ 'you would give orchestration_policy (request/fileCount/moduleCount/introducesContract/explicitDrive) '
|
|
28
|
+
+ 'plus a run_id you mint and keep for this dispatch (e.g. a session or task id). The first call starts '
|
|
29
|
+
+ 'the chain and returns the first specialist\'s delegation; each subsequent call with the same run_id '
|
|
30
|
+
+ 'returns the next one. Never returns more than the current step — act on currentDelegation only, then '
|
|
31
|
+
+ 'call again. `done: true` with `currentDelegation: null` means the chain is exhausted (or the route\'s '
|
|
32
|
+
+ 'track resolved to no specialists at all, e.g. immediate).',
|
|
33
|
+
inputSchema: {
|
|
34
|
+
type: 'object',
|
|
35
|
+
required: ['request', 'run_id'],
|
|
36
|
+
properties: {
|
|
37
|
+
request: { type: 'string', description: 'The same request text passed to orchestration_policy.' },
|
|
38
|
+
run_id: { type: 'string', description: 'Caller-chosen id for this dispatch. Reuse it across calls to advance the same chain.' },
|
|
39
|
+
fileCount: { type: 'number', description: 'Approximate number of files involved.' },
|
|
40
|
+
moduleCount: { type: 'number', description: 'Approximate number of modules involved.' },
|
|
41
|
+
introducesContract: { type: 'boolean', description: 'Whether the change introduces a new contract/dependency.' },
|
|
42
|
+
explicitDrive: { type: 'boolean', description: 'Whether drive/full-send mode is explicitly active.' },
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
outputSchema: { type: 'object' },
|
|
46
|
+
safety: { class: 'write', filesystem: 'write', network: 'none', process: 'none' },
|
|
47
|
+
},
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
export async function orchestrationDelegationNext(args = {}, { cwd = process.cwd() } = {}) {
|
|
51
|
+
const { request, run_id: runId } = args;
|
|
52
|
+
if (!request || typeof request !== 'string') return { error: 'Missing "request".' };
|
|
53
|
+
if (!runId || typeof runId !== 'string') return { error: 'Missing "run_id" — mint and reuse one id for this dispatch.' };
|
|
54
|
+
|
|
55
|
+
const route = routeRequest({
|
|
56
|
+
request,
|
|
57
|
+
fileCount: args.fileCount ?? 0,
|
|
58
|
+
moduleCount: args.moduleCount ?? 0,
|
|
59
|
+
introducesContract: args.introducesContract ?? false,
|
|
60
|
+
explicitDrive: args.explicitDrive ?? false,
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
return advanceDelegation(cwd, runId, route);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export const TOOL_HANDLERS = {
|
|
67
|
+
orchestration_delegation_next: orchestrationDelegationNext,
|
|
68
|
+
};
|
|
@@ -13,16 +13,61 @@
|
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
import { runOrchestration, startRun, getRun, getRuns } from '../../orchestration/runtime.mjs';
|
|
16
|
+
import { HOST } from '../../orchestration/worker.mjs';
|
|
17
|
+
import { loadProjectConfig } from '../../config/project-config.mjs';
|
|
18
|
+
import { governWebResults } from './web-search-governance.mjs';
|
|
19
|
+
import { resolveNonNegativeSetting } from '../../env-config.mjs';
|
|
16
20
|
|
|
17
|
-
|
|
21
|
+
// Host loop instructions echoed on an awaiting-host run so the calling agent
|
|
22
|
+
// never has to guess the protocol: execute each materialized prompt as that
|
|
23
|
+
// specialist, submit the result, and follow next_task until it is null.
|
|
24
|
+
// Worded to be un-misreadable as a failure: a VS Code host once read "did not
|
|
25
|
+
// execute" as prepare-only-and-stuck, retried with worker_backend "provider",
|
|
26
|
+
// and died on missing API credits — so the text must lead with success and
|
|
27
|
+
// explicitly forbid that fallback.
|
|
28
|
+
|
|
29
|
+
const HOST_INSTRUCTIONS =
|
|
30
|
+
'ACTION REQUIRED — this run succeeded and is now waiting on YOU, the calling agent. The host worker '
|
|
31
|
+
+ 'backend means Construct materialized each specialist prompt and you execute them in your own '
|
|
32
|
+
+ 'session (no API credits are spent). This is the normal path, not a failure: do NOT re-run with '
|
|
33
|
+
+ 'worker_backend "provider" (real API spend, needs provider credits) and do not report the run as '
|
|
34
|
+
+ 'incomplete. For each task in `tasks`, execute its `system`/`user` prompt yourself as that '
|
|
35
|
+
+ 'specialist role, in order, then submit the result by calling orchestration_task_result with '
|
|
36
|
+
+ '{ run_id, task_id, output }. Its response carries next_task (the next prompt to execute) or null '
|
|
37
|
+
+ 'once the run is terminal — keep submitting until next_task is null, then re-read the run via '
|
|
38
|
+
+ 'orchestration_status if you need the final consolidated output.';
|
|
39
|
+
|
|
40
|
+
// Every state a run can rest in. `degraded` and `completed-prepare-only` are
|
|
41
|
+
// terminal too — the poll loop must stop on them or a finished degraded/prepare-only
|
|
42
|
+
// remote run reads as "still executing" until the overall deadline elapses.
|
|
43
|
+
|
|
44
|
+
const TERMINAL = ['completed', 'completed-with-failures', 'completed-prepare-only', 'degraded', 'cancelled', 'error'];
|
|
45
|
+
|
|
46
|
+
export function shapeRun(run) {
|
|
47
|
+
const hasTasks = Array.isArray(run.tasks) && run.tasks.length > 0;
|
|
48
|
+
const allPrepared = hasTasks && run.tasks.every((t) => t.status === 'prepared');
|
|
49
|
+
const degraded = run.degraded ?? run.execution?.degraded ?? false;
|
|
50
|
+
|
|
51
|
+
// Terminal-status taxonomy (construct-fbxv.1): a degraded run never surfaces as a
|
|
52
|
+
// bare 'completed'. All-prepared is the more specific "no specialist executed"
|
|
53
|
+
// signal, so it wins; otherwise any degraded run — whether it prepared zero tasks
|
|
54
|
+
// or executed with a capability gap — reports 'degraded'. Legacy runs persisted as
|
|
55
|
+
// bare 'completed' before this taxonomy are re-derived the same way.
|
|
56
|
+
const shapedStatus = allPrepared
|
|
57
|
+
? 'completed-prepare-only'
|
|
58
|
+
: degraded && run.status === 'completed'
|
|
59
|
+
? 'degraded'
|
|
60
|
+
: run.status;
|
|
18
61
|
|
|
19
|
-
function shapeRun(run) {
|
|
20
62
|
return {
|
|
21
63
|
runId: run.runId,
|
|
22
|
-
status:
|
|
64
|
+
status: shapedStatus,
|
|
65
|
+
prepareOnly: allPrepared,
|
|
66
|
+
runMode: run.runMode ?? null,
|
|
67
|
+
semantics: run.semantics ?? null,
|
|
23
68
|
executionMode: run.execution?.executionMode,
|
|
24
|
-
degraded
|
|
25
|
-
degradationReason: run.execution?.degradationReason ?? null,
|
|
69
|
+
degraded,
|
|
70
|
+
degradationReason: run.degradationReason ?? run.execution?.degradationReason ?? null,
|
|
26
71
|
intent: run.plan?.intent ?? null,
|
|
27
72
|
track: run.plan?.track ?? null,
|
|
28
73
|
suggestedWorkflowType: run.plan?.suggestedWorkflowType ?? null,
|
|
@@ -31,10 +76,38 @@ function shapeRun(run) {
|
|
|
31
76
|
tasks: (run.tasks || []).map((t) => ({
|
|
32
77
|
id: t.id, role: t.role, status: t.status, executor: t.executor,
|
|
33
78
|
output: t.output ?? null, reasoning: t.reasoning ?? null, error: t.error ?? null,
|
|
79
|
+
webCapability: t.webCapability ?? null,
|
|
80
|
+
webEvidence: t.webEvidence ?? null,
|
|
81
|
+
webSearchRequests: t.webSearchRequests ?? 0,
|
|
82
|
+
// Host-backend fields only: the materialized prompt an awaiting-host task
|
|
83
|
+
// carries (nulled out once submitHostTaskResult records a real result),
|
|
84
|
+
// and provenanceSource — present only on a host-reported result, so it can
|
|
85
|
+
// never be confused with a construct-verified provider execution.
|
|
86
|
+
...(t.hostPrompt ? { system: t.hostPrompt.system, user: t.hostPrompt.user } : {}),
|
|
87
|
+
...(t.provenanceSource ? { provenanceSource: t.provenanceSource } : {}),
|
|
34
88
|
})),
|
|
35
89
|
};
|
|
36
90
|
}
|
|
37
91
|
|
|
92
|
+
// Fail-closed remote ingress guard (ADR-0050): the remote orchestration service is
|
|
93
|
+
// out-of-repo and cannot be trusted to govern. Re-run every task's webEvidence through
|
|
94
|
+
// the single F08 grader so a citation can never arrive trusted or ungoverned, and mark the
|
|
95
|
+
// run degraded if any item was not already trust:'untrusted' with a valid Admiralty grade.
|
|
96
|
+
|
|
97
|
+
export function governRemoteWebEvidence(run, now = Date.now()) {
|
|
98
|
+
let tampered = false;
|
|
99
|
+
for (const t of run.tasks || []) {
|
|
100
|
+
if (!Array.isArray(t.webEvidence) || t.webEvidence.length === 0) continue;
|
|
101
|
+
if (t.webEvidence.some((e) => e?.trust !== 'untrusted' || !/^[A-F][1-6]$/.test(e?.admiralty || ''))) tampered = true;
|
|
102
|
+
t.webEvidence = governWebResults(t.webEvidence, { now });
|
|
103
|
+
}
|
|
104
|
+
if (tampered) {
|
|
105
|
+
run.degraded = true;
|
|
106
|
+
run.degradationReason = run.degradationReason || 'remote-web-evidence-regoverned';
|
|
107
|
+
}
|
|
108
|
+
return run;
|
|
109
|
+
}
|
|
110
|
+
|
|
38
111
|
function toRequest(args) {
|
|
39
112
|
return {
|
|
40
113
|
request: args.request,
|
|
@@ -48,6 +121,27 @@ function toRequest(args) {
|
|
|
48
121
|
};
|
|
49
122
|
}
|
|
50
123
|
|
|
124
|
+
// Only MCP dispatch reaches orchestrationRun() — an in-process CLI call goes
|
|
125
|
+
// through runOrchestration/planRun directly (see bin/construct's `orchestrate
|
|
126
|
+
// run`), never through this function — so the host-execution default belongs
|
|
127
|
+
// exactly here: when the caller gave no explicit worker_backend AND the
|
|
128
|
+
// project's own construct.config.json declares none, an MCP-originated run
|
|
129
|
+
// defaults to the host executing specialists in its own session rather than
|
|
130
|
+
// inline prepare-only or a provider API spend the caller may have no credits
|
|
131
|
+
// for. The RAW (pre-default-merge) project config is the check that matters —
|
|
132
|
+
// loadProjectConfig always merges DEFAULT_PROJECT_CONFIG in, so the merged
|
|
133
|
+
// config's workerBackend is never actually absent; only the raw file reveals
|
|
134
|
+
// whether the user configured one.
|
|
135
|
+
|
|
136
|
+
function resolveMcpDefaultBackend(explicitBackend, { cwd, env }) {
|
|
137
|
+
if (explicitBackend) return explicitBackend;
|
|
138
|
+
try {
|
|
139
|
+
const { raw } = loadProjectConfig(cwd, env);
|
|
140
|
+
if (raw?.orchestration?.workerBackend) return undefined;
|
|
141
|
+
} catch { /* no resolvable config — fall through to the host default */ }
|
|
142
|
+
return HOST;
|
|
143
|
+
}
|
|
144
|
+
|
|
51
145
|
// A remote orchestration service is opt-in: only when CONSTRUCT_ORCHESTRATION_URL
|
|
52
146
|
// is set (team / enterprise). The token comes from the Construct env, not from
|
|
53
147
|
// the dashboard server, so the in-process default path carries no dashboard coupling.
|
|
@@ -67,6 +161,17 @@ function unreachable(base, detail) {
|
|
|
67
161
|
};
|
|
68
162
|
}
|
|
69
163
|
|
|
164
|
+
// A bounded fetch timing out is not the same failure as a genuinely unreachable host
|
|
165
|
+
// (ORCH-004 AC3): the error text must say timeout and name the bound so a healthy but
|
|
166
|
+
// slow service is never misreported as network-down.
|
|
167
|
+
|
|
168
|
+
function remoteFetchFailed(base, err) {
|
|
169
|
+
if (err?.name === 'RemoteFetchTimeoutError') {
|
|
170
|
+
return { error: `${err.message}. Raise CONSTRUCT_ORCHESTRATION_TIMEOUT_MS if the remote service is healthy but slow.`, service: base, failFast: true };
|
|
171
|
+
}
|
|
172
|
+
return unreachable(base, err.message);
|
|
173
|
+
}
|
|
174
|
+
|
|
70
175
|
export async function orchestrationRun(args = {}, { env = process.env, cwd = process.cwd(), fetchImpl = fetch } = {}) {
|
|
71
176
|
const { request, worker_backend, wait = true, timeout_ms = 120000 } = args;
|
|
72
177
|
if (!request || typeof request !== 'string') {
|
|
@@ -75,15 +180,17 @@ export async function orchestrationRun(args = {}, { env = process.env, cwd = pro
|
|
|
75
180
|
|
|
76
181
|
const reqObj = toRequest(args);
|
|
77
182
|
const remote = remoteService(env);
|
|
78
|
-
if (remote) return runViaService(remote, reqObj, { wait, timeout_ms, workerBackend: worker_backend, fetchImpl });
|
|
183
|
+
if (remote) return runViaService(remote, reqObj, { wait, timeout_ms, workerBackend: worker_backend, fetchImpl, env });
|
|
79
184
|
|
|
80
|
-
const
|
|
185
|
+
const workerBackend = resolveMcpDefaultBackend(worker_backend, { cwd, env });
|
|
186
|
+
const opts = { env, cwd, workerBackend, fetchImpl };
|
|
81
187
|
try {
|
|
82
188
|
if (!wait) {
|
|
83
189
|
const planned = await startRun(reqObj, opts);
|
|
84
190
|
return { runId: planned.runId, status: planned.status, executionMode: planned.execution?.executionMode, poll: 'orchestration_status with run_id' };
|
|
85
191
|
}
|
|
86
|
-
|
|
192
|
+
const shaped = shapeRun(await runOrchestration(reqObj, opts));
|
|
193
|
+
return shaped.status === 'awaiting-host' ? { ...shaped, hostInstructions: HOST_INSTRUCTIONS } : shaped;
|
|
87
194
|
} catch (err) {
|
|
88
195
|
return { error: `Orchestration failed: ${err.message}`, failFast: true };
|
|
89
196
|
}
|
|
@@ -92,13 +199,16 @@ export async function orchestrationRun(args = {}, { env = process.env, cwd = pro
|
|
|
92
199
|
export async function orchestrationStatus(args = {}, { env = process.env, cwd = process.cwd(), fetchImpl = fetch } = {}) {
|
|
93
200
|
const { run_id, limit = 20 } = args;
|
|
94
201
|
const remote = remoteService(env);
|
|
95
|
-
if (remote) return statusViaService(remote, { run_id, limit }, { fetchImpl });
|
|
202
|
+
if (remote) return statusViaService(remote, { run_id, limit }, { fetchImpl, env });
|
|
96
203
|
try {
|
|
97
204
|
if (run_id) {
|
|
98
205
|
const run = await getRun(cwd, run_id, { env });
|
|
99
|
-
|
|
206
|
+
if (!run) return { error: `No run found with id ${run_id}.` };
|
|
207
|
+
const shaped = shapeRun(run);
|
|
208
|
+
return shaped.status === 'awaiting-host' ? { ...shaped, hostInstructions: HOST_INSTRUCTIONS } : shaped;
|
|
100
209
|
}
|
|
101
|
-
|
|
210
|
+
const runs = await getRuns(cwd, { env, limit });
|
|
211
|
+
return runs.map(shapeRun);
|
|
102
212
|
} catch (err) {
|
|
103
213
|
return { error: `Failed to read orchestration runs: ${err.message}` };
|
|
104
214
|
}
|
|
@@ -108,13 +218,64 @@ export async function orchestrationStatus(args = {}, { env = process.env, cwd =
|
|
|
108
218
|
// is set; the wire shape matches the standalone orchestration service's
|
|
109
219
|
// /api/orchestration/runs contract (ADR-0022).
|
|
110
220
|
|
|
111
|
-
|
|
221
|
+
// Reachability fail-fast belongs to the deadline check in the poll loop, not the
|
|
222
|
+
// per-request budget: a remote LLM-backed run needs a generous per-request window
|
|
223
|
+
// (ORCH-004; construct-o6t8.2). Default is 30s, well above the industry reachability-probe
|
|
224
|
+
// mistake this replaces, and short of the 600s ceiling the OpenAI/Anthropic SDKs use for
|
|
225
|
+
// long completions — an orchestration run/status round trip is shorter-lived than that.
|
|
226
|
+
// Env-overridable via the shared non-negative resolver so an explicit 0 is honored
|
|
227
|
+
// (near-instant abort) rather than silently treated as unset, and garbage/negative
|
|
228
|
+
// values fall back to the default instead of producing a NaN AbortSignal delay.
|
|
229
|
+
|
|
230
|
+
export const DEFAULT_REQUEST_TIMEOUT_MS = 30000;
|
|
231
|
+
|
|
232
|
+
function requestTimeoutMs(env) {
|
|
233
|
+
return resolveNonNegativeSetting(env, 'CONSTRUCT_ORCHESTRATION_TIMEOUT_MS', DEFAULT_REQUEST_TIMEOUT_MS);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Per-request bound on every remote fetch (ORCH-004; construct-o6t8.2 / construct-neq9.6):
|
|
237
|
+
// a timer aborts the request at perRequestMs and races it against a timeout rejection, so
|
|
238
|
+
// a hung request settles the call even when the underlying fetch ignores its own signal.
|
|
239
|
+
// The timer is cleared the instant either side of the race wins, so a healthy fast response
|
|
240
|
+
// leaves no scheduled timer alive to fire and resolve a promise outside its own async scope
|
|
241
|
+
// — the leak that tripped node:test's "resolution still pending after the loop resolved". The
|
|
242
|
+
// rejection carries the numbered RemoteFetchTimeoutError so a bounded timeout stays
|
|
243
|
+
// distinguishable from a genuinely unreachable host, never the signal's own DOMException
|
|
244
|
+
// message ("aborted due to timeout" with no number).
|
|
245
|
+
|
|
246
|
+
class RemoteFetchTimeoutError extends Error {
|
|
247
|
+
constructor(perRequestMs) {
|
|
248
|
+
super(`Remote orchestration request timed out after ${perRequestMs}ms`);
|
|
249
|
+
this.name = 'RemoteFetchTimeoutError';
|
|
250
|
+
this.timeoutMs = perRequestMs;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
async function timedRemoteFetch(fetchImpl, url, opts, perRequestMs) {
|
|
255
|
+
const controller = new AbortController();
|
|
256
|
+
let clearTimer;
|
|
257
|
+
const timeout = new Promise((_, reject) => {
|
|
258
|
+
const timer = setTimeout(() => {
|
|
259
|
+
const err = new RemoteFetchTimeoutError(perRequestMs);
|
|
260
|
+
controller.abort(err);
|
|
261
|
+
reject(err);
|
|
262
|
+
}, perRequestMs);
|
|
263
|
+
clearTimer = () => clearTimeout(timer);
|
|
264
|
+
});
|
|
265
|
+
try {
|
|
266
|
+
return await Promise.race([fetchImpl(url, { ...opts, signal: controller.signal }), timeout]);
|
|
267
|
+
} finally {
|
|
268
|
+
clearTimer();
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
async function runViaService(remote, reqObj, { wait, timeout_ms, workerBackend, fetchImpl, env = process.env, perRequestTimeoutMs = requestTimeoutMs(env) } = {}) {
|
|
112
273
|
const { base, token } = remote;
|
|
113
274
|
const headers = { 'Content-Type': 'application/json', ...(token ? { Authorization: `Bearer ${token}` } : {}) };
|
|
114
275
|
const body = JSON.stringify({ ...reqObj, workerBackend });
|
|
115
276
|
let started;
|
|
116
277
|
try {
|
|
117
|
-
const res = await fetchImpl
|
|
278
|
+
const res = await timedRemoteFetch(fetchImpl, `${base}/api/orchestration/runs`, { method: 'POST', headers, body }, perRequestTimeoutMs);
|
|
118
279
|
if (res.status === 401 || res.status === 403) {
|
|
119
280
|
return { error: `Remote service rejected the request (HTTP ${res.status}). Set CONSTRUCT_ORCHESTRATION_TOKEN (or CONSTRUCT_DASHBOARD_TOKEN).`, service: base, failFast: true };
|
|
120
281
|
}
|
|
@@ -122,7 +283,7 @@ async function runViaService(remote, reqObj, { wait, timeout_ms, workerBackend,
|
|
|
122
283
|
if (!res.ok) return { error: `Remote service error (HTTP ${res.status}): ${envelope.error || 'unknown'}`, service: base };
|
|
123
284
|
started = envelope.data;
|
|
124
285
|
} catch (err) {
|
|
125
|
-
return
|
|
286
|
+
return remoteFetchFailed(base, err);
|
|
126
287
|
}
|
|
127
288
|
|
|
128
289
|
if (!wait) {
|
|
@@ -137,27 +298,44 @@ async function runViaService(remote, reqObj, { wait, timeout_ms, workerBackend,
|
|
|
137
298
|
}
|
|
138
299
|
await new Promise((r) => setTimeout(r, 250));
|
|
139
300
|
try {
|
|
140
|
-
const res = await fetchImpl
|
|
301
|
+
const res = await timedRemoteFetch(fetchImpl, `${base}/api/orchestration/runs/${encodeURIComponent(started.runId)}`, { headers }, perRequestTimeoutMs);
|
|
141
302
|
const envelope = await res.json();
|
|
142
303
|
if (!res.ok) return { error: `Remote service error while polling (HTTP ${res.status}): ${envelope.error || 'unknown'}`, runId: started.runId, service: base };
|
|
143
304
|
run = envelope.data;
|
|
144
305
|
} catch (err) {
|
|
306
|
+
if (err?.name === 'RemoteFetchTimeoutError') {
|
|
307
|
+
return { error: `${err.message} while polling run ${started.runId}. Raise CONSTRUCT_ORCHESTRATION_TIMEOUT_MS if the remote service is healthy but slow.`, runId: started.runId, service: base, failFast: true };
|
|
308
|
+
}
|
|
145
309
|
return { error: `Lost connection to the remote service while polling run ${started.runId}: ${err.message}`, runId: started.runId, service: base, failFast: true };
|
|
146
310
|
}
|
|
147
311
|
}
|
|
148
|
-
return { ...shapeRun(run), service: base };
|
|
312
|
+
return { ...shapeRun(governRemoteWebEvidence(run)), service: base };
|
|
149
313
|
}
|
|
150
314
|
|
|
151
|
-
|
|
315
|
+
// The status poll GET carries the same AbortSignal bound as the run POST (ORCH-004),
|
|
316
|
+
// so a service that accepts the connection but never replies cannot hang this call
|
|
317
|
+
// indefinitely. `timeoutMs` is overridable directly for tests; production callers
|
|
318
|
+
// resolve it from env the same way runViaService does.
|
|
319
|
+
|
|
320
|
+
export async function statusViaService(remote, { run_id, limit } = {}, { fetchImpl, env = process.env, timeoutMs = requestTimeoutMs(env) } = {}) {
|
|
152
321
|
const { base, token } = remote;
|
|
153
322
|
const headers = token ? { Authorization: `Bearer ${token}` } : {};
|
|
154
323
|
const path = run_id ? `/api/orchestration/runs/${encodeURIComponent(run_id)}` : `/api/orchestration/runs?limit=${encodeURIComponent(limit)}`;
|
|
155
324
|
try {
|
|
156
|
-
const res = await fetchImpl
|
|
325
|
+
const res = await timedRemoteFetch(fetchImpl, `${base}${path}`, { headers }, timeoutMs);
|
|
157
326
|
const envelope = await res.json();
|
|
158
327
|
if (!res.ok) return { error: `Remote service error (HTTP ${res.status}): ${envelope.error || 'unknown'}`, service: base };
|
|
328
|
+
// The remote service is out-of-repo and cannot be trusted to have governed its
|
|
329
|
+
// own web evidence (ADR-0050); re-run every run through the F08 grader on the
|
|
330
|
+
// status path exactly as runViaService does, so a citation can never arrive
|
|
331
|
+
// pre-marked trusted. shapeRun then applies the honest terminal-status taxonomy.
|
|
332
|
+
if (Array.isArray(envelope.data)) {
|
|
333
|
+
return envelope.data.map((r) => shapeRun(governRemoteWebEvidence(r)));
|
|
334
|
+
} else if (envelope.data) {
|
|
335
|
+
return shapeRun(governRemoteWebEvidence(envelope.data));
|
|
336
|
+
}
|
|
159
337
|
return envelope.data;
|
|
160
338
|
} catch (err) {
|
|
161
|
-
return
|
|
339
|
+
return remoteFetchFailed(base, err);
|
|
162
340
|
}
|
|
163
341
|
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/mcp/tools/orchestration-task-result.tool.mjs — self-registered MCP tool
|
|
3
|
+
* (LMCP-B5, lib/mcp/tool-registry.mjs) that ingests one host-executed
|
|
4
|
+
* specialist task result for a run planned with worker_backend=host.
|
|
5
|
+
*
|
|
6
|
+
* orchestration_run materializes each specialist's prompt (system + user turn)
|
|
7
|
+
* and stands the run at 'awaiting-host' without ever calling a model itself —
|
|
8
|
+
* the calling host executes each prompt in its own session (the model/
|
|
9
|
+
* subscription it is already running under) and reports the result back here.
|
|
10
|
+
* submitHostTaskResult (lib/orchestration/runtime.mjs) is the single place that
|
|
11
|
+
* validates and records the result and finalizes the run once every task is
|
|
12
|
+
* terminal; this file only shapes that call as an MCP tool and echoes back the
|
|
13
|
+
* next materialized prompt so a host can drive the whole run in a simple loop:
|
|
14
|
+
* call orchestration_run once, then call this tool per task until next_task
|
|
15
|
+
* is null.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { submitHostTaskResult } from '../../orchestration/runtime.mjs';
|
|
19
|
+
import { shapeRun } from './orchestration-run.mjs';
|
|
20
|
+
|
|
21
|
+
export const TOOL_DEFS = [
|
|
22
|
+
{
|
|
23
|
+
name: 'orchestration_task_result',
|
|
24
|
+
description:
|
|
25
|
+
'Submit one host-executed specialist task result for an orchestration run planned with '
|
|
26
|
+
+ 'worker_backend=host (the MCP default). orchestration_run returns each task\'s materialized '
|
|
27
|
+
+ 'prompt (system/user) without executing it; execute that prompt yourself as the named '
|
|
28
|
+
+ 'specialist role, then call this tool with the run_id, task_id, and your output. The '
|
|
29
|
+
+ 'response carries next_task (the next awaiting prompt) or null once the run is terminal — '
|
|
30
|
+
+ 'loop until null. model/provider/reasoning are optional, self-reported fields (never '
|
|
31
|
+
+ 'independently verified), recorded with provenanceSource "host-reported".',
|
|
32
|
+
inputSchema: {
|
|
33
|
+
type: 'object',
|
|
34
|
+
required: ['run_id', 'task_id', 'output'],
|
|
35
|
+
properties: {
|
|
36
|
+
run_id: { type: 'string', description: 'The run id returned by orchestration_run.' },
|
|
37
|
+
task_id: { type: 'string', description: 'The task id this result answers (e.g. "t1"), from orchestration_run\'s task list or a prior call\'s next_task.' },
|
|
38
|
+
output: { type: 'string', description: 'The specialist output you produced for this task. Must be non-empty.' },
|
|
39
|
+
model: { type: 'string', description: 'Optional: the model you used to execute this task (self-reported).' },
|
|
40
|
+
provider: { type: 'string', description: 'Optional: the provider/vendor family you used (self-reported).' },
|
|
41
|
+
reasoning: { type: 'string', description: 'Optional: your reasoning/thinking for this task, if you want it disclosed.' },
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
outputSchema: { type: 'object' },
|
|
45
|
+
safety: { class: 'write', filesystem: 'write', network: 'none', process: 'none' },
|
|
46
|
+
},
|
|
47
|
+
];
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* @param {object} args { run_id, task_id, output, model?, provider?, reasoning? }
|
|
51
|
+
* @param {object} [opts] { env, cwd } — defaulted so this behaves identically whether called
|
|
52
|
+
* directly (tests, CLI) or dispatched by the MCP server (which does not inject opts today,
|
|
53
|
+
* matching orchestration_run/orchestration_status's own convention).
|
|
54
|
+
*/
|
|
55
|
+
export async function orchestrationTaskResult(args = {}, { env = process.env, cwd = process.cwd() } = {}) {
|
|
56
|
+
const { run_id, task_id, output, model, provider, reasoning } = args;
|
|
57
|
+
if (!run_id || typeof run_id !== 'string') return { error: 'Missing "run_id" — the run id from orchestration_run.' };
|
|
58
|
+
if (!task_id || typeof task_id !== 'string') return { error: 'Missing "task_id".' };
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
const { run, nextTask } = await submitHostTaskResult(cwd, run_id, task_id, { output, model, provider, reasoning }, { env });
|
|
62
|
+
const shaped = shapeRun(run);
|
|
63
|
+
return {
|
|
64
|
+
accepted: true,
|
|
65
|
+
run_status: shaped.status,
|
|
66
|
+
next_task: nextTask
|
|
67
|
+
? { task_id: nextTask.id, role: nextTask.role, system: nextTask.hostPrompt?.system ?? null, user: nextTask.hostPrompt?.user ?? null }
|
|
68
|
+
: null,
|
|
69
|
+
};
|
|
70
|
+
} catch (err) {
|
|
71
|
+
return { accepted: false, error: err.message, code: err.code || 'HOST_RESULT_REJECTED' };
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export const TOOL_HANDLERS = {
|
|
76
|
+
orchestration_task_result: orchestrationTaskResult,
|
|
77
|
+
};
|