@yemi33/minions 0.1.2447 → 0.1.2449
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/bin/cli-api-client.js +1 -1
- package/bin/install-internal-minions.js +1382 -44
- package/bin/install-layout.js +150 -0
- package/bin/minions.js +460 -167
- package/dashboard/docs/typography.md +65 -12
- package/dashboard/js/command-center.js +66 -7
- package/dashboard/js/detail-panel.js +36 -0
- package/dashboard/js/memory-panel.js +59 -12
- package/dashboard/js/qa.js +186 -45
- package/dashboard/js/refresh.js +148 -12
- package/dashboard/js/render-dispatch.js +3 -4
- package/dashboard/js/render-inbox.js +2 -2
- package/dashboard/js/render-other.js +3 -3
- package/dashboard/js/render-pipelines.js +14 -0
- package/dashboard/js/render-plans.js +57 -9
- package/dashboard/js/render-prd.js +132 -23
- package/dashboard/js/render-prs.js +195 -166
- package/dashboard/js/render-schedules.js +63 -3
- package/dashboard/js/render-utils.js +3 -3
- package/dashboard/js/render-watches.js +19 -3
- package/dashboard/js/render-work-items.js +238 -30
- package/dashboard/js/settings.js +205 -54
- package/dashboard/js/utils.js +51 -1
- package/dashboard/pages/home.html +1 -1
- package/dashboard/pages/qa.html +1 -16
- package/dashboard/pages/work.html +40 -0
- package/dashboard/shared/cc-limits.js +79 -0
- package/dashboard/shared/pr-filters.js +21 -38
- package/dashboard/shared/project-git-summary.js +1 -1
- package/dashboard/shared/record-filters.js +169 -0
- package/dashboard/shared/watches-source.js +1 -1
- package/dashboard/shared/welcome-popup.js +1 -1
- package/dashboard/shared/wi-filters.js +302 -0
- package/dashboard/slim/body.html +1 -0
- package/dashboard/slim/js/command-send.js +26 -0
- package/dashboard/slim/js/modals-tiles.js +380 -39
- package/dashboard/slim/js/status.js +13 -21
- package/dashboard/slim/layout.html +1 -0
- package/dashboard/slim/panel-bootstrap.js +6 -2
- package/dashboard/slim/styles.css +38 -0
- package/dashboard/styles.css +159 -55
- package/dashboard-build.js +52 -6
- package/dashboard.js +956 -423
- package/docs/README.md +11 -6
- package/docs/api-errors.md +2 -2
- package/docs/architecture-review-2026-07-09.md +1 -1
- package/docs/architecture.excalidraw +2 -2
- package/docs/auto-discovery.md +18 -9
- package/docs/branch-derivation.md +4 -4
- package/docs/capture-demos.js +39 -2
- package/docs/ci-runner-canary.md +123 -0
- package/docs/claude-md-propagation.md +2 -2
- package/docs/cloud-agent-dispatch.md +204 -0
- package/docs/command-center.md +7 -7
- package/docs/completion-reports.md +43 -20
- package/docs/constants.md +10 -3
- package/docs/constellation-bridge.md +134 -6
- package/docs/constellation-style-telemetry.md +4 -4
- package/docs/contracts/capability-protocol.v1.json +165 -0
- package/docs/cooldown-merge-semantics.md +12 -12
- package/docs/copilot-cli-schema.md +7 -7
- package/docs/cross-repo-plans.md +10 -10
- package/docs/dead-code-audit-retractions.md +5 -5
- package/docs/default-branch-ci.md +173 -0
- package/docs/deprecated.json +31 -31
- package/docs/design-inbox-entries-schema.md +3 -3
- package/docs/design-language.md +1051 -0
- package/docs/design-state-storage.md +11 -11
- package/docs/diagnostics-crash-reports.md +9 -9
- package/docs/diagnostics-memory.md +5 -5
- package/docs/documentation-audit-2026-07-09.md +7 -7
- package/docs/engine-restart.md +90 -5
- package/docs/harness-mode.md +1 -1
- package/docs/internal-install.md +338 -39
- package/docs/kb-dedup-duplicate-pair-investigation.md +5 -5
- package/docs/kb-pr3223-cascade-archiving.md +1 -1
- package/docs/kb-pr696-merge-conflict-docs.md +6 -6
- package/docs/kb-sweep.md +35 -35
- package/docs/keep-processes.md +1 -1
- package/docs/live-checkout-mode.md +30 -30
- package/docs/managed-spawn.md +18 -14
- package/docs/named-agents.md +7 -7
- package/docs/plan-lifecycle.md +69 -2
- package/docs/pr-author-identity.md +114 -0
- package/docs/pr-auto-fix-dispatch.md +19 -4
- package/docs/pr-comment-followup.md +6 -6
- package/docs/pr-review-fix-loop.md +59 -10
- package/docs/process-termination.md +40 -0
- package/docs/proposals/repo-pool-for-live-checkout.md +13 -13
- package/docs/qa-runbook-lifecycle.md +367 -17
- package/docs/qa-runbooks.md +3 -3
- package/docs/rfc-completion-json.md +18 -18
- package/docs/runtime-adapters.md +26 -21
- package/docs/security.md +6 -6
- package/docs/self-improvement.md +4 -4
- package/docs/shared-lifecycle-module-map.md +473 -472
- package/docs/skills.md +52 -3
- package/docs/slim-ux/concepts.md +121 -116
- package/docs/specs/agent-configurability.md +18 -18
- package/docs/specs/agent-rename.md +18 -18
- package/docs/team-memory.md +38 -21
- package/docs/timeouts-and-liveness.md +118 -10
- package/docs/tutorials/01-install-and-connect.md +1 -1
- package/docs/watches.md +40 -39
- package/docs/workspace-manifests.md +4 -4
- package/docs/worktree-lifecycle.md +293 -14
- package/engine/README.md +46 -0
- package/engine/{ado-comment.js → ado/comment.js} +8 -8
- package/engine/{ado-git-auth.js → ado/git-auth.js} +4 -4
- package/engine/{ado.js → ado/index.js} +417 -63
- package/engine/{ado-status.js → ado/status.js} +6 -8
- package/engine/{ado-token.js → ado/token.js} +1 -1
- package/engine/{acp-transport.js → agents/acp-transport.js} +62 -22
- package/engine/{agent-worker-pool.js → agents/agent-worker-pool.js} +17 -8
- package/engine/{cc-worker-pool.js → agents/cc-worker-pool.js} +16 -6
- package/engine/{claude-md-context.js → agents/claude-md-context.js} +5 -5
- package/engine/{harness-context.js → agents/harness-context.js} +5 -5
- package/engine/{harness.js → agents/harness.js} +3 -3
- package/engine/{llm.js → agents/llm.js} +18 -14
- package/engine/{model-discovery.js → agents/model-discovery.js} +2 -2
- package/engine/{playbook.js → agents/playbook.js} +155 -22
- package/engine/{pooled-agent-process.js → agents/pooled-agent-process.js} +14 -12
- package/engine/{preflight.js → agents/preflight.js} +29 -10
- package/engine/{spawn-agent.js → agents/spawn-agent.js} +25 -14
- package/engine/{spawn-phase-watchdog.js → agents/spawn-phase-watchdog.js} +16 -7
- package/engine/{steering.js → agents/steering.js} +5 -5
- package/engine/{tools-inventory.js → agents/tools-inventory.js} +2 -2
- package/engine/{agent-api-validation.js → api/agent-api-validation.js} +2 -2
- package/engine/{api-validation.js → api/api-validation.js} +1 -1
- package/engine/api/bridge.js +787 -0
- package/engine/{cc-api-validation.js → api/cc-api-validation.js} +1 -1
- package/engine/api/companion.js +560 -0
- package/engine/{content-api-validation.js → api/content-api-validation.js} +2 -2
- package/engine/{pr-issue-validation.js → api/pr-issue-validation.js} +33 -6
- package/engine/{settings-validation.js → api/settings-validation.js} +32 -4
- package/engine/api-contracts/agent-content.js +4 -4
- package/engine/api-contracts/capability-manifest.js +236 -0
- package/engine/api-contracts/capability-protocol.js +333 -0
- package/engine/api-contracts/cc-ops.js +1 -1
- package/engine/api-contracts/config-runtime.js +5 -0
- package/engine/api-contracts/core.js +28 -1
- package/engine/api-contracts/index.js +100 -0
- package/engine/api-contracts/orchestration.js +18 -5
- package/engine/api-contracts/pull-requests.js +37 -6
- package/engine/api-contracts/qa-process.js +29 -6
- package/engine/api-contracts/work-plan-prd.js +21 -1
- package/engine/cloud/contract.js +212 -0
- package/engine/cloud/index.js +159 -0
- package/engine/{execution-model.js → core/execution-model.js} +1 -1
- package/engine/{features.js → core/features.js} +4 -4
- package/engine/{operator-identity.js → core/operator-identity.js} +1 -1
- package/engine/{queries.js → core/queries.js} +201 -36
- package/engine/{safe-expr.js → core/safe-expr.js} +1 -1
- package/engine/{shared.js → core/shared.js} +1637 -175
- package/engine/{stdio-timestamps.js → core/stdio-timestamps.js} +1 -1
- package/engine/{untrusted-fence.js → core/untrusted-fence.js} +3 -3
- package/engine/db/index.js +11 -2
- package/engine/db/migrations/002-dispatches.js +3 -3
- package/engine/db/migrations/003-work-items.js +1 -1
- package/engine/db/migrations/004-pull-requests.js +1 -1
- package/engine/db/migrations/006-metrics.js +1 -1
- package/engine/db/migrations/007-watches.js +2 -2
- package/engine/db/migrations/008-small-state.js +1 -1
- package/engine/db/migrations/009-qa.js +1 -1
- package/engine/db/migrations/010-pr-links.js +1 -1
- package/engine/db/migrations/011-remaining-state.js +1 -1
- package/engine/db/migrations/012-steering-deliveries.js +2 -2
- package/engine/db/migrations/013-backfill-broken-note-links.js +1 -1
- package/engine/db/migrations/014-pr-fix-target-prefs.js +2 -2
- package/engine/db/migrations/015-plans-prds.js +0 -0
- package/engine/db/migrations/018-sql-only-cutover.js +2 -2
- package/engine/db/migrations/021-archived-work-items.js +1 -1
- package/engine/db/migrations/022-global-cc-session.js +1 -1
- package/engine/db/migrations/023-engine-state.js +1 -1
- package/engine/db/migrations/025-malformed-work-item-phantoms.js +1 -1
- package/engine/db/migrations/027-review-learning-lifecycle.js +1 -1
- package/engine/db/migrations/029-repair-reused-versions.js +20 -0
- package/engine/db/migrations/031-pr-author-identity.js +137 -0
- package/engine/{consolidation.js → memory/consolidation.js} +6 -6
- package/engine/{kb-sweep-runner.js → memory/kb-sweep-runner.js} +2 -2
- package/engine/{kb-sweep.js → memory/kb-sweep.js} +9 -7
- package/engine/{memory-retrieval.js → memory/memory-retrieval.js} +46 -4
- package/engine/{memory-store.js → memory/memory-store.js} +3 -3
- package/engine/{promotion.js → memory/promotion.js} +3 -3
- package/engine/{review-learning-backfill.js → memory/review-learning-backfill.js} +6 -6
- package/engine/{review-learning.js → memory/review-learning.js} +10 -5
- package/engine/{diagnostics-memory.js → observability/diagnostics-memory.js} +1 -1
- package/engine/{logs-store.js → observability/logs-store.js} +5 -5
- package/engine/{metrics-store.js → observability/metrics-store.js} +4 -4
- package/engine/{check-status.js → operations/check-status.js} +3 -3
- package/engine/{cli.js → operations/cli.js} +271 -113
- package/engine/{distribution.js → operations/distribution.js} +5 -6
- package/engine/{cleanup.js → orchestration/cleanup.js} +72 -45
- package/engine/{cooldown.js → orchestration/cooldown.js} +5 -5
- package/engine/{dispatch-events.js → orchestration/dispatch-events.js} +2 -2
- package/engine/{dispatch.js → orchestration/dispatch.js} +129 -36
- package/engine/orchestration/failed-scheduled-cleanup.js +274 -0
- package/engine/{lifecycle.js → orchestration/lifecycle.js} +198 -90
- package/engine/{meeting.js → orchestration/meeting.js} +6 -16
- package/engine/{pipeline.js → orchestration/pipeline.js} +12 -12
- package/engine/{pre-dispatch-eval.js → orchestration/pre-dispatch-eval.js} +10 -9
- package/engine/{routing.js → orchestration/routing.js} +3 -3
- package/engine/{schedule-bootstrap.js → orchestration/schedule-bootstrap.js} +4 -4
- package/engine/{scheduler.js → orchestration/scheduler.js} +38 -8
- package/engine/{timeout.js → orchestration/timeout.js} +158 -109
- package/engine/{db-events.js → persistence/db-events.js} +2 -2
- package/engine/{dispatch-store.js → persistence/dispatch-store.js} +7 -7
- package/engine/{inbox-store.js → persistence/inbox-store.js} +2 -2
- package/engine/{note-link-backfill.js → persistence/note-link-backfill.js} +4 -4
- package/engine/{pr-fix-target-store.js → persistence/pr-fix-target-store.js} +8 -8
- package/engine/{pull-requests-store.js → persistence/pull-requests-store.js} +21 -7
- package/engine/{small-state-store.js → persistence/small-state-store.js} +31 -31
- package/engine/persistence/state-operations.js +350 -0
- package/engine/{steering-store.js → persistence/steering-store.js} +6 -6
- package/engine/{issues.js → planning/issues.js} +2 -2
- package/engine/{plan-prd-validation.js → planning/plan-prd-validation.js} +8 -2
- package/engine/planning/prd-result-sidecar.js +190 -0
- package/engine/{prd-store.js → planning/prd-store.js} +17 -17
- package/engine/{project-discovery.js → planning/project-discovery.js} +5 -5
- package/engine/{projects.js → planning/projects.js} +10 -10
- package/engine/{resolve-area.js → planning/resolve-area.js} +1 -1
- package/engine/{work-item-validation.js → planning/work-item-validation.js} +39 -3
- package/engine/{work-items-store.js → planning/work-items-store.js} +29 -21
- package/engine/{keep-process-sweep.js → processes/keep-process-sweep.js} +57 -17
- package/engine/{managed-spawn-launcher.js → processes/managed-spawn-launcher.js} +3 -3
- package/engine/{managed-spawn.js → processes/managed-spawn.js} +97 -46
- package/engine/{process-utils.js → processes/process-utils.js} +599 -55
- package/engine/{abandoned-pr-reconciliation.js → providers/abandoned-pr-reconciliation.js} +17 -7
- package/engine/{comment-classifier.js → providers/comment-classifier.js} +85 -17
- package/engine/{comment-format.js → providers/comment-format.js} +5 -5
- package/engine/{gh-comment.js → providers/gh-comment.js} +15 -15
- package/engine/{gh-token.js → providers/gh-token.js} +4 -4
- package/engine/{github.js → providers/github.js} +131 -54
- package/engine/{pr-action.js → providers/pr-action.js} +13 -12
- package/engine/{pr-clone-keep.js → providers/pr-clone-keep.js} +7 -7
- package/engine/{pr-devbox.js → providers/pr-devbox.js} +6 -6
- package/engine/{pr-fix-target.js → providers/pr-fix-target.js} +13 -13
- package/engine/{pr-remote-patch.js → providers/pr-remote-patch.js} +4 -4
- package/engine/{pr-resolve.js → providers/pr-resolve.js} +7 -7
- package/engine/{pr-temp-clone.js → providers/pr-temp-clone.js} +5 -5
- package/engine/{pr-track.js → providers/pr-track.js} +11 -13
- package/engine/{shared-branch-pr-reconcile.js → providers/shared-branch-pr-reconcile.js} +4 -4
- package/engine/qa/auto-prd-qa.js +313 -0
- package/engine/{qa-from-prd.js → qa/from-prd.js} +42 -12
- package/engine/qa/prd-session.js +240 -0
- package/engine/{qa-process-validation.js → qa/process-validation.js} +14 -9
- package/engine/{qa-runbooks.js → qa/runbooks.js} +1 -1
- package/engine/{qa-runs.js → qa/runs.js} +286 -15
- package/engine/{qa-sessions.js → qa/sessions.js} +595 -49
- package/engine/qa/visual-journey.js +654 -0
- package/engine/{qa-runners.js → qa-runners/index.js} +7 -7
- package/engine/qa-runners/maestro.js +3 -3
- package/engine/qa-runners/playwright.js +2 -2
- package/engine/{restart-health.js → recovery/restart-health.js} +48 -4
- package/engine/recovery/stop-stack.js +607 -0
- package/engine/{supervisor.js → recovery/supervisor.js} +105 -175
- package/engine/{watchdog.js → recovery/watchdog.js} +136 -13
- package/engine/runtimes/claude.js +14 -12
- package/engine/runtimes/codex.js +8 -6
- package/engine/runtimes/copilot.js +17 -16
- package/engine/{watch-actions.js → watches/actions.js} +13 -13
- package/engine/{watches.js → watches/index.js} +43 -32
- package/engine/{watches-store.js → watches/store.js} +4 -4
- package/engine/{create-pr-worktree.js → worktrees/create-pr.js} +1 -1
- package/engine/{worktree-gc.js → worktrees/gc.js} +70 -22
- package/engine/worktrees/inventory.js +671 -0
- package/engine/{live-checkout.js → worktrees/live-checkout.js} +4 -4
- package/engine/{worktree-pool.js → worktrees/pool.js} +2 -2
- package/engine/{worktree-preflight.js → worktrees/preflight.js} +1 -0
- package/engine/worktrees/quarantine-refs.js +173 -0
- package/engine.js +1137 -208
- package/minions.js +147 -77
- package/package.json +10 -6
- package/playbooks/_pr-description-audit.md +110 -78
- package/playbooks/build-fix-complex.md +2 -0
- package/playbooks/fix.md +16 -12
- package/playbooks/implement-shared.md +2 -0
- package/playbooks/implement.md +19 -20
- package/playbooks/plan-to-prd.md +18 -3
- package/playbooks/qa-session-draft.md +136 -1
- package/playbooks/qa-session-execute.md +80 -2
- package/playbooks/qa-session-setup.md +17 -1
- package/playbooks/qa-validate.md +1 -1
- package/playbooks/setup.md +2 -0
- package/playbooks/shared-rules.md +25 -32
- package/playbooks/templates/followup-dispatch.md +4 -3
- package/playbooks/verify.md +1 -1
- package/prompts/cc-system.md +19 -27
- package/watch-plugins/README.md +92 -0
- package/watch-plugins/ado-author-prs.js +336 -0
- package/watch-plugins/gh-author-prs.js +375 -0
- package/watch-plugins/http.js +474 -0
- package/watch-plugins/teams-channel.js +869 -0
- package/docs/dev-composite-workflow.md +0 -101
- package/docs/pr-screenshots/pr-886/after-single-header.png +0 -0
- package/docs/pr-screenshots/pr-886/before-duplicate-header.png +0 -0
- package/docs/pr-screenshots/pr-895/01-cancellation-reason-detail.png +0 -0
- package/docs/pr-screenshots/pr-899/worker-pool-worktrees-AFTER.png +0 -0
- package/docs/pr-screenshots/pr-899/worker-pool-worktrees-BEFORE.png +0 -0
- package/docs/pr-screenshots/pr-901/projects-tab-default.png +0 -0
- package/docs/pr-screenshots/pr-901/projects-tab-fmf-selected.png +0 -0
- package/docs/pr-screenshots/pr-916/model-picker-AFTER-crop.png +0 -0
- package/docs/pr-screenshots/pr-916/model-picker-AFTER.png +0 -0
- package/docs/pr-screenshots/pr-916/model-picker-BEFORE-crop.png +0 -0
- package/docs/pr-screenshots/pr-916/model-picker-BEFORE.png +0 -0
- package/docs/pr-screenshots/pr-916/model-picker-dropdown-AFTER.png +0 -0
- package/docs/pr-screenshots/pr-979/auto-fix-pane-AFTER.png +0 -0
- package/docs/pr-screenshots/pr-979/auto-fix-pane-BEFORE.png +0 -0
- package/docs/pr-screenshots/pr-985/pr-column-em-dash-AFTER.png +0 -0
- package/docs/pr-screenshots/pr-985/pr-column-em-dash-BEFORE.png +0 -0
- package/docs/visual-evidence-ci.md +0 -103
- package/engine/bridge.js +0 -379
- package/engine/quarantine-refs.js +0 -103
- package/engine/state-operations.js +0 -178
- /package/engine/{steering-constraints.js → agents/steering-constraints.js} +0 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const { CC_API_LIMITS, PRESENCE_VISIBILITIES } = require('../cc-api-validation');
|
|
3
|
+
const { CC_API_LIMITS, PRESENCE_VISIBILITIES } = require('../api/cc-api-validation');
|
|
4
|
+
const { CAPABILITY_SCOPE, CAPABILITY_CONFIRM } = require('./capability-protocol');
|
|
4
5
|
|
|
5
6
|
module.exports = {
|
|
6
7
|
owner: 'core',
|
|
@@ -10,6 +11,8 @@ module.exports = {
|
|
|
10
11
|
'GET /api/status',
|
|
11
12
|
'GET /api/engine-log',
|
|
12
13
|
'GET /api/constellation/v1/snapshot',
|
|
14
|
+
'GET /api/capabilities/protocol',
|
|
15
|
+
'GET /api/capabilities',
|
|
13
16
|
'GET /state/<path>',
|
|
14
17
|
'GET /api/metrics',
|
|
15
18
|
'GET /api/tools',
|
|
@@ -49,6 +52,22 @@ module.exports = {
|
|
|
49
52
|
'GET /api/status': {
|
|
50
53
|
audit: 'audited',
|
|
51
54
|
noInputReason: 'Returns the current dashboard snapshot; accepts no caller input.',
|
|
55
|
+
// Phase-0 allowlist. Since issue #2949 this snapshot is only the small
|
|
56
|
+
// live envelope — engine health/tick, the ADO+GitHub throttle state,
|
|
57
|
+
// project git status, autoMode, and the version banner. The heavy work,
|
|
58
|
+
// plan, and PR slices moved to their own endpoints, each of which is a
|
|
59
|
+
// separately-declared capability. So the summary projection grants ONE
|
|
60
|
+
// scope, not the union of every read scope: granting the plan and PR
|
|
61
|
+
// read scopes here would be over-declaration for a payload that carries
|
|
62
|
+
// neither.
|
|
63
|
+
capability: {
|
|
64
|
+
id: 'status.summary',
|
|
65
|
+
title: 'Read the engine status summary',
|
|
66
|
+
description: 'Read the live engine health, throttle, and project summary snapshot.',
|
|
67
|
+
scopes: [CAPABILITY_SCOPE.WORK_READ],
|
|
68
|
+
destructive: false,
|
|
69
|
+
confirm: CAPABILITY_CONFIRM.NONE,
|
|
70
|
+
},
|
|
52
71
|
},
|
|
53
72
|
'GET /api/engine-log': {
|
|
54
73
|
audit: 'audited',
|
|
@@ -62,6 +81,14 @@ module.exports = {
|
|
|
62
81
|
audit: 'audited',
|
|
63
82
|
noInputReason: 'Returns the versioned bridge snapshot; accepts no caller input.',
|
|
64
83
|
},
|
|
84
|
+
'GET /api/capabilities/protocol': {
|
|
85
|
+
audit: 'audited',
|
|
86
|
+
noInputReason: 'Returns the static capability-protocol artifact for live negotiation; accepts no caller input.',
|
|
87
|
+
},
|
|
88
|
+
'GET /api/capabilities': {
|
|
89
|
+
audit: 'audited',
|
|
90
|
+
noInputReason: 'Returns the derived capability manifest; accepts no caller input.',
|
|
91
|
+
},
|
|
65
92
|
'GET /state/<path>': {
|
|
66
93
|
audit: 'audited',
|
|
67
94
|
path: {
|
|
@@ -19,6 +19,100 @@ const OWNERS = Object.freeze(OWNER_NAMES);
|
|
|
19
19
|
const LOCATION_POLICIES = new Set(['none', 'optional', 'required', 'pending-audit']);
|
|
20
20
|
const AUDIT_STATUSES = new Set(['audited', 'pending-audit']);
|
|
21
21
|
|
|
22
|
+
// The capability vocabulary is owned by the cross-repo protocol contract, never
|
|
23
|
+
// restated here. This module only decides WHICH routes opt in and enforces that
|
|
24
|
+
// each declaration is complete against that contract.
|
|
25
|
+
const {
|
|
26
|
+
CAPABILITY_SCOPES,
|
|
27
|
+
CAPABILITY_CONFIRM_MODES,
|
|
28
|
+
CAPABILITY_CONFIRM,
|
|
29
|
+
CAPABILITY_ENTRY_FIELDS,
|
|
30
|
+
} = require('./capability-protocol');
|
|
31
|
+
|
|
32
|
+
const CAPABILITY_SCOPE_SET = new Set(CAPABILITY_SCOPES);
|
|
33
|
+
const CAPABILITY_CONFIRM_SET = new Set(CAPABILITY_CONFIRM_MODES);
|
|
34
|
+
// Derived from the route key or the audited request contract — a hand-typed
|
|
35
|
+
// copy could drift from the route it claims to describe, so these entry fields
|
|
36
|
+
// may not be declared in an override.
|
|
37
|
+
const CAPABILITY_DERIVED_FIELDS = new Set(['method', 'path', 'params']);
|
|
38
|
+
const CAPABILITY_DECLARED_FIELDS = CAPABILITY_ENTRY_FIELDS
|
|
39
|
+
.filter(entryField => !CAPABILITY_DERIVED_FIELDS.has(entryField.name));
|
|
40
|
+
// Producer-only key: not published in the manifest, it exists so a
|
|
41
|
+
// non-destructive claim on a state-changing route has to be argued rather than
|
|
42
|
+
// assumed (see normalizeCapability).
|
|
43
|
+
const CAPABILITY_PRODUCER_FIELDS = new Set(['nonDestructiveRationale']);
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Validate the opt-in `capability` block on a route override.
|
|
47
|
+
*
|
|
48
|
+
* Fail-closed in both directions: a route without a block is simply not a
|
|
49
|
+
* capability (it never reaches the manifest), and a route WITH a block must
|
|
50
|
+
* declare every required protocol field or the catalog build throws. In
|
|
51
|
+
* particular `destructive` and `confirm` are always explicit — the remote
|
|
52
|
+
* authorization posture of a capability must never be inferred from a route's
|
|
53
|
+
* method, path, or free-text description, because rewording a description
|
|
54
|
+
* would then silently reclassify it for a remote caller.
|
|
55
|
+
*/
|
|
56
|
+
function normalizeCapability(value, key, stateChanging) {
|
|
57
|
+
if (value === undefined) return null;
|
|
58
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
59
|
+
throw new Error(`${key} capability must be an object`);
|
|
60
|
+
}
|
|
61
|
+
for (const declaredField of Object.keys(value)) {
|
|
62
|
+
if (CAPABILITY_DERIVED_FIELDS.has(declaredField)) {
|
|
63
|
+
throw new Error(`${key} capability must not declare "${declaredField}" — it is derived from the route contract`);
|
|
64
|
+
}
|
|
65
|
+
if (!CAPABILITY_DECLARED_FIELDS.some(entryField => entryField.name === declaredField)
|
|
66
|
+
&& !CAPABILITY_PRODUCER_FIELDS.has(declaredField)) {
|
|
67
|
+
throw new Error(`${key} capability declares unknown field "${declaredField}"`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
for (const entryField of CAPABILITY_DECLARED_FIELDS) {
|
|
71
|
+
if (entryField.required && value[entryField.name] === undefined) {
|
|
72
|
+
throw new Error(`${key} capability must explicitly declare "${entryField.name}"`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
for (const textField of ['id', 'title', 'description']) {
|
|
76
|
+
if (typeof value[textField] !== 'string' || !value[textField].trim()) {
|
|
77
|
+
throw new Error(`${key} capability ${textField} must be a non-empty string`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
if (!Array.isArray(value.scopes) || value.scopes.length === 0) {
|
|
81
|
+
throw new Error(`${key} capability must declare at least one scope`);
|
|
82
|
+
}
|
|
83
|
+
for (const scope of value.scopes) {
|
|
84
|
+
if (!CAPABILITY_SCOPE_SET.has(scope)) {
|
|
85
|
+
throw new Error(`${key} capability declares unknown scope "${scope}"; the scope vocabulary is owned by engine/api-contracts/capability-protocol.js`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
if (typeof value.destructive !== 'boolean') {
|
|
89
|
+
throw new Error(`${key} capability destructive must be an explicit boolean`);
|
|
90
|
+
}
|
|
91
|
+
if (!CAPABILITY_CONFIRM_SET.has(value.confirm)) {
|
|
92
|
+
throw new Error(`${key} capability declares unknown confirm mode "${value.confirm}"; the confirmation enum is owned by engine/api-contracts/capability-protocol.js`);
|
|
93
|
+
}
|
|
94
|
+
if (value.destructive && value.confirm !== CAPABILITY_CONFIRM.PER_CALL) {
|
|
95
|
+
throw new Error(`${key} capability is destructive and must require per-call confirmation`);
|
|
96
|
+
}
|
|
97
|
+
// Corroborating assertion only: `stateChanging` is method-derived, so it can
|
|
98
|
+
// contradict a `destructive: false` claim but never supply one.
|
|
99
|
+
const rationale = value.nonDestructiveRationale;
|
|
100
|
+
const needsRationale = stateChanging && value.destructive === false;
|
|
101
|
+
if (needsRationale && (typeof rationale !== 'string' || !rationale.trim())) {
|
|
102
|
+
throw new Error(`${key} capability claims destructive: false on a state-changing route and must justify it with nonDestructiveRationale`);
|
|
103
|
+
}
|
|
104
|
+
if (!needsRationale && rationale !== undefined) {
|
|
105
|
+
throw new Error(`${key} capability declares nonDestructiveRationale where it does not apply`);
|
|
106
|
+
}
|
|
107
|
+
const normalized = {};
|
|
108
|
+
for (const entryField of CAPABILITY_DECLARED_FIELDS) {
|
|
109
|
+
const declared = value[entryField.name];
|
|
110
|
+
if (declared === undefined) continue;
|
|
111
|
+
normalized[entryField.name] = typeof declared === 'string' ? declared.trim() : cloneSerializable(declared);
|
|
112
|
+
}
|
|
113
|
+
return normalized;
|
|
114
|
+
}
|
|
115
|
+
|
|
22
116
|
function canonicalPathTemplate(value) {
|
|
23
117
|
const pathTemplate = String(value || '').trim();
|
|
24
118
|
if (!pathTemplate.startsWith('/')) {
|
|
@@ -212,6 +306,8 @@ function buildRouteContract(owner, key, bodyless, override = {}) {
|
|
|
212
306
|
request,
|
|
213
307
|
negativeTests,
|
|
214
308
|
};
|
|
309
|
+
const capability = normalizeCapability(override.capability, parsed.key, stateChanging);
|
|
310
|
+
if (capability) contract.capability = capability;
|
|
215
311
|
if (noInput) {
|
|
216
312
|
contract.noInputReason = override.noInputReason
|
|
217
313
|
|| (stateChanging
|
|
@@ -301,6 +397,10 @@ function routeContractMetadata(route) {
|
|
|
301
397
|
...(contract.noInputReason ? { noInputReason: contract.noInputReason } : {}),
|
|
302
398
|
request: contract.request,
|
|
303
399
|
negativeTests: contract.negativeTests,
|
|
400
|
+
// Whitelisted explicitly: this clone is the only metadata a consumer sees,
|
|
401
|
+
// so an un-listed key is silently dropped and the capability manifest would
|
|
402
|
+
// come back empty.
|
|
403
|
+
...(contract.capability ? { capability: contract.capability } : {}),
|
|
304
404
|
});
|
|
305
405
|
}
|
|
306
406
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const keepProcesses = require('../keep-process-sweep');
|
|
4
|
-
const managedSpawn = require('../managed-spawn');
|
|
3
|
+
const keepProcesses = require('../processes/keep-process-sweep');
|
|
4
|
+
const managedSpawn = require('../processes/managed-spawn');
|
|
5
5
|
const managedSpecNameMax = managedSpawn.validateManagedSpecName('x').maxLength;
|
|
6
|
-
const agentApiValidation = require('../agent-api-validation');
|
|
6
|
+
const agentApiValidation = require('../api/agent-api-validation');
|
|
7
7
|
const paging = require('./paging');
|
|
8
8
|
|
|
9
9
|
// The optional progressive-load paging window is the ONLY caller-supplied input
|
|
@@ -48,6 +48,7 @@ module.exports = {
|
|
|
48
48
|
'GET /api/pipelines',
|
|
49
49
|
'GET /api/keep-processes',
|
|
50
50
|
'GET /api/worktree-quarantine-refs',
|
|
51
|
+
'GET /api/worktree-inventory',
|
|
51
52
|
'POST /api/keep-processes/kill',
|
|
52
53
|
'GET /api/managed-processes',
|
|
53
54
|
'GET /api/managed-processes/by-name/<name>',
|
|
@@ -159,6 +160,16 @@ module.exports = {
|
|
|
159
160
|
},
|
|
160
161
|
],
|
|
161
162
|
},
|
|
163
|
+
'GET /api/worktree-inventory': {
|
|
164
|
+
audit: 'audited',
|
|
165
|
+
noInputReason: 'Read-only classification of configured project worktree roots and git registrations; accepts no caller input.',
|
|
166
|
+
constraints: [
|
|
167
|
+
{
|
|
168
|
+
kind: 'configured-project-membership',
|
|
169
|
+
source: 'config.projects[].localPath',
|
|
170
|
+
},
|
|
171
|
+
],
|
|
172
|
+
},
|
|
162
173
|
'GET /api/managed-processes': {
|
|
163
174
|
audit: 'audited',
|
|
164
175
|
query: {
|
|
@@ -347,6 +358,7 @@ module.exports = {
|
|
|
347
358
|
{ name: 'participants', type: 'array', required: false, allowedValuesSource: 'config.agents keys' },
|
|
348
359
|
{ name: 'priority', type: 'string', required: false, default: 'medium', allowedValuesSource: 'shared.WORK_ITEM_PRIORITY_VALUES' },
|
|
349
360
|
{ name: 'enabled', type: 'boolean', required: false, default: true },
|
|
361
|
+
{ name: 'executionSurface', type: 'string', required: false, default: 'repo', allowedValuesSource: 'shared.EXECUTION_SURFACE_VALUES' },
|
|
350
362
|
],
|
|
351
363
|
},
|
|
352
364
|
constraints: [
|
|
@@ -373,6 +385,7 @@ module.exports = {
|
|
|
373
385
|
{ name: 'participants', type: 'array', required: false, allowedValuesSource: 'config.agents keys' },
|
|
374
386
|
{ name: 'priority', type: 'string', required: false, allowedValuesSource: 'shared.WORK_ITEM_PRIORITY_VALUES' },
|
|
375
387
|
{ name: 'enabled', type: 'boolean', required: false },
|
|
388
|
+
{ name: 'executionSurface', type: 'string', required: false, allowedValuesSource: 'shared.EXECUTION_SURFACE_VALUES' },
|
|
376
389
|
],
|
|
377
390
|
},
|
|
378
391
|
constraints: [
|
|
@@ -562,7 +575,7 @@ module.exports = {
|
|
|
562
575
|
{ name: 'notify', type: 'string', required: false, default: 'inbox', allowedValues: ['inbox', 'none'] },
|
|
563
576
|
{ name: 'stopAfter', type: 'integer', required: false, default: 0, min: 0, max: Number.MAX_SAFE_INTEGER },
|
|
564
577
|
{ name: 'onNotMet', type: 'string|null', required: false, allowedValues: ['notify'] },
|
|
565
|
-
{ name: 'action', type: 'object|array|null', required: false, allowedValuesSource: 'watch
|
|
578
|
+
{ name: 'action', type: 'object|array|null', required: false, allowedValuesSource: 'watch action registry' },
|
|
566
579
|
{ name: 'requires', type: 'array|null', required: false, allowedValuesSource: 'watches target-type registry requirement conditions' },
|
|
567
580
|
],
|
|
568
581
|
},
|
|
@@ -591,7 +604,7 @@ module.exports = {
|
|
|
591
604
|
{ name: 'notify', type: 'string', required: false, allowedValues: ['inbox', 'none'] },
|
|
592
605
|
{ name: 'stopAfter', type: 'integer', required: false, min: 0, max: Number.MAX_SAFE_INTEGER },
|
|
593
606
|
{ name: 'onNotMet', type: 'string|null', required: false, allowedValues: ['notify'] },
|
|
594
|
-
{ name: 'action', type: 'object|array|null', required: false, allowedValuesSource: 'watch
|
|
607
|
+
{ name: 'action', type: 'object|array|null', required: false, allowedValuesSource: 'watch action registry' },
|
|
595
608
|
{ name: 'requires', type: 'array|null', required: false, allowedValuesSource: 'watches target-type registry requirement conditions' },
|
|
596
609
|
],
|
|
597
610
|
},
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const { PR_ACTIONS } = require('../pr-action');
|
|
4
|
-
const { EXECUTION_TARGET_IDS } = require('../pr-fix-target');
|
|
5
|
-
const { PR_FIX_CAUSE } = require('../shared');
|
|
3
|
+
const { PR_ACTIONS } = require('../providers/pr-action');
|
|
4
|
+
const { EXECUTION_TARGET_IDS } = require('../providers/pr-fix-target');
|
|
5
|
+
const { PR_FIX_CAUSE } = require('../core/shared');
|
|
6
6
|
const paging = require('./paging');
|
|
7
|
+
const { CAPABILITY_SCOPE, CAPABILITY_CONFIRM } = require('./capability-protocol');
|
|
7
8
|
|
|
8
9
|
const PR_REFERENCE_FORMATS = Object.freeze([
|
|
9
10
|
'GitHub PR URL',
|
|
@@ -44,6 +45,7 @@ module.exports = {
|
|
|
44
45
|
'POST /api/pr-action/prepare-create-pr-worktree',
|
|
45
46
|
'POST /api/pr-action/cleanup-create-pr-worktree',
|
|
46
47
|
'POST /api/pull-requests/observe',
|
|
48
|
+
'POST /api/pull-requests/automation',
|
|
47
49
|
'POST /api/pull-requests/delete',
|
|
48
50
|
'POST /api/pull-requests/clear-paused-cause',
|
|
49
51
|
'POST /api/pull-requests/clear-build-fix-ineffective',
|
|
@@ -62,6 +64,14 @@ module.exports = {
|
|
|
62
64
|
...paging.listPagingClampConstraints(),
|
|
63
65
|
{ kind: 'ignores-unknown-query-params', location: 'query' },
|
|
64
66
|
],
|
|
67
|
+
capability: {
|
|
68
|
+
id: 'pr.list',
|
|
69
|
+
title: 'List tracked pull requests',
|
|
70
|
+
description: 'Read the tracked pull-request records with build, review, and merge state.',
|
|
71
|
+
scopes: [CAPABILITY_SCOPE.PR_READ],
|
|
72
|
+
destructive: false,
|
|
73
|
+
confirm: CAPABILITY_CONFIRM.NONE,
|
|
74
|
+
},
|
|
65
75
|
},
|
|
66
76
|
'GET /api/prs/<id>': {
|
|
67
77
|
audit: 'audited',
|
|
@@ -129,7 +139,7 @@ module.exports = {
|
|
|
129
139
|
type: 'string',
|
|
130
140
|
required: true,
|
|
131
141
|
allowedValues: PR_ACTIONS,
|
|
132
|
-
allowedValuesSource: 'engine/pr-action.PR_ACTIONS',
|
|
142
|
+
allowedValuesSource: 'engine/providers/pr-action.PR_ACTIONS',
|
|
133
143
|
},
|
|
134
144
|
{ name: 'execute', type: 'boolean', required: false, default: false },
|
|
135
145
|
{ name: 'draftOnly', type: 'boolean', required: false, default: false },
|
|
@@ -154,7 +164,7 @@ module.exports = {
|
|
|
154
164
|
type: 'string',
|
|
155
165
|
required: false,
|
|
156
166
|
allowedValues: EXECUTION_TARGET_IDS,
|
|
157
|
-
allowedValuesSource: 'engine/pr-fix-target.EXECUTION_TARGET_IDS',
|
|
167
|
+
allowedValuesSource: 'engine/providers/pr-fix-target.EXECUTION_TARGET_IDS',
|
|
158
168
|
},
|
|
159
169
|
{ name: 'remember', type: 'boolean', required: false, default: false },
|
|
160
170
|
{ name: 'reprompt', type: 'boolean', required: false, default: false },
|
|
@@ -249,6 +259,27 @@ module.exports = {
|
|
|
249
259
|
precedence: ['contextOnly', 'observe'],
|
|
250
260
|
}],
|
|
251
261
|
}),
|
|
262
|
+
'POST /api/pull-requests/automation': auditedBody([
|
|
263
|
+
{ name: 'host', type: 'string', required: true, allowedValues: ['github', 'ado'] },
|
|
264
|
+
{
|
|
265
|
+
name: 'slug',
|
|
266
|
+
type: 'string',
|
|
267
|
+
required: true,
|
|
268
|
+
shapeByHost: {
|
|
269
|
+
github: 'owner/repo',
|
|
270
|
+
ado: 'org/project/repo',
|
|
271
|
+
},
|
|
272
|
+
},
|
|
273
|
+
{
|
|
274
|
+
name: 'number',
|
|
275
|
+
type: 'integer',
|
|
276
|
+
required: true,
|
|
277
|
+
min: 1,
|
|
278
|
+
max: Number.MAX_SAFE_INTEGER,
|
|
279
|
+
},
|
|
280
|
+
{ name: 'automation', type: 'string', required: true, allowedValues: ['fix', 'review'] },
|
|
281
|
+
{ name: 'enabled', type: 'boolean', required: true },
|
|
282
|
+
], { seam: 'tracked PR automation' }),
|
|
252
283
|
'POST /api/pull-requests/delete': auditedBody([
|
|
253
284
|
{
|
|
254
285
|
name: 'id',
|
|
@@ -280,7 +311,7 @@ module.exports = {
|
|
|
280
311
|
type: 'string',
|
|
281
312
|
required: true,
|
|
282
313
|
allowedValues: Object.values(PR_FIX_CAUSE),
|
|
283
|
-
allowedValuesSource: 'engine/shared.PR_FIX_CAUSE',
|
|
314
|
+
allowedValuesSource: 'engine/core/shared.PR_FIX_CAUSE',
|
|
284
315
|
},
|
|
285
316
|
], { seam: 'tracked PR pause store' }),
|
|
286
317
|
'POST /api/pull-requests/clear-build-fix-ineffective': auditedBody([
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const qaFromPrd = require('../qa
|
|
4
|
-
const keepProcesses = require('../keep-process-sweep');
|
|
5
|
-
const qaRunbooks = require('../qa
|
|
6
|
-
const qaRuns = require('../qa
|
|
7
|
-
const qaSessions = require('../qa
|
|
8
|
-
const shared = require('../shared');
|
|
3
|
+
const qaFromPrd = require('../qa/from-prd');
|
|
4
|
+
const keepProcesses = require('../processes/keep-process-sweep');
|
|
5
|
+
const qaRunbooks = require('../qa/runbooks');
|
|
6
|
+
const qaRuns = require('../qa/runs');
|
|
7
|
+
const qaSessions = require('../qa/sessions');
|
|
8
|
+
const shared = require('../core/shared');
|
|
9
9
|
|
|
10
10
|
const sessionIdPath = {
|
|
11
11
|
policy: 'required',
|
|
@@ -65,6 +65,7 @@ module.exports = {
|
|
|
65
65
|
'POST /api/qa/sessions/<id>/dismiss',
|
|
66
66
|
'GET /api/qa/sessions/<id>/test',
|
|
67
67
|
'GET /api/qa/sessions/<id>',
|
|
68
|
+
'DELETE /api/qa/sessions/<id>',
|
|
68
69
|
'GET /api/qa/runners',
|
|
69
70
|
'POST /api/qa/runners/reload',
|
|
70
71
|
'POST /api/meetings',
|
|
@@ -80,6 +81,7 @@ module.exports = {
|
|
|
80
81
|
bodyless: [
|
|
81
82
|
'DELETE /api/qa/runs/<id>',
|
|
82
83
|
'DELETE /api/qa/runbooks/<id>',
|
|
84
|
+
'DELETE /api/qa/sessions/<id>',
|
|
83
85
|
'POST /api/qa/sessions/<id>/approve',
|
|
84
86
|
'POST /api/qa/runners/reload',
|
|
85
87
|
],
|
|
@@ -531,6 +533,27 @@ module.exports = {
|
|
|
531
533
|
{ strategy: 'missing-session-or-test-file', expectedStatus: 404 },
|
|
532
534
|
],
|
|
533
535
|
},
|
|
536
|
+
'DELETE /api/qa/sessions/<id>': {
|
|
537
|
+
audit: 'audited',
|
|
538
|
+
path: sessionIdPath,
|
|
539
|
+
constraints: [
|
|
540
|
+
{ kind: 'body-forbidden' },
|
|
541
|
+
{ kind: 'terminal-state-required', source: 'qa-sessions.TERMINAL_STATES' },
|
|
542
|
+
{
|
|
543
|
+
kind: 'owned-linked-resource-cleanup',
|
|
544
|
+
source: 'qa-sessions.deleteSession',
|
|
545
|
+
resources: ['qa-tests/<id>/', 'qa-runs record with runbookId qa-session-<id>'],
|
|
546
|
+
excludes: ['managed-spawn processes'],
|
|
547
|
+
},
|
|
548
|
+
],
|
|
549
|
+
negativeTests: [
|
|
550
|
+
{ strategy: 'non-empty-body-before-delete', expectedStatus: 400 },
|
|
551
|
+
{ strategy: 'unsafe-id', expectedStatus: 400 },
|
|
552
|
+
{ strategy: 'well-formed-missing-id', expectedStatus: 404 },
|
|
553
|
+
{ strategy: 'non-terminal-session', expectedStatus: 409 },
|
|
554
|
+
{ strategy: 'snapshot-session-run-and-test-directory-on-rejection', expectedStatus: '4xx' },
|
|
555
|
+
],
|
|
556
|
+
},
|
|
534
557
|
'POST /api/qa/sessions/<id>/approve': {
|
|
535
558
|
audit: 'audited',
|
|
536
559
|
path: sessionIdPath,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const paging = require('./paging');
|
|
4
|
+
const { CAPABILITY_SCOPE, CAPABILITY_CONFIRM } = require('./capability-protocol');
|
|
4
5
|
|
|
5
6
|
const AUDITED_NEGATIVE_TESTS = Object.freeze([{
|
|
6
7
|
strategy: 'isolated-http-invalid-input-state-snapshot',
|
|
@@ -77,6 +78,14 @@ module.exports = {
|
|
|
77
78
|
negativeTests: [
|
|
78
79
|
{ strategy: 'reject-unknown-query-param-blaming-the-rejected-name', expectedStatus: 400 },
|
|
79
80
|
],
|
|
81
|
+
capability: {
|
|
82
|
+
id: 'plan.list',
|
|
83
|
+
title: 'List plans',
|
|
84
|
+
description: 'Read the plan inventory with each plan\'s lifecycle status.',
|
|
85
|
+
scopes: [CAPABILITY_SCOPE.PLAN_READ],
|
|
86
|
+
destructive: false,
|
|
87
|
+
confirm: CAPABILITY_CONFIRM.NONE,
|
|
88
|
+
},
|
|
80
89
|
},
|
|
81
90
|
'POST /api/work-items': {
|
|
82
91
|
audit: 'audited',
|
|
@@ -279,6 +288,14 @@ module.exports = {
|
|
|
279
288
|
...paging.listPagingClampConstraints(),
|
|
280
289
|
{ kind: 'ignores-unknown-query-params', location: 'query' },
|
|
281
290
|
],
|
|
291
|
+
capability: {
|
|
292
|
+
id: 'work.list',
|
|
293
|
+
title: 'List work items',
|
|
294
|
+
description: 'Read the current work-item queue with its status, agent, and project.',
|
|
295
|
+
scopes: [CAPABILITY_SCOPE.WORK_READ],
|
|
296
|
+
destructive: false,
|
|
297
|
+
confirm: CAPABILITY_CONFIRM.NONE,
|
|
298
|
+
},
|
|
282
299
|
},
|
|
283
300
|
'GET /api/work-items/archive': {
|
|
284
301
|
// Archived-item listing: reads no query/body. Affirmed input-less.
|
|
@@ -412,7 +429,10 @@ module.exports = {
|
|
|
412
429
|
{ name: 'requestedBy', type: 'string', required: false },
|
|
413
430
|
],
|
|
414
431
|
},
|
|
415
|
-
constraints: [
|
|
432
|
+
constraints: [
|
|
433
|
+
'feedback must contain non-whitespace text and PRD status must allow revision',
|
|
434
|
+
'a live revision operation is rejected with 409 revision-in-progress and the existing work item id',
|
|
435
|
+
],
|
|
416
436
|
},
|
|
417
437
|
'POST /api/plans/discuss': {
|
|
418
438
|
audit: 'audited',
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* engine/cloud/contract.js — versioned contract for CLOUD DISPATCH PROVIDERS.
|
|
5
|
+
*
|
|
6
|
+
* A cloud provider hands a work item to a REMOTE coding agent (Agency, the
|
|
7
|
+
* GitHub Copilot coding agent, an ADO work-item assignment picked up by a bot)
|
|
8
|
+
* and lets Minions track that remote session until a pull request exists.
|
|
9
|
+
*
|
|
10
|
+
* ── Why this is NOT a runtime adapter ────────────────────────────────────
|
|
11
|
+
* `engine/runtimes/contract.js` describes a LOCAL CLI PROCESS: argv building,
|
|
12
|
+
* prompt-on-stdin, PID tracking, stdout stream parsing, session resume, cost
|
|
13
|
+
* accounting per turn. A cloud agent has none of those. You submit a task, get
|
|
14
|
+
* an opaque session handle, poll it, and eventually a PR appears in the repo.
|
|
15
|
+
* Modelling that as a runtime would force every adapter method to lie
|
|
16
|
+
* (`buildArgs` for a process that never spawns, `parseStreamChunk` for a stream
|
|
17
|
+
* that does not exist) and would drag cloud dispatch through the local
|
|
18
|
+
* spawn/timeout/worktree machinery it must specifically avoid.
|
|
19
|
+
*
|
|
20
|
+
* So cloud is its own small contract, reached through its own registry, and
|
|
21
|
+
* selected by the `cloud` EXECUTION SURFACE rather than by a runtime name.
|
|
22
|
+
* Orchestration must never import a concrete provider or branch on a provider
|
|
23
|
+
* id — resolve through `engine/cloud/index.js` and use `capabilities`.
|
|
24
|
+
*
|
|
25
|
+
* ── Contract (apiVersion 1) ──────────────────────────────────────────────
|
|
26
|
+
* name : string — registry key; must match the registration key.
|
|
27
|
+
* apiVersion : 1
|
|
28
|
+
* capabilities : {
|
|
29
|
+
* cancel?: boolean — cancelSession() is meaningful.
|
|
30
|
+
* steering?: boolean — mid-session follow-up messages.
|
|
31
|
+
* liveLog?: boolean — a human-viewable live session URL.
|
|
32
|
+
* }
|
|
33
|
+
*
|
|
34
|
+
* isConfigured({ config, project }) -> boolean
|
|
35
|
+
* Cheap, synchronous, NO network. Answers "could a dispatch to this
|
|
36
|
+
* provider possibly work right now for this project?" — credentials
|
|
37
|
+
* present, repo binding resolvable, not disabled in config. Used as a
|
|
38
|
+
* fail-closed pre-dispatch gate so a misconfigured provider surfaces as
|
|
39
|
+
* CLOUD_PROVIDER_UNAVAILABLE at dispatch time instead of a mystery
|
|
40
|
+
* session that never starts.
|
|
41
|
+
*
|
|
42
|
+
* createSession({ project, repo, baseBranch, prompt, workItem, workdir })
|
|
43
|
+
* -> Promise<{ sessionId, url?, providerRef? }>
|
|
44
|
+
* Submits the task. `prompt` is the SAME rendered playbook prompt a
|
|
45
|
+
* local agent would have received (minus local-only sections) — that
|
|
46
|
+
* rendered instruction block is the entire reason this surface exists.
|
|
47
|
+
* `sessionId` must be a stable, non-empty string usable by pollSession.
|
|
48
|
+
*
|
|
49
|
+
* pollSession(sessionId, { project }) -> Promise<CloudSessionStatus>
|
|
50
|
+
* Read-only. Must be safe to call every poll tick, and must never throw
|
|
51
|
+
* for an ordinary "still running" answer.
|
|
52
|
+
*
|
|
53
|
+
* cancelSession(sessionId, { reason, project }) -> Promise<void>
|
|
54
|
+
* Best-effort. Only required when capabilities.cancel is true.
|
|
55
|
+
*
|
|
56
|
+
* CloudSessionStatus:
|
|
57
|
+
* { state, prUrl?, summary?, error?, raw? }
|
|
58
|
+
* state ∈ CLOUD_SESSION_STATE. `prUrl` is what the engine adopts into its PR
|
|
59
|
+
* record so the existing ADO/GitHub pollers and review/fix loops take over
|
|
60
|
+
* unchanged; a SUCCEEDED status with no `prUrl` is a contract violation and
|
|
61
|
+
* is rejected by `normalizeSessionStatus`.
|
|
62
|
+
*/
|
|
63
|
+
|
|
64
|
+
const CLOUD_PROVIDER_API_VERSION = 1;
|
|
65
|
+
|
|
66
|
+
const REQUIRED_METHODS = Object.freeze([
|
|
67
|
+
'isConfigured',
|
|
68
|
+
'createSession',
|
|
69
|
+
'pollSession',
|
|
70
|
+
]);
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Remote session lifecycle. Deliberately smaller than any one provider's
|
|
74
|
+
* native state machine — providers map their vocabulary onto these five so
|
|
75
|
+
* orchestration never learns provider-specific state names.
|
|
76
|
+
*/
|
|
77
|
+
const CLOUD_SESSION_STATE = Object.freeze({
|
|
78
|
+
QUEUED: 'queued',
|
|
79
|
+
RUNNING: 'running',
|
|
80
|
+
SUCCEEDED: 'succeeded',
|
|
81
|
+
FAILED: 'failed',
|
|
82
|
+
CANCELLED: 'cancelled',
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
const CLOUD_SESSION_STATE_VALUES = Object.freeze(Object.values(CLOUD_SESSION_STATE));
|
|
86
|
+
|
|
87
|
+
const TERMINAL_CLOUD_SESSION_STATES = Object.freeze(new Set([
|
|
88
|
+
CLOUD_SESSION_STATE.SUCCEEDED,
|
|
89
|
+
CLOUD_SESSION_STATE.FAILED,
|
|
90
|
+
CLOUD_SESSION_STATE.CANCELLED,
|
|
91
|
+
]));
|
|
92
|
+
|
|
93
|
+
function isTerminalCloudSessionState(state) {
|
|
94
|
+
return TERMINAL_CLOUD_SESSION_STATES.has(String(state || '').trim().toLowerCase());
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Validate + normalize a provider at registration time. Strict by design: a
|
|
99
|
+
* provider that registers successfully is one orchestration can call without
|
|
100
|
+
* defensive checks at every call site.
|
|
101
|
+
*/
|
|
102
|
+
function normalizeCloudProvider(name, provider) {
|
|
103
|
+
if (!name || typeof name !== 'string') {
|
|
104
|
+
throw new Error('registerCloudProvider: name must be a non-empty string');
|
|
105
|
+
}
|
|
106
|
+
if (!provider || typeof provider !== 'object' || Array.isArray(provider)) {
|
|
107
|
+
throw new Error('registerCloudProvider: provider must be an object');
|
|
108
|
+
}
|
|
109
|
+
if (provider.name != null && provider.name !== name) {
|
|
110
|
+
throw new Error(
|
|
111
|
+
`registerCloudProvider: provider name "${provider.name}" does not match registry key "${name}"`
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
if (provider.apiVersion == null) {
|
|
115
|
+
throw new Error(`Cloud provider "${name}" must declare apiVersion ${CLOUD_PROVIDER_API_VERSION}`);
|
|
116
|
+
}
|
|
117
|
+
if (provider.apiVersion !== CLOUD_PROVIDER_API_VERSION) {
|
|
118
|
+
throw new Error(
|
|
119
|
+
`Cloud provider "${name}" API version ${provider.apiVersion} is unsupported; `
|
|
120
|
+
+ `expected ${CLOUD_PROVIDER_API_VERSION}`
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (provider.capabilities == null) {
|
|
125
|
+
provider.capabilities = {};
|
|
126
|
+
} else if (typeof provider.capabilities !== 'object' || Array.isArray(provider.capabilities)) {
|
|
127
|
+
throw new Error(`Cloud provider "${name}" member capabilities must be an object`);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
for (const method of REQUIRED_METHODS) {
|
|
131
|
+
if (typeof provider[method] !== 'function') {
|
|
132
|
+
throw new Error(`Cloud provider "${name}" is missing required method ${method}()`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
if (provider.cancelSession != null && typeof provider.cancelSession !== 'function') {
|
|
136
|
+
throw new Error(`Cloud provider "${name}" member cancelSession must be a function`);
|
|
137
|
+
}
|
|
138
|
+
// A provider that advertises cancellation must actually implement it —
|
|
139
|
+
// otherwise the session-timeout path would silently leave remote work
|
|
140
|
+
// running with no way to stop it.
|
|
141
|
+
if (provider.capabilities.cancel === true && typeof provider.cancelSession !== 'function') {
|
|
142
|
+
throw new Error(`Cloud provider "${name}" declares capabilities.cancel but does not implement cancelSession()`);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
provider.name = name;
|
|
146
|
+
return provider;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Normalize what a provider returned from pollSession into the shape
|
|
151
|
+
* orchestration is allowed to trust. Throws on a contract violation rather
|
|
152
|
+
* than guessing, so a broken provider fails loudly at the poll site instead of
|
|
153
|
+
* silently completing a work item with no PR.
|
|
154
|
+
*/
|
|
155
|
+
function normalizeSessionStatus(providerName, status) {
|
|
156
|
+
if (!status || typeof status !== 'object' || Array.isArray(status)) {
|
|
157
|
+
throw new Error(`Cloud provider "${providerName}" pollSession() must return an object`);
|
|
158
|
+
}
|
|
159
|
+
const state = String(status.state || '').trim().toLowerCase();
|
|
160
|
+
if (!CLOUD_SESSION_STATE_VALUES.includes(state)) {
|
|
161
|
+
throw new Error(
|
|
162
|
+
`Cloud provider "${providerName}" pollSession() returned unknown state `
|
|
163
|
+
+ `"${status.state}" (expected one of ${CLOUD_SESSION_STATE_VALUES.join(', ')})`
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
const prUrl = typeof status.prUrl === 'string' && status.prUrl.trim() ? status.prUrl.trim() : null;
|
|
167
|
+
if (state === CLOUD_SESSION_STATE.SUCCEEDED && !prUrl) {
|
|
168
|
+
throw new Error(
|
|
169
|
+
`Cloud provider "${providerName}" reported state "succeeded" without a prUrl — `
|
|
170
|
+
+ `a cloud dispatch succeeds only when it produced a pull request Minions can adopt`
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
return {
|
|
174
|
+
state,
|
|
175
|
+
prUrl,
|
|
176
|
+
summary: typeof status.summary === 'string' ? status.summary : '',
|
|
177
|
+
error: typeof status.error === 'string' ? status.error : '',
|
|
178
|
+
raw: status.raw !== undefined ? status.raw : null,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Normalize a createSession() result. `sessionId` is the handle every later
|
|
184
|
+
* poll/cancel depends on, so an empty one is a hard error at submit time
|
|
185
|
+
* rather than an untrackable dispatch discovered minutes later.
|
|
186
|
+
*/
|
|
187
|
+
function normalizeCreatedSession(providerName, created) {
|
|
188
|
+
if (!created || typeof created !== 'object' || Array.isArray(created)) {
|
|
189
|
+
throw new Error(`Cloud provider "${providerName}" createSession() must return an object`);
|
|
190
|
+
}
|
|
191
|
+
const sessionId = typeof created.sessionId === 'string' ? created.sessionId.trim() : '';
|
|
192
|
+
if (!sessionId) {
|
|
193
|
+
throw new Error(`Cloud provider "${providerName}" createSession() must return a non-empty sessionId`);
|
|
194
|
+
}
|
|
195
|
+
return {
|
|
196
|
+
sessionId,
|
|
197
|
+
url: typeof created.url === 'string' && created.url.trim() ? created.url.trim() : null,
|
|
198
|
+
providerRef: created.providerRef !== undefined ? created.providerRef : null,
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
module.exports = {
|
|
203
|
+
CLOUD_PROVIDER_API_VERSION,
|
|
204
|
+
CLOUD_SESSION_STATE,
|
|
205
|
+
CLOUD_SESSION_STATE_VALUES,
|
|
206
|
+
TERMINAL_CLOUD_SESSION_STATES,
|
|
207
|
+
REQUIRED_METHODS,
|
|
208
|
+
isTerminalCloudSessionState,
|
|
209
|
+
normalizeCloudProvider,
|
|
210
|
+
normalizeSessionStatus,
|
|
211
|
+
normalizeCreatedSession,
|
|
212
|
+
};
|