@moretea-labs/forge 1.8.1 → 1.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +5 -2
- package/CHANGELOG.md +20 -0
- package/README.en.md +24 -3
- package/README.md +10 -6
- package/README.zh-CN.md +5 -3
- package/THIRD_PARTY_NOTICES.md +1 -2
- package/adapters/chatgpt/browser-delivery-runtime.ts +5 -3
- package/adapters/chatgpt/provider-delivery.ts +1 -1
- package/adapters/chatgpt/work-conversation-binding-store.ts +20 -1
- package/adapters/computer/chatgpt-extension-target.ts +295 -50
- package/adapters/computer/chrome-extension/background.js +227 -71
- package/adapters/computer/chrome-extension/content.js +181 -157
- package/adapters/computer/chrome-extension/manifest.json +3 -1
- package/adapters/computer/desktop-operator-provider.ts +18 -6
- package/adapters/computer/interaction-target-authority.ts +39 -9
- package/adapters/mcp/access-mode.ts +18 -39
- package/adapters/mcp/auth.ts +2 -6
- package/adapters/mcp/instructions.ts +2 -2
- package/adapters/mcp/multi-repository.ts +7 -28
- package/adapters/mcp/runtime-gateway/context-adapter.ts +79 -40
- package/adapters/mcp/runtime-gateway/context-pack-sidecar.ts +7 -15
- package/adapters/mcp/runtime-gateway/execution-tools.ts +7 -13
- package/adapters/mcp/runtime-gateway/plugin-adapter.ts +16 -3
- package/adapters/mcp/runtime-gateway/process-tools.ts +3 -21
- package/adapters/mcp/runtime-gateway/router.ts +3 -5
- package/adapters/mcp/runtime-gateway/routing-policy.ts +17 -10
- package/adapters/mcp/runtime-gateway/runtime-observation-adapter.ts +1 -1
- package/adapters/mcp/runtime-gateway/runtime-tool-definitions.ts +14 -9
- package/adapters/mcp/runtime-gateway/runtime-tools.ts +1 -5
- package/adapters/mcp/runtime-gateway/scheduler-adapter.ts +36 -1
- package/adapters/mcp/runtime-gateway/status-inbox-adapter.ts +58 -4
- package/adapters/mcp/runtime-gateway/typescript-navigation-sidecar.ts +7 -3
- package/adapters/mcp/runtime-gateway/work-adapter.ts +16 -16
- package/adapters/mcp/runtime-gateway/work-input-wire-migration.ts +0 -17
- package/adapters/mcp/runtime-gateway/work-semantic-operations.ts +69 -12
- package/adapters/mcp/runtime-gateway/workflow-supervisor-adapter.ts +26 -2
- package/adapters/mcp/server.ts +24 -3
- package/adapters/mcp/setup.ts +4 -10
- package/adapters/mcp/tool-mapping/access-tools.ts +0 -6
- package/adapters/mcp/tool-mapping/legacy-tool-service.ts +672 -2935
- package/adapters/mcp/tool-mapping/post-finalize-work-attribution.ts +1 -1
- package/adapters/mcp/tool-mapping/repository-tools.ts +52 -86
- package/adapters/mcp/toolset-names.ts +11 -42
- package/adapters/mcp/toolset.ts +16 -90
- package/{src/cli → adapters}/mcp/transports/AGENTS.md +5 -5
- package/{src/cli → adapters}/mcp/transports/CLAUDE.md +5 -5
- package/adapters/mcp/transports/http-observation.ts +18 -6
- package/adapters/mcp/transports/http-post-lifecycle.ts +19 -0
- package/adapters/mcp/transports/http.ts +54 -14
- package/adapters/mcp/types.ts +0 -6
- package/assets/hooks/lib/session-state.sh +0 -38
- package/assets/hooks/post-bash.sh +1 -1
- package/assets/hooks/post-tool-observer.sh +0 -5
- package/assets/hooks/prompt-guard.sh +0 -55
- package/assets/partials/04-project-structure.partial.md +1 -1
- package/assets/partials/07-footer.partial.md +1 -1
- package/assets/partials-agents/02-operating-mode.partial.md +1 -1
- package/assets/partials-agents/08-deep-docs.partial.md +1 -1
- package/assets/reference-configs/agentic-development-flow.md +1 -1
- package/assets/reference-configs/external-tooling.md +0 -76
- package/assets/reference-configs/release-deploy.md +1 -1
- package/assets/templates/helpers/architecture-queue.sh +1 -1
- package/assets/templates/helpers/check-agent-tooling.sh +1 -327
- package/assets/templates/helpers/check-task-workflow.sh +1 -1
- package/assets/templates/helpers/ensure-task-workflow.sh +0 -14
- package/assets/templates/helpers/migrate-project-template.sh +2 -2
- package/assets/workflow-contract.v1.json +0 -2
- package/docs/README.md +5 -2
- package/docs/architecture/CURRENT.md +114 -30
- package/docs/architecture/decisions/20260904-ios-device-migration-baseline.md +42 -0
- package/docs/architecture/decisions/20260918-stable-candidate-release-lifecycle.md +15 -2
- package/docs/architecture/decisions/20260924-thin-semantic-working-context.md +4 -2
- package/docs/architecture/decisions/20261001-supervisor-turn-authority.md +109 -4
- package/docs/architecture/decisions/20261009-exact-code-context-discovery.md +19 -0
- package/docs/architecture/decisions/20261009-generated-xcode-cache-retention.md +37 -0
- package/docs/architecture/decisions/20261011-autonomous-continuation-convergence.md +597 -0
- package/docs/architecture/decisions/20261017-edit-session-finalization-reconciliation.md +17 -0
- package/docs/architecture/decisions/20261019-retire-host-skills.md +16 -0
- package/docs/architecture/diagrams/agentic-dev-plugin-architecture.html +1 -1
- package/docs/architecture/domains/public-surface.md +3 -5
- package/docs/architecture/domains/verification.md +5 -5
- package/docs/architecture/modules/controller-runtime/transport-lifecycle.md +2 -2
- package/docs/architecture/requests/2026-09-04-computer-plugin-workflow-taxonomy.md +4 -4
- package/docs/architecture/reviews/20261015-code-search-runtime-acceptance.md +75 -0
- package/docs/forge-plugin-management.md +14 -2
- package/docs/operations/controller-browser-plugin.md +23 -0
- package/docs/operations/features.md +6 -7
- package/docs/operations/features.zh-CN.md +1 -1
- package/docs/operations/github-repository.md +4 -4
- package/docs/operations/github-semantic-release-projection.md +20 -0
- package/docs/operations/homebrew.md +2 -2
- package/docs/operations/mcp-tool-exposure.md +13 -54
- package/docs/operations/runtime-performance-diagnostics.md +8 -0
- package/docs/operations/standalone-disaster-recovery.md +1 -1
- package/docs/tutorials/01-install-and-start.zh-CN.md +1 -1
- package/docs/tutorials/03-first-repository-task.md +2 -0
- package/docs/wiki/Computer-and-Plugins.md +78 -0
- package/docs/wiki/Computer-and-Plugins.zh-CN.md +88 -0
- package/docs/wiki/Home.md +16 -2
- package/docs/wiki/Implementation.md +24 -12
- package/docs/wiki/Installation.md +28 -32
- package/docs/wiki/Integrations.md +4 -0
- package/docs/wiki/Quick-Start.md +35 -22
- package/docs/wiki/Releases-and-Upgrades.md +15 -25
- package/docs/wiki/_Sidebar.md +5 -1
- package/package.json +1 -12
- package/packages/kernel/controller/infrastructure/controller-session-store.ts +63 -38
- package/packages/kernel/identity/infrastructure/activity-projection-store.ts +10 -0
- package/packages/kernel/scheduler/application/eligibility.ts +4 -1
- package/packages/kernel/scheduler/infrastructure/schedule-store.ts +13 -10
- package/packages/kernel/work/application/work-service.ts +1 -0
- package/packages/kernel/work/domain/repository-completion-receipt.ts +1 -1
- package/packages/kernel/work/domain/state-machine.ts +9 -2
- package/packages/kernel/work/infrastructure/work-contract-store.ts +28 -1
- package/packages/plugin-runtime/browser/provider-registry.ts +12 -36
- package/packages/plugin-runtime/computer/conversation-target.ts +17 -7
- package/scripts/assemble-template.ts +11 -72
- package/scripts/check-mcp-compatibility.ts +55 -74
- package/scripts/check-package-identity.mjs +0 -5
- package/scripts/check-runtime-architecture.mjs +305 -28
- package/scripts/init-project.sh +0 -46
- package/scripts/install-ios-hid-toolchain.sh +60 -0
- package/scripts/install-standalone-recovery.ts +1 -1
- package/scripts/lib/project-init-lib.sh +3 -3
- package/scripts/migrate-windows-wsl-controller-home.ts +2 -0
- package/scripts/open-source-audit-allowlist.txt +0 -1
- package/scripts/run-governed-gate.ts +2 -0
- package/scripts/smoke-ios-physical-device.ts +38 -10
- package/scripts/smoke-mcp-http-runtime.ts +7 -8
- package/scripts/smoke-mcp-tool-surface.ts +27 -41
- package/scripts/smoke-workflow-supervisor-chrome-adapter.ts +6 -3
- package/scripts/sync-generated-authority.mjs +0 -1
- package/src/cli/agent-jobs/executor-health.ts +1 -1
- package/src/cli/agent-jobs/integration.ts +2 -2
- package/src/cli/chatgpt-browser/file-policy.ts +2 -2
- package/src/cli/commands/doctor.ts +1 -143
- package/src/cli/commands/global-runtime.ts +3 -196
- package/src/cli/commands/init-hook.ts +6 -23
- package/src/cli/commands/init.ts +3 -252
- package/src/cli/commands/mcp.ts +0 -5
- package/src/cli/commands/prompt-guard-decision.ts +0 -2
- package/src/cli/commands/recovery.ts +2 -14
- package/src/cli/commands/repository.ts +17 -1
- package/src/cli/commands/setup-execution.ts +41 -0
- package/src/cli/controller/completion-orchestrator.ts +1 -1
- package/src/cli/controller/composite-operations.ts +1 -1
- package/src/cli/controller/context/instruction-context.ts +1 -1
- package/src/cli/controller/context/known-paths.ts +2 -2
- package/src/cli/controller/context/query-planning.ts +11 -4
- package/src/cli/controller/context/rule-resolution.ts +2 -2
- package/src/cli/controller/context/source-materializer.ts +2 -2
- package/src/cli/controller/context/structural-provider.ts +84 -0
- package/src/cli/controller/context/structural.ts +6 -6
- package/src/cli/controller/context/types.ts +8 -9
- package/src/cli/controller/context-pack.ts +97 -76
- package/src/cli/controller/operational-plan.ts +1 -1
- package/src/cli/controller/systemd-user.ts +4 -10
- package/src/cli/editing/edit-session.ts +17 -5
- package/src/cli/github/github.ts +4 -4
- package/src/cli/github/semantic-projection.ts +174 -0
- package/src/cli/hook/prompt-intents.ts +0 -9
- package/src/cli/index.ts +1 -43
- package/src/cli/local-bridge/connector-freshness.ts +1 -12
- package/src/cli/local-bridge/console-automations.ts +1 -1
- package/src/cli/local-bridge/console-view-models.ts +1 -1
- package/src/cli/local-bridge/facade-api.ts +0 -2
- package/src/cli/repositories/command-classifier.ts +0 -9
- package/src/cli/repositories/command-scope.ts +2 -2
- package/src/cli/repositories/locks.ts +20 -0
- package/src/cli/repositories/registry.ts +2 -1
- package/src/cli/repositories/safe-patch.ts +2 -12
- package/src/cli/repositories/structured-git.ts +39 -3
- package/src/cli/repository/inspector.ts +185 -343
- package/src/effects/process-runner.ts +17 -1
- package/src/runtime/context/generic-lsp-provider.ts +1 -1
- package/src/runtime/context/semantic-navigation-contract.ts +7 -0
- package/src/runtime/context/semantic-navigation.ts +1 -1
- package/src/runtime/control-plane/cleanup-artifact-retention.ts +4 -4
- package/src/runtime/control-plane/execution/work-delivery-service.ts +150 -31
- package/src/runtime/control-plane/execution/work-handle-authority.ts +12 -4
- package/src/runtime/control-plane/execution/work-handle-store.ts +53 -1
- package/src/runtime/control-plane/execution/work-operation-service.ts +20 -10
- package/src/runtime/control-plane/execution/work-preparation-service.ts +108 -4
- package/src/runtime/control-plane/execution/work-process-evidence.ts +2 -2
- package/src/runtime/control-plane/execution/work-terminal-cleanup.ts +28 -13
- package/src/runtime/control-plane/execution/work-validation-reconciler.ts +5 -1
- package/src/runtime/control-plane/execution/work-verification-snapshot.ts +1 -1
- package/src/runtime/control-plane/facade/capability-registry.ts +2 -0
- package/src/runtime/control-plane/facade/work-continuation.ts +1 -1
- package/src/runtime/control-plane/generated-cache-retention.ts +114 -63
- package/src/runtime/control-plane/global-scheduler/maintenance.ts +20 -8
- package/src/runtime/control-plane/global-scheduler/reconciliation.ts +1 -11
- package/src/runtime/control-plane/governance/external-effects.ts +1 -1
- package/src/runtime/control-plane/launcher/chatgpt-work-continuation.ts +1 -0
- package/src/runtime/control-plane/runtime-cleanup.ts +12 -93
- package/src/runtime/evidence/sensitive-output.ts +22 -2
- package/src/runtime/execution/jobs/child-reference.ts +11 -0
- package/src/runtime/execution/jobs/receipt-recovery.ts +1 -11
- package/src/runtime/execution/process-runtime/command-facade.ts +3 -3
- package/src/runtime/execution/process-runtime/runtime.ts +20 -16
- package/src/runtime/execution/thin-harness/execution-router.ts +16 -16
- package/src/runtime/execution/thin-harness/fast-executor.ts +2 -2
- package/src/runtime/execution/thin-harness/search-worker.ts +2 -2
- package/src/runtime/execution/workers/executor.ts +1 -2
- package/src/runtime/maintenance/cleanup.ts +41 -5
- package/src/runtime/plugins/action-application.ts +12 -1
- package/src/runtime/plugins/app-store-connect-adapter.ts +252 -4
- package/src/runtime/plugins/browser-adapter.ts +381 -313
- package/src/runtime/plugins/browser-macos-bridge.ts +114 -112
- package/src/runtime/plugins/browser-manifest-surface.ts +2 -1
- package/src/runtime/plugins/browser-registration.ts +2 -0
- package/src/runtime/plugins/browser-runtime.ts +26 -35
- package/src/runtime/plugins/computer-chatgpt-browser-adapter-installer.ts +11 -2
- package/src/runtime/plugins/computer-registration.ts +76 -9
- package/src/runtime/plugins/github-adapter.ts +30 -3
- package/src/runtime/plugins/gmail-adapter.ts +166 -1
- package/src/runtime/plugins/google-shared.ts +10 -5
- package/src/runtime/plugins/ios/remote-xpc-hid.ts +70 -30
- package/src/runtime/plugins/ios-adapter.ts +45 -40
- package/src/runtime/plugins/ios-agent-device.ts +54 -4
- package/src/runtime/plugins/ios-device-ownership.ts +103 -0
- package/src/runtime/plugins/ios-physical-device.ts +497 -79
- package/src/runtime/plugins/local-system-adapter.ts +1 -99
- package/src/runtime/plugins/store.ts +80 -7
- package/src/runtime/plugins/types.ts +4 -0
- package/src/runtime/projections/git-status-sampler.ts +2 -19
- package/src/runtime/recovery/actions.ts +1 -1
- package/src/runtime/recovery/lifecycle-inventory.ts +1 -11
- package/src/runtime/recovery/probe.ts +2 -2
- package/src/runtime/release/release-coordinator.ts +20 -4
- package/src/runtime/root/controller-progression-composition.ts +67 -5
- package/src/runtime/root/package-connector-service.ts +123 -11
- package/src/runtime/root/package-runtime-release.ts +6 -18
- package/src/runtime/root/package-runtime-service.ts +7 -13
- package/src/runtime/root/release-execution-canary.ts +18 -5
- package/src/runtime/root/release-manifest.ts +0 -34
- package/src/runtime/root/release-materialize.ts +0 -78
- package/src/runtime/root/runtime.ts +1 -2
- package/src/runtime/root/scheduled-controller-composition.ts +2 -1
- package/src/runtime/root/service.ts +90 -11
- package/src/runtime/root/types.ts +0 -6
- package/src/runtime/root/workflow-supervisor-composition.ts +207 -19
- package/src/runtime/root/workflow-supervisor-runtime.ts +365 -10
- package/src/runtime/safe-tooling/google-credential-store.ts +1 -11
- package/src/runtime/safe-tooling/ios-development.ts +22 -8
- package/src/runtime/safe-tooling/job-summary.ts +1 -1
- package/src/runtime/safe-tooling/workspace-auth.ts +4 -1
- package/src/runtime/shared/atomic-text-file.ts +21 -0
- package/src/runtime/shared/json-files.ts +8 -3
- package/src/runtime/standalone-recovery/core.ts +189 -174
- package/src/runtime/standalone-recovery/entry.ts +62 -30
- package/src/runtime/standalone-recovery/operation-lock.ts +11 -1
- package/src/runtime/workflow/schedules/chatgpt-automation-migration.ts +1 -0
- package/src/runtime/workflow/schedules/engine.ts +36 -14
- package/src/runtime/workflow/schedules/pending-activation.ts +11 -4
- package/src/runtime/workflow/schedules/work-continuation.ts +51 -6
- package/supervisor/client.ts +31 -14
- package/supervisor/control-plane.ts +210 -115
- package/supervisor/forge-validators.ts +88 -21
- package/supervisor/native-browser-adapter.ts +393 -28
- package/supervisor/native-messaging/host.ts +21 -2
- package/supervisor/protocol.ts +74 -26
- package/supervisor/server.ts +16 -8
- package/supervisor/store.ts +476 -57
- package/supervisor/types.ts +19 -13
- package/SKILL.md +0 -427
- package/adapters/computer/chatgpt-target-router.ts +0 -103
- package/adapters/mcp/runtime-gateway/automation-receipt-adapter.ts +0 -87
- package/adapters/mcp/runtime-gateway/work-validation-reconciler.ts +0 -1
- package/adapters/mcp/toolset-selection.ts +0 -41
- package/agents/openai.yaml +0 -4
- package/assets/skill-hooks.json +0 -41
- package/assets/skill-version.json +0 -189
- package/assets/skills/claude-review/SKILL.md +0 -231
- package/assets/skills/codex-review/SKILL.md +0 -103
- package/assets/templates/helpers/check-skill-version.ts +0 -193
- package/scripts/check-skill-version.ts +0 -5
- package/scripts/ensure-codegraph.sh +0 -16
- package/scripts/run-skill-hook.ts +0 -244
- package/scripts/sync-codex-installed-copies.sh +0 -182
- package/src/cli/commands/tools.ts +0 -167
- package/src/cli/mcp/access-mode.ts +0 -2
- package/src/cli/mcp/access-tools.ts +0 -2
- package/src/cli/mcp/audit.ts +0 -2
- package/src/cli/mcp/auth.ts +0 -2
- package/src/cli/mcp/instructions.ts +0 -2
- package/src/cli/mcp/legacy-context.ts +0 -2
- package/src/cli/mcp/legacy-tool-service.ts +0 -2
- package/src/cli/mcp/multi-repository.ts +0 -2
- package/src/cli/mcp/oauth.ts +0 -2
- package/src/cli/mcp/openai-secure-tunnel.ts +0 -2
- package/src/cli/mcp/paths.ts +0 -2
- package/src/cli/mcp/policy.ts +0 -2
- package/src/cli/mcp/post-finalize-work-attribution.ts +0 -2
- package/src/cli/mcp/proxy-env.ts +0 -2
- package/src/cli/mcp/readiness-projection.ts +0 -2
- package/src/cli/mcp/redaction.ts +0 -2
- package/src/cli/mcp/repo.ts +0 -2
- package/src/cli/mcp/repository-tools.ts +0 -2
- package/src/cli/mcp/server.ts +0 -2
- package/src/cli/mcp/setup.ts +0 -2
- package/src/cli/mcp/tool-capability-cache.ts +0 -2
- package/src/cli/mcp/tool-contract.ts +0 -2
- package/src/cli/mcp/tools.ts +0 -2
- package/src/cli/mcp/toolset-names.ts +0 -2
- package/src/cli/mcp/toolset-selection.ts +0 -2
- package/src/cli/mcp/toolset.ts +0 -2
- package/src/cli/mcp/transports/http.ts +0 -2
- package/src/cli/mcp/transports/session-registry.ts +0 -2
- package/src/cli/mcp/transports/stdio.ts +0 -2
- package/src/cli/mcp/types.ts +0 -2
- package/src/cli/mcp/umbrella-tools.ts +0 -2
- package/src/cli/tools/codegraph.ts +0 -695
- package/src/core/adoption/helper-wrapper-plan.ts +0 -159
- package/src/runtime/context/codegraph-cache-boundary.ts +0 -255
- package/src/runtime/context/codegraph-read-provider.ts +0 -611
- package/src/runtime/context/codegraph-sidecar.cjs +0 -248
- package/src/runtime/control-plane/codegraph-cache-retention.ts +0 -164
- package/src/runtime/gateway/mcp/execution-tools.ts +0 -2
- package/src/runtime/gateway/mcp/legacy-ios-tool-adapter.ts +0 -2
- package/src/runtime/gateway/mcp/persisted-check-process.ts +0 -2
- package/src/runtime/gateway/mcp/process-tools.ts +0 -2
- package/src/runtime/gateway/mcp/router.ts +0 -2
- package/src/runtime/gateway/mcp/runtime-tool-definitions.ts +0 -2
- package/src/runtime/gateway/mcp/runtime-tools.ts +0 -2
- package/src/runtime/gateway/mcp/work-validation-reconciler.ts +0 -2
- package/src/runtime/plugins/browser-provider-registry.ts +0 -2
- package/src/runtime/plugins/computer-chatgpt-macos-target.ts +0 -891
|
@@ -1,162 +1,35 @@
|
|
|
1
1
|
import type { CallToolResult, McpToolDefinition } from '../../../packages/protocols/mcp/tool-contract';
|
|
2
2
|
export type { CallToolResult, McpToolDefinition } from '../../../packages/protocols/mcp/tool-contract';
|
|
3
3
|
import { createHash } from "crypto";
|
|
4
|
-
import {
|
|
5
|
-
existsSync,
|
|
6
|
-
mkdirSync,
|
|
7
|
-
readdirSync,
|
|
8
|
-
readFileSync,
|
|
9
|
-
statSync,
|
|
10
|
-
writeFileSync,
|
|
11
|
-
appendFileSync,
|
|
12
|
-
} from "fs";
|
|
4
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync, appendFileSync } from "fs";
|
|
13
5
|
import { basename, dirname, join } from "path";
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
createEditSavepoint,
|
|
19
|
-
EditSessionPatchError,
|
|
20
|
-
finalizeEditSession,
|
|
21
|
-
getEditSession,
|
|
22
|
-
getEditSessionDiff,
|
|
23
|
-
listEditSessions,
|
|
24
|
-
PREFERRED_EDIT_PATCH_BATCH_OPERATIONS,
|
|
25
|
-
rollbackEditSession,
|
|
26
|
-
verifyEditSession,
|
|
27
|
-
} from "../../../src/cli/editing/edit-session";
|
|
28
|
-
import {
|
|
29
|
-
EDIT_OPERATION_INPUT_SCHEMA,
|
|
30
|
-
normalizeEditOperations,
|
|
31
|
-
} from "../../../src/cli/editing/edit-operation-contract";
|
|
32
|
-
import {
|
|
33
|
-
cancelAgentJob,
|
|
34
|
-
getAgentJob,
|
|
35
|
-
getAgentJobEvents,
|
|
36
|
-
getAgentJobLog,
|
|
37
|
-
listAgentJobs,
|
|
38
|
-
} from "../../../src/cli/agent-jobs/job-manager";
|
|
39
|
-
import {
|
|
40
|
-
classifyGitHubCopilotPreflight,
|
|
41
|
-
classifyLocalExecutorHealth,
|
|
42
|
-
executorHealthCode,
|
|
43
|
-
isExecutorHealthError,
|
|
44
|
-
type ExecutorHealth,
|
|
45
|
-
} from "../../../src/cli/agent-jobs/executor-health";
|
|
46
|
-
import { integrateAgentJob, taskRunDiff } from "../../../src/cli/agent-jobs/integration";
|
|
47
|
-
import {
|
|
48
|
-
appendTask,
|
|
49
|
-
archiveIssue,
|
|
50
|
-
createIssue,
|
|
51
|
-
getIssue,
|
|
52
|
-
getIssueEffectiveView,
|
|
53
|
-
getIssueReadView,
|
|
54
|
-
inspectIssueReadiness,
|
|
55
|
-
inspectTaskReadiness,
|
|
56
|
-
acceptVerifiedTask,
|
|
57
|
-
listIssueEffectiveViews,
|
|
58
|
-
listIssues,
|
|
59
|
-
projectIssueEffectiveView,
|
|
60
|
-
planIssue,
|
|
61
|
-
projectBoard,
|
|
62
|
-
recordTaskVerification,
|
|
63
|
-
restoreIssue,
|
|
64
|
-
setTaskDependencies,
|
|
65
|
-
splitTask,
|
|
66
|
-
supersedeTask,
|
|
67
|
-
updateIssue,
|
|
68
|
-
updateTask,
|
|
69
|
-
} from "../../../src/cli/controller/issue-store";
|
|
70
|
-
import { applyMigratedIssueDecision } from '../../../src/cli/controller/migrated-issue-decision';
|
|
71
|
-
import {
|
|
72
|
-
listControllerChecks,
|
|
73
|
-
} from "../../../src/cli/controller/check-runner";
|
|
74
|
-
import { normalizeCheckIds } from '../../../src/runtime/control-plane/facade/check-normalization';
|
|
6
|
+
import { beginEditSession, applyEditOperations, createEditSavepoint, EditSessionPatchError, finalizeEditSession, getEditSession, getEditSessionDiff, listEditSessions, PREFERRED_EDIT_PATCH_BATCH_OPERATIONS, rollbackEditSession } from "../../../src/cli/editing/edit-session";
|
|
7
|
+
import { EDIT_OPERATION_INPUT_SCHEMA, normalizeEditOperations } from "../../../src/cli/editing/edit-operation-contract";
|
|
8
|
+
import { listAgentJobs } from "../../../src/cli/agent-jobs/job-manager";
|
|
9
|
+
import { listControllerChecks } from "../../../src/cli/controller/check-runner";
|
|
75
10
|
import { buildExecutionDiagnostics, buildRequirementBoard } from '../../../src/runtime/control-plane/facade/requirement-board';
|
|
76
11
|
import { getWorkContract } from '../../../packages/kernel/work/api';
|
|
77
12
|
import { admitDirectEditWorkContract } from '../../../src/runtime/control-plane/facade/repository-work-admission';
|
|
78
13
|
import { listCapabilityDescriptors, summarizeCapabilityGroups } from '../../../src/runtime/control-plane/facade/capability-registry';
|
|
79
|
-
import {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
parseWorklogCategory,
|
|
83
|
-
} from "../../../src/cli/controller/worklog";
|
|
84
|
-
import { inspectProjectGovernance, reconcileProjectGovernance } from "../../../src/cli/controller/governance";
|
|
85
|
-
import { taskExecutionPolicy } from "../../../src/cli/controller/execution-policy";
|
|
86
|
-
import { finishEditSession, finishTaskRun } from "../../../src/cli/controller/completion-orchestrator";
|
|
87
|
-
import { buildControllerTaskLedgerProjection } from "../../../src/cli/controller/task-ledger";
|
|
88
|
-
import { loadControllerProjectState, saveControllerProjectState } from "../../../src/cli/controller/project-state";
|
|
89
|
-
import {
|
|
90
|
-
FORGE_MCP_SCHEMA_VERSION,
|
|
91
|
-
FORGE_TOOL_SURFACE,
|
|
92
|
-
FORGE_VERSION,
|
|
93
|
-
forgeToolSurfaceFingerprint,
|
|
94
|
-
MIN_AGENT_TIMEOUT_MS,
|
|
95
|
-
formatDurationMs,
|
|
96
|
-
normalizeAgentTimeoutMs,
|
|
97
|
-
} from "../../../src/cli/controller/runtime-config";
|
|
98
|
-
import { DEFAULT_CONTROLLER_TOOL_NAMES, PREFERRED_FACADE_TOOL_NAMES } from "../toolset-names";
|
|
14
|
+
import { exportControllerWorklog, listControllerWorklogEvents, parseWorklogCategory } from "../../../src/cli/controller/worklog";
|
|
15
|
+
import { FORGE_MCP_SCHEMA_VERSION, FORGE_TOOL_SURFACE, FORGE_VERSION, forgeToolSurfaceFingerprint, MIN_AGENT_TIMEOUT_MS, formatDurationMs, normalizeAgentTimeoutMs } from "../../../src/cli/controller/runtime-config";
|
|
16
|
+
import { PREFERRED_FACADE_TOOL_NAMES } from "../toolset-names";
|
|
99
17
|
import { buildLocalConnectorStatusForRepo } from "../../../src/cli/local-bridge/connector-freshness";
|
|
100
|
-
import
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
IssueStatus,
|
|
105
|
-
TaskDraft,
|
|
106
|
-
TaskRisk,
|
|
107
|
-
TaskCommandEvidence,
|
|
108
|
-
TaskStatus,
|
|
109
|
-
TaskVerification,
|
|
110
|
-
} from "../../../src/cli/controller/types";
|
|
111
|
-
import {
|
|
112
|
-
gitDiff,
|
|
113
|
-
gitSnapshot,
|
|
114
|
-
searchRepository,
|
|
115
|
-
} from "../../../src/cli/repository/inspector";
|
|
116
|
-
import {
|
|
117
|
-
closeGitHubIssue,
|
|
118
|
-
getGitHubStatus,
|
|
119
|
-
publishIssueToGitHub,
|
|
120
|
-
refreshGitHubIssue,
|
|
121
|
-
} from "../../../src/cli/github/github";
|
|
122
|
-
import {
|
|
123
|
-
getGitHubPluginStatus,
|
|
124
|
-
saveGitHubPluginConfig,
|
|
125
|
-
} from "../../../src/cli/github/plugin";
|
|
126
|
-
import {
|
|
127
|
-
getLocalBridgeJobEvents,
|
|
128
|
-
getLocalBridgeJobSnapshot,
|
|
129
|
-
listLocalBridgeJobs,
|
|
130
|
-
readLocalBridgeJobOutput,
|
|
131
|
-
reconcileLocalBridgeJobs,
|
|
132
|
-
} from "../../../src/cli/local-bridge/job-store";
|
|
18
|
+
import { gitDiff, gitSnapshot, searchRepository } from "../../../src/cli/repository/inspector";
|
|
19
|
+
import { getGitHubStatus } from "../../../src/cli/github/github";
|
|
20
|
+
import { getGitHubPluginStatus, saveGitHubPluginConfig } from "../../../src/cli/github/plugin";
|
|
21
|
+
import { getLocalBridgeJobEvents, getLocalBridgeJobSnapshot, listLocalBridgeJobs, readLocalBridgeJobOutput, reconcileLocalBridgeJobs } from "../../../src/cli/local-bridge/job-store";
|
|
133
22
|
import { runHelper } from "../../../src/cli/runtime/helper-runner";
|
|
134
|
-
import {
|
|
135
|
-
|
|
136
|
-
openSession,
|
|
137
|
-
readSession,
|
|
138
|
-
runBrowserConsult,
|
|
139
|
-
runBrowserFollowup,
|
|
140
|
-
} from "../../../src/cli/chatgpt-browser/engine";
|
|
141
|
-
import type {
|
|
142
|
-
BrowserProviderName,
|
|
143
|
-
NativeBrowserChannel,
|
|
144
|
-
ThinkingLevel,
|
|
145
|
-
} from "../../../src/cli/chatgpt-browser/types";
|
|
23
|
+
import { listSessions, openSession, readSession, runBrowserConsult, runBrowserFollowup } from "../../../src/cli/chatgpt-browser/engine";
|
|
24
|
+
import type { BrowserProviderName, NativeBrowserChannel, ThinkingLevel } from "../../../src/cli/chatgpt-browser/types";
|
|
146
25
|
import { hashMcpInput, tryWriteMcpAuditEntry } from "../audit";
|
|
147
|
-
import { loadMcpRuntimeState } from "../auth";
|
|
148
26
|
import { resolveLocalBridgeSurface, summarizeRecentJobs } from "../../../src/runtime/shared/local-bridge-surface";
|
|
149
27
|
import { resolveRepoPreferredControllerHome } from "../../../src/cli/repositories/controller-home";
|
|
150
28
|
import { resolveRepositoryCheckStorage } from '../../../src/runtime/execution/process-runtime/check-storage';
|
|
151
29
|
import { resolveRepositorySelection } from '../../../src/cli/repositories/registry';
|
|
152
30
|
import { ensureRepositoryRuntimeStorageBinding } from '../../../src/cli/repositories/runtime-storage';
|
|
153
|
-
import {
|
|
154
|
-
import { runPersistedCheckViaProcessRuntime } from '../runtime-gateway/persisted-check-process';
|
|
155
|
-
import { getProcessRecord } from '../../../src/runtime/execution/process-runtime/store';
|
|
156
|
-
import { processCheckCompletionReceipt } from '../../../src/runtime/execution/process-runtime/check-receipt';
|
|
157
|
-
import { normalizeMcpRelativePath, resolveMcpPath } from "../paths";
|
|
31
|
+
import { resolveMcpPath } from "../paths";
|
|
158
32
|
import { currentGitBranch, isForgeAdopted } from "../repo";
|
|
159
|
-
import { repositoryToolNames } from "./repository-tools";
|
|
160
33
|
import { redactMcpText } from "../redaction";
|
|
161
34
|
import type { McpPolicy } from "../types";
|
|
162
35
|
|
|
@@ -178,6 +51,8 @@ export interface McpToolContext {
|
|
|
178
51
|
/** Bound repository identity for session cache keys. */
|
|
179
52
|
repoId?: string;
|
|
180
53
|
checkoutId?: string;
|
|
54
|
+
/** Canonical tools/list projection supplied by the MCP registry; never authored by the client. */
|
|
55
|
+
publishedToolNames?: readonly string[];
|
|
181
56
|
/** Request-scoped MCP cancellation; never persisted or reused across calls. */
|
|
182
57
|
signal?: AbortSignal;
|
|
183
58
|
}
|
|
@@ -214,30 +89,6 @@ function errorResult(
|
|
|
214
89
|
return result;
|
|
215
90
|
}
|
|
216
91
|
|
|
217
|
-
function dispatchErrorResult(
|
|
218
|
-
code: string,
|
|
219
|
-
message: string,
|
|
220
|
-
options: {
|
|
221
|
-
requestId?: string;
|
|
222
|
-
retryable?: boolean;
|
|
223
|
-
details?: unknown;
|
|
224
|
-
} = {},
|
|
225
|
-
): CallToolResult {
|
|
226
|
-
const payload = {
|
|
227
|
-
accepted: false,
|
|
228
|
-
code,
|
|
229
|
-
message: redactMcpText(message).text,
|
|
230
|
-
retryable: options.retryable === true,
|
|
231
|
-
requestId: options.requestId,
|
|
232
|
-
details: options.details,
|
|
233
|
-
};
|
|
234
|
-
const result = textResult({
|
|
235
|
-
error: payload,
|
|
236
|
-
...payload,
|
|
237
|
-
});
|
|
238
|
-
result.isError = true;
|
|
239
|
-
return result;
|
|
240
|
-
}
|
|
241
92
|
|
|
242
93
|
function audit(
|
|
243
94
|
ctx: McpToolContext,
|
|
@@ -319,429 +170,25 @@ function knownRunPathReplacements(
|
|
|
319
170
|
.map(([from, to]) => ({ from, to }));
|
|
320
171
|
}
|
|
321
172
|
|
|
322
|
-
function redactRunText(
|
|
323
|
-
text: string,
|
|
324
|
-
repoRoot: string,
|
|
325
|
-
run: Partial<{
|
|
326
|
-
executionRoot: string;
|
|
327
|
-
worktree: string;
|
|
328
|
-
worktreePath: string;
|
|
329
|
-
}>,
|
|
330
|
-
maxChars: number,
|
|
331
|
-
): { text: string; truncated: boolean; redactions: Array<{ type: string; count: number }> } {
|
|
332
|
-
let scrubbed = text;
|
|
333
|
-
for (const entry of knownRunPathReplacements(repoRoot, run)) {
|
|
334
|
-
scrubbed = scrubbed.split(entry.from).join(entry.to);
|
|
335
|
-
}
|
|
336
|
-
scrubbed = scrubbed
|
|
337
|
-
.replace(/\/(?:private\/)?tmp\/[^\s"']+/g, "<abs-path>")
|
|
338
|
-
.replace(/\/(?:private\/)?var\/folders\/[^\s"']+/g, "<abs-path>")
|
|
339
|
-
.replace(/\/Users\/[^\s"']+/g, "<abs-path>")
|
|
340
|
-
.replace(/[A-Za-z]:\\[^\s"']+/g, "<abs-path>");
|
|
341
|
-
const redacted = redactMcpText(scrubbed);
|
|
342
|
-
if (redacted.text.length <= maxChars) {
|
|
343
|
-
return { text: redacted.text, truncated: false, redactions: redacted.redactions };
|
|
344
|
-
}
|
|
345
|
-
return {
|
|
346
|
-
text: redacted.text.slice(-maxChars),
|
|
347
|
-
truncated: true,
|
|
348
|
-
redactions: redacted.redactions,
|
|
349
|
-
};
|
|
350
|
-
}
|
|
351
173
|
|
|
352
|
-
function maybeTail(
|
|
353
|
-
value: string | undefined,
|
|
354
|
-
repoRoot: string,
|
|
355
|
-
run: Partial<{
|
|
356
|
-
executionRoot: string;
|
|
357
|
-
worktree: string;
|
|
358
|
-
worktreePath: string;
|
|
359
|
-
}>,
|
|
360
|
-
maxChars: number,
|
|
361
|
-
): { text: string; truncated: boolean; redactions: Array<{ type: string; count: number }> } | undefined {
|
|
362
|
-
if (!value) return undefined;
|
|
363
|
-
return redactRunText(value, repoRoot, run, maxChars);
|
|
364
|
-
}
|
|
365
174
|
|
|
366
|
-
function summarizeAgentRun(
|
|
367
|
-
repoRoot: string,
|
|
368
|
-
run: Record<string, unknown>,
|
|
369
|
-
options: {
|
|
370
|
-
includePaths?: boolean;
|
|
371
|
-
includeOutput?: boolean;
|
|
372
|
-
tailChars?: number;
|
|
373
|
-
} = {},
|
|
374
|
-
): Record<string, unknown> {
|
|
375
|
-
const tailChars = Math.min(Math.max(options.tailChars ?? 4096, 256), 16 * 1024);
|
|
376
|
-
const base: Record<string, unknown> = {
|
|
377
|
-
runId: run.runId,
|
|
378
|
-
issueId: run.issueId,
|
|
379
|
-
taskId: run.taskId,
|
|
380
|
-
agent: run.agent,
|
|
381
|
-
provider: run.provider,
|
|
382
|
-
executionMode: run.executionMode,
|
|
383
|
-
executionClass: run.executionClass,
|
|
384
|
-
allowedPaths: run.allowedPaths,
|
|
385
|
-
status: run.status,
|
|
386
|
-
closureState: run.closureState,
|
|
387
|
-
closureUpdatedAt: run.closureUpdatedAt,
|
|
388
|
-
preservationReason: run.preservationReason,
|
|
389
|
-
preservationDetails: typeof run.preservationDetails === "string"
|
|
390
|
-
? redactRunText(run.preservationDetails, repoRoot, {
|
|
391
|
-
executionRoot: typeof run.executionRoot === "string" ? run.executionRoot : undefined,
|
|
392
|
-
worktree: typeof run.worktree === "string" ? run.worktree : undefined,
|
|
393
|
-
worktreePath: typeof run.worktreePath === "string" ? run.worktreePath : undefined,
|
|
394
|
-
}, 1000).text
|
|
395
|
-
: run.preservationDetails,
|
|
396
|
-
branch: run.branch,
|
|
397
|
-
baseRevision: run.baseRevision,
|
|
398
|
-
changedFiles: Array.isArray(run.changedFiles) ? run.changedFiles.slice(0, 100) : run.changedFiles,
|
|
399
|
-
exitCode: run.exitCode,
|
|
400
|
-
error: typeof run.error === "string"
|
|
401
|
-
? redactRunText(run.error, repoRoot, {
|
|
402
|
-
executionRoot: typeof run.executionRoot === "string" ? run.executionRoot : undefined,
|
|
403
|
-
worktree: typeof run.worktree === "string" ? run.worktree : undefined,
|
|
404
|
-
worktreePath: typeof run.worktreePath === "string" ? run.worktreePath : undefined,
|
|
405
|
-
}, 1000).text
|
|
406
|
-
: run.error,
|
|
407
|
-
executorHealth: run.executorHealth,
|
|
408
|
-
timeoutMs: run.timeoutMs,
|
|
409
|
-
deadlineAt: run.deadlineAt,
|
|
410
|
-
startupDeadlineAt: run.startupDeadlineAt,
|
|
411
|
-
lastHeartbeatAt: run.lastHeartbeatAt,
|
|
412
|
-
progress: run.progress,
|
|
413
|
-
autoIntegrate: run.autoIntegrate,
|
|
414
|
-
autoIntegrationError: typeof run.autoIntegrationError === "string"
|
|
415
|
-
? redactRunText(run.autoIntegrationError, repoRoot, {
|
|
416
|
-
executionRoot: typeof run.executionRoot === "string" ? run.executionRoot : undefined,
|
|
417
|
-
worktree: typeof run.worktree === "string" ? run.worktree : undefined,
|
|
418
|
-
worktreePath: typeof run.worktreePath === "string" ? run.worktreePath : undefined,
|
|
419
|
-
}, 1000).text
|
|
420
|
-
: run.autoIntegrationError,
|
|
421
|
-
worktreeCleanedAt: run.worktreeCleanedAt,
|
|
422
|
-
cleanupBranchDeletedAt: run.cleanupBranchDeletedAt,
|
|
423
|
-
diffArtifactPath: run.diffArtifactPath,
|
|
424
|
-
terminationReason: run.terminationReason,
|
|
425
|
-
createdAt: run.createdAt,
|
|
426
|
-
startedAt: run.startedAt,
|
|
427
|
-
finishedAt: run.finishedAt,
|
|
428
|
-
integratedSessionId: run.integratedSessionId,
|
|
429
|
-
integratedAt: run.integratedAt,
|
|
430
|
-
timing: run.timing,
|
|
431
|
-
github: run.github && typeof run.github === "object"
|
|
432
|
-
? {
|
|
433
|
-
owner: (run.github as Record<string, unknown>).owner,
|
|
434
|
-
repo: (run.github as Record<string, unknown>).repo,
|
|
435
|
-
taskId: (run.github as Record<string, unknown>).taskId,
|
|
436
|
-
state: (run.github as Record<string, unknown>).state,
|
|
437
|
-
url: (run.github as Record<string, unknown>).url,
|
|
438
|
-
pullRequestUrl: (run.github as Record<string, unknown>).pullRequestUrl,
|
|
439
|
-
baseRef: (run.github as Record<string, unknown>).baseRef,
|
|
440
|
-
model: (run.github as Record<string, unknown>).model,
|
|
441
|
-
createPullRequest: (run.github as Record<string, unknown>).createPullRequest,
|
|
442
|
-
}
|
|
443
|
-
: run.github,
|
|
444
|
-
detail: {
|
|
445
|
-
pathsIncluded: options.includePaths === true,
|
|
446
|
-
outputIncluded: options.includeOutput !== false,
|
|
447
|
-
next: {
|
|
448
|
-
log: "Call get_task_run_log for a larger bounded tail.",
|
|
449
|
-
diff: "Call get_task_diff to inspect Git changes from an isolated worktree.",
|
|
450
|
-
},
|
|
451
|
-
},
|
|
452
|
-
};
|
|
453
|
-
if (options.includeOutput !== false) {
|
|
454
|
-
const stdout = maybeTail(
|
|
455
|
-
typeof run.stdoutTail === "string" ? run.stdoutTail : undefined,
|
|
456
|
-
repoRoot,
|
|
457
|
-
{
|
|
458
|
-
executionRoot: typeof run.executionRoot === "string" ? run.executionRoot : undefined,
|
|
459
|
-
worktree: typeof run.worktree === "string" ? run.worktree : undefined,
|
|
460
|
-
worktreePath: typeof run.worktreePath === "string" ? run.worktreePath : undefined,
|
|
461
|
-
},
|
|
462
|
-
tailChars,
|
|
463
|
-
);
|
|
464
|
-
const stderr = maybeTail(
|
|
465
|
-
typeof run.stderrTail === "string" ? run.stderrTail : undefined,
|
|
466
|
-
repoRoot,
|
|
467
|
-
{
|
|
468
|
-
executionRoot: typeof run.executionRoot === "string" ? run.executionRoot : undefined,
|
|
469
|
-
worktree: typeof run.worktree === "string" ? run.worktree : undefined,
|
|
470
|
-
worktreePath: typeof run.worktreePath === "string" ? run.worktreePath : undefined,
|
|
471
|
-
},
|
|
472
|
-
tailChars,
|
|
473
|
-
);
|
|
474
|
-
if (stdout) {
|
|
475
|
-
base.stdoutTail = stdout.text;
|
|
476
|
-
base.stdoutTailTruncated = stdout.truncated;
|
|
477
|
-
}
|
|
478
|
-
if (stderr) {
|
|
479
|
-
base.stderrTail = stderr.text;
|
|
480
|
-
base.stderrTailTruncated = stderr.truncated;
|
|
481
|
-
}
|
|
482
|
-
const redactions = [...(stdout?.redactions ?? []), ...(stderr?.redactions ?? [])];
|
|
483
|
-
if (redactions.length > 0) base.redactions = redactions;
|
|
484
|
-
}
|
|
485
|
-
if (options.includePaths === true) {
|
|
486
|
-
Object.assign(base, {
|
|
487
|
-
repoRoot: run.repoRoot,
|
|
488
|
-
executionRoot: run.executionRoot,
|
|
489
|
-
worktree: run.worktree,
|
|
490
|
-
worktreePath: run.worktreePath,
|
|
491
|
-
promptPath: run.promptPath,
|
|
492
|
-
stdoutPath: run.stdoutPath,
|
|
493
|
-
stderrPath: run.stderrPath,
|
|
494
|
-
resultPath: run.resultPath,
|
|
495
|
-
eventsPath: run.eventsPath,
|
|
496
|
-
controllerPid: run.controllerPid,
|
|
497
|
-
controllerEpoch: run.controllerEpoch,
|
|
498
|
-
controllerEpochPath: run.controllerEpochPath,
|
|
499
|
-
launchPid: run.launchPid,
|
|
500
|
-
workerPid: run.workerPid,
|
|
501
|
-
agentPid: run.agentPid,
|
|
502
|
-
});
|
|
503
|
-
}
|
|
504
|
-
return base;
|
|
505
|
-
}
|
|
506
175
|
|
|
507
|
-
function summarizeRunList(
|
|
508
|
-
repoRoot: string,
|
|
509
|
-
runs: Array<Record<string, unknown>>,
|
|
510
|
-
includePaths = false,
|
|
511
|
-
): Array<Record<string, unknown>> {
|
|
512
|
-
return runs.map((run) => summarizeAgentRun(repoRoot, run, { includePaths, includeOutput: false }));
|
|
513
|
-
}
|
|
514
176
|
|
|
515
|
-
function compactDispatchRun(run: Record<string, unknown>): Record<string, unknown> {
|
|
516
|
-
const runId = typeof run.runId === "string" ? run.runId : String(run.runId ?? "");
|
|
517
|
-
const detail: Record<string, unknown> = runId
|
|
518
|
-
? {
|
|
519
|
-
get_task_run: { run_id: runId },
|
|
520
|
-
get_task_run_log: { run_id: runId },
|
|
521
|
-
get_task_run_events: { run_id: runId },
|
|
522
|
-
get_task_diff: { run_id: runId },
|
|
523
|
-
}
|
|
524
|
-
: {};
|
|
525
|
-
return {
|
|
526
|
-
runId: run.runId,
|
|
527
|
-
issueId: run.issueId,
|
|
528
|
-
taskId: run.taskId,
|
|
529
|
-
status: run.status,
|
|
530
|
-
agent: run.agent,
|
|
531
|
-
provider: run.provider,
|
|
532
|
-
executionMode: run.executionMode,
|
|
533
|
-
executionClass: run.executionClass,
|
|
534
|
-
timeoutMs: run.timeoutMs,
|
|
535
|
-
startedAt: run.startedAt,
|
|
536
|
-
deadlineAt: run.deadlineAt,
|
|
537
|
-
finishedAt: run.finishedAt,
|
|
538
|
-
integratedSessionId: run.integratedSessionId,
|
|
539
|
-
autoIntegrationError: run.autoIntegrationError,
|
|
540
|
-
worktreeCleanedAt: run.worktreeCleanedAt,
|
|
541
|
-
accepted: run.accepted,
|
|
542
|
-
reused: run.reused,
|
|
543
|
-
github: run.github,
|
|
544
|
-
error: run.error,
|
|
545
|
-
detail,
|
|
546
|
-
};
|
|
547
|
-
}
|
|
548
177
|
|
|
549
|
-
function compactTaskView(task: Record<string, unknown>): Record<string, unknown> {
|
|
550
|
-
return {
|
|
551
|
-
id: task.id,
|
|
552
|
-
title: task.title,
|
|
553
|
-
status: task.status,
|
|
554
|
-
effectiveStatus: task.effectiveStatus,
|
|
555
|
-
declaredStatus: task.declaredStatus,
|
|
556
|
-
latestRunId: task.latestRunId,
|
|
557
|
-
latestRunStatus: task.latestRunStatus,
|
|
558
|
-
activeRunId: task.activeRunId,
|
|
559
|
-
activeRunStatus: task.activeRunStatus,
|
|
560
|
-
verificationStatus: task.verificationStatus,
|
|
561
|
-
dispatchable: task.dispatchable,
|
|
562
|
-
retryable: task.retryable,
|
|
563
|
-
requiresExplicitRetry: task.requiresExplicitRetry,
|
|
564
|
-
};
|
|
565
|
-
}
|
|
566
178
|
|
|
567
|
-
function compactIssueView(issue: Record<string, unknown>): Record<string, unknown> {
|
|
568
|
-
const id = typeof issue.id === "string" ? issue.id : String(issue.id ?? "");
|
|
569
|
-
const tasks = Array.isArray(issue.tasks) ? issue.tasks as Array<Record<string, unknown>> : [];
|
|
570
|
-
return {
|
|
571
|
-
id: issue.id,
|
|
572
|
-
title: issue.title,
|
|
573
|
-
status: issue.status,
|
|
574
|
-
lifecycleStatus: issue.lifecycleStatus,
|
|
575
|
-
github: issue.github,
|
|
576
|
-
taskCount: tasks.length,
|
|
577
|
-
tasks: tasks.map((task) => compactTaskView(task)),
|
|
578
|
-
detail: id ? { get_issue: { issue_id: id } } : {},
|
|
579
|
-
};
|
|
580
|
-
}
|
|
581
179
|
|
|
582
180
|
const PROJECT_BOARD_RECENT_ISSUE_LIMIT = 5;
|
|
583
181
|
const PROJECT_BOARD_TASK_LIMIT = 8;
|
|
584
182
|
|
|
585
|
-
function compactProjectBoardText(value: unknown, maxChars = 96): unknown {
|
|
586
|
-
if (typeof value !== "string" || value.length <= maxChars) return value;
|
|
587
|
-
return `${value.slice(0, Math.max(0, maxChars - 1))}…`;
|
|
588
|
-
}
|
|
589
183
|
|
|
590
|
-
function compactProjectBoardTask(
|
|
591
|
-
task: Record<string, unknown>,
|
|
592
|
-
options: { includeLifecycle?: boolean } = {},
|
|
593
|
-
): Record<string, unknown> {
|
|
594
|
-
const compact: Record<string, unknown> = {
|
|
595
|
-
issueId: task.issueId,
|
|
596
|
-
taskId: task.taskId,
|
|
597
|
-
id: task.id,
|
|
598
|
-
title: compactProjectBoardText(task.title),
|
|
599
|
-
agent: task.agent,
|
|
600
|
-
effectiveStatus: task.effectiveStatus,
|
|
601
|
-
executionClass: task.executionClass,
|
|
602
|
-
approval: task.approval,
|
|
603
|
-
};
|
|
604
|
-
if (options.includeLifecycle) {
|
|
605
|
-
Object.assign(compact, {
|
|
606
|
-
status: task.status,
|
|
607
|
-
declaredStatus: task.declaredStatus,
|
|
608
|
-
statusReason: task.statusReason,
|
|
609
|
-
verificationStatus: task.verificationStatus,
|
|
610
|
-
dispatchable: task.dispatchable,
|
|
611
|
-
queueable: task.queueable,
|
|
612
|
-
retryable: task.retryable,
|
|
613
|
-
requiresExplicitRetry: task.requiresExplicitRetry,
|
|
614
|
-
});
|
|
615
|
-
}
|
|
616
|
-
return compact;
|
|
617
|
-
}
|
|
618
184
|
|
|
619
|
-
function compactProjectBoardIssue(
|
|
620
|
-
issue: Record<string, unknown>,
|
|
621
|
-
options: { includeTasks?: boolean } = {},
|
|
622
|
-
): Record<string, unknown> {
|
|
623
|
-
const tasks = Array.isArray(issue.tasks) ? issue.tasks as Array<Record<string, unknown>> : [];
|
|
624
|
-
const taskCounts = tasks.reduce<Record<string, number>>((counts, task) => {
|
|
625
|
-
const status = typeof task.effectiveStatus === "string" ? task.effectiveStatus : "unknown";
|
|
626
|
-
counts[status] = (counts[status] ?? 0) + 1;
|
|
627
|
-
return counts;
|
|
628
|
-
}, {});
|
|
629
|
-
const compact: Record<string, unknown> = {
|
|
630
|
-
id: issue.id,
|
|
631
|
-
title: compactProjectBoardText(issue.title),
|
|
632
|
-
kind: issue.kind,
|
|
633
|
-
status: issue.status,
|
|
634
|
-
lifecycleStatus: issue.lifecycleStatus,
|
|
635
|
-
updatedAt: issue.updatedAt,
|
|
636
|
-
isCurrent: issue.isCurrent === true,
|
|
637
|
-
taskCount: tasks.length,
|
|
638
|
-
taskCounts,
|
|
639
|
-
readyTaskCount: tasks.filter((task) => task.dispatchable === true).length,
|
|
640
|
-
queueableTaskCount: tasks.filter((task) => task.queueable === true).length,
|
|
641
|
-
};
|
|
642
|
-
if (options.includeTasks) {
|
|
643
|
-
compact.tasks = tasks
|
|
644
|
-
.slice(0, PROJECT_BOARD_TASK_LIMIT)
|
|
645
|
-
.map((task) => compactProjectBoardTask(task, { includeLifecycle: true }));
|
|
646
|
-
compact.taskTruncatedCount = Math.max(0, tasks.length - PROJECT_BOARD_TASK_LIMIT);
|
|
647
|
-
}
|
|
648
|
-
return compact;
|
|
649
|
-
}
|
|
650
185
|
|
|
651
|
-
function summarizeProjectBoard(board: Record<string, unknown>): Record<string, unknown> {
|
|
652
|
-
const issues = Array.isArray(board.issues) ? board.issues as Array<Record<string, unknown>> : [];
|
|
653
|
-
const readyTasks = Array.isArray(board.readyTasks) ? board.readyTasks as Array<Record<string, unknown>> : [];
|
|
654
|
-
const queueableTasks = Array.isArray(board.queueableTasks) ? board.queueableTasks as Array<Record<string, unknown>> : [];
|
|
655
|
-
const currentIssue = issues.find((issue) => issue.isCurrent === true);
|
|
656
|
-
const recentIssueCandidates = currentIssue
|
|
657
|
-
? issues.filter((issue) => issue !== currentIssue)
|
|
658
|
-
: issues;
|
|
659
|
-
const recentIssues = recentIssueCandidates.slice(0, PROJECT_BOARD_RECENT_ISSUE_LIMIT);
|
|
660
|
-
return {
|
|
661
|
-
detailLevel: "summary",
|
|
662
|
-
issueCount: issues.length,
|
|
663
|
-
archivedIssueCount: board.archivedIssueCount,
|
|
664
|
-
counts: board.counts,
|
|
665
|
-
declaredCounts: board.declaredCounts,
|
|
666
|
-
archivedCounts: board.archivedCounts,
|
|
667
|
-
currentIssueId: board.currentIssueId,
|
|
668
|
-
currentIssue: currentIssue ? compactProjectBoardIssue(currentIssue, { includeTasks: true }) : undefined,
|
|
669
|
-
recentIssues: recentIssues.map((issue) => compactProjectBoardIssue(issue)),
|
|
670
|
-
recentIssueTruncatedCount: Math.max(0, recentIssueCandidates.length - recentIssues.length),
|
|
671
|
-
readyTaskCount: readyTasks.length,
|
|
672
|
-
readyTasks: readyTasks.slice(0, PROJECT_BOARD_TASK_LIMIT).map((task) => compactProjectBoardTask(task)),
|
|
673
|
-
readyTaskTruncatedCount: Math.max(0, readyTasks.length - PROJECT_BOARD_TASK_LIMIT),
|
|
674
|
-
queueableTaskCount: queueableTasks.length,
|
|
675
|
-
queueableTasks: queueableTasks.slice(0, PROJECT_BOARD_TASK_LIMIT).map((task) => compactProjectBoardTask(task)),
|
|
676
|
-
queueableTaskTruncatedCount: Math.max(0, queueableTasks.length - PROJECT_BOARD_TASK_LIMIT),
|
|
677
|
-
detailPointer: {
|
|
678
|
-
tool: "get_project_board",
|
|
679
|
-
arguments: { detail_level: "detail" },
|
|
680
|
-
},
|
|
681
|
-
};
|
|
682
|
-
}
|
|
683
186
|
|
|
684
187
|
const UPDATE_TASK_SUMMARY_TASK_LIMIT = 12;
|
|
685
188
|
|
|
686
|
-
function compactUpdatedTaskView(task: Record<string, unknown>): Record<string, unknown> {
|
|
687
|
-
return {
|
|
688
|
-
...compactTaskView(task),
|
|
689
|
-
title: compactProjectBoardText(task.title),
|
|
690
|
-
};
|
|
691
|
-
}
|
|
692
189
|
|
|
693
|
-
function summarizeUpdatedIssue(
|
|
694
|
-
issue: Record<string, unknown>,
|
|
695
|
-
taskId: string,
|
|
696
|
-
): Record<string, unknown> {
|
|
697
|
-
const tasks = Array.isArray(issue.tasks) ? issue.tasks as Array<Record<string, unknown>> : [];
|
|
698
|
-
const updatedTask = tasks.find((task) => task.id === taskId);
|
|
699
|
-
return {
|
|
700
|
-
detailLevel: "summary",
|
|
701
|
-
id: issue.id,
|
|
702
|
-
title: compactProjectBoardText(issue.title),
|
|
703
|
-
status: issue.status,
|
|
704
|
-
lifecycleStatus: issue.lifecycleStatus,
|
|
705
|
-
github: issue.github,
|
|
706
|
-
taskCount: tasks.length,
|
|
707
|
-
tasks: tasks.slice(0, UPDATE_TASK_SUMMARY_TASK_LIMIT).map(compactUpdatedTaskView),
|
|
708
|
-
taskTruncatedCount: Math.max(0, tasks.length - UPDATE_TASK_SUMMARY_TASK_LIMIT),
|
|
709
|
-
updatedTask: updatedTask ? compactUpdatedTaskView(updatedTask) : undefined,
|
|
710
|
-
detailPointer: {
|
|
711
|
-
tool: "get_issue",
|
|
712
|
-
arguments: { issue_id: issue.id, detail_level: "full" },
|
|
713
|
-
},
|
|
714
|
-
};
|
|
715
|
-
}
|
|
716
190
|
|
|
717
|
-
function dispatchExecutorHealthResult(
|
|
718
|
-
health: ExecutorHealth,
|
|
719
|
-
options: { requestId?: string; retryable?: boolean } = {},
|
|
720
|
-
): CallToolResult {
|
|
721
|
-
return dispatchErrorResult(executorHealthCode(health), health.message, {
|
|
722
|
-
requestId: options.requestId,
|
|
723
|
-
retryable: options.retryable,
|
|
724
|
-
details: { executorHealth: health },
|
|
725
|
-
});
|
|
726
|
-
}
|
|
727
191
|
|
|
728
|
-
function taskExecutorHealth(
|
|
729
|
-
ctx: McpToolContext,
|
|
730
|
-
task: ControllerTask,
|
|
731
|
-
agent: ControllerAgent,
|
|
732
|
-
githubRepo?: string,
|
|
733
|
-
): ExecutorHealth | null {
|
|
734
|
-
if (agent === "github-copilot") {
|
|
735
|
-
return classifyGitHubCopilotPreflight(getGitHubStatus(ctx.repoRoot, githubRepo));
|
|
736
|
-
}
|
|
737
|
-
return classifyLocalExecutorHealth(
|
|
738
|
-
agent,
|
|
739
|
-
{
|
|
740
|
-
agentRunner: ctx.policy.execution.agentRunner,
|
|
741
|
-
allowedAgents: ctx.policy.execution.allowedAgents,
|
|
742
|
-
},
|
|
743
|
-
);
|
|
744
|
-
}
|
|
745
192
|
|
|
746
193
|
function listFilesUnder(
|
|
747
194
|
repoRoot: string,
|
|
@@ -820,28 +267,7 @@ function timestampPrefix(date = new Date()): string {
|
|
|
820
267
|
return `${yyyy}${mm}${dd}-${hh}${min}`;
|
|
821
268
|
}
|
|
822
269
|
|
|
823
|
-
function parseControllerAgent(value: unknown): ControllerAgent | null {
|
|
824
|
-
const normalized = String(value ?? "")
|
|
825
|
-
.trim()
|
|
826
|
-
.toLowerCase();
|
|
827
|
-
return normalized === "codex" ||
|
|
828
|
-
normalized === "claude" ||
|
|
829
|
-
normalized === "github-copilot"
|
|
830
|
-
? normalized
|
|
831
|
-
: null;
|
|
832
|
-
}
|
|
833
270
|
|
|
834
|
-
function resolveDispatchAgent(
|
|
835
|
-
ctx: McpToolContext,
|
|
836
|
-
task: ControllerTask,
|
|
837
|
-
requested: unknown,
|
|
838
|
-
): ControllerAgent | null {
|
|
839
|
-
const hasExplicitRequest = requested !== undefined && requested !== null && String(requested).trim() !== "";
|
|
840
|
-
if (hasExplicitRequest) return parseControllerAgent(requested);
|
|
841
|
-
if (task.recommendedAgent) return task.recommendedAgent;
|
|
842
|
-
const firstLocal = ctx.policy.execution.allowedAgents[0];
|
|
843
|
-
return firstLocal ?? task.recommendedAgent ?? null;
|
|
844
|
-
}
|
|
845
271
|
|
|
846
272
|
function runnerTimeoutMs(ctx: McpToolContext, value: unknown): number {
|
|
847
273
|
return normalizeAgentTimeoutMs(value, {
|
|
@@ -991,50 +417,8 @@ interface TaskVerificationLeaseConflict {
|
|
|
991
417
|
blockingProcessId?: string;
|
|
992
418
|
}
|
|
993
419
|
|
|
994
|
-
function taskVerificationLeaseConflict(record: {
|
|
995
|
-
error?: { message?: unknown };
|
|
996
|
-
}): TaskVerificationLeaseConflict | undefined {
|
|
997
|
-
const message = typeof record.error?.message === "string" ? record.error.message.trim() : "";
|
|
998
|
-
if (!message.startsWith("PROCESS_LEASE_CONFLICT:")) return undefined;
|
|
999
|
-
const match = /^PROCESS_LEASE_CONFLICT:\s*(.+?)@process:([A-Za-z0-9._-]+)$/.exec(message);
|
|
1000
|
-
return {
|
|
1001
|
-
message,
|
|
1002
|
-
resourceKey: match?.[1],
|
|
1003
|
-
blockingProcessId: match?.[2],
|
|
1004
|
-
};
|
|
1005
|
-
}
|
|
1006
420
|
|
|
1007
|
-
function taskVerificationRetryRequestId(
|
|
1008
|
-
requestBase: string,
|
|
1009
|
-
checkId: string,
|
|
1010
|
-
conflictedProcessId: string,
|
|
1011
|
-
): string {
|
|
1012
|
-
const stableBase = requestBase.replace(/:retry:[a-f0-9]{16}$/i, "");
|
|
1013
|
-
const retryToken = createHash("sha256")
|
|
1014
|
-
.update(`${stableBase}\0${checkId}\0${conflictedProcessId}`)
|
|
1015
|
-
.digest("hex")
|
|
1016
|
-
.slice(0, 16);
|
|
1017
|
-
return `${stableBase}:retry:${retryToken}`;
|
|
1018
|
-
}
|
|
1019
421
|
|
|
1020
|
-
function controllerTaskDrafts(value: unknown): TaskDraft[] {
|
|
1021
|
-
return taskObjects(value).map((task) => ({
|
|
1022
|
-
title: String(task.title ?? "").trim(),
|
|
1023
|
-
objective: String(task.objective ?? "").trim(),
|
|
1024
|
-
dependsOn: stringList(task.depends_on ?? task.dependsOn),
|
|
1025
|
-
allowedPaths: stringList(task.allowed_paths ?? task.allowedPaths),
|
|
1026
|
-
forbiddenPaths: stringList(task.forbidden_paths ?? task.forbiddenPaths),
|
|
1027
|
-
checks: stringList(task.checks),
|
|
1028
|
-
acceptanceCriteria: stringList(
|
|
1029
|
-
task.acceptance_criteria ?? task.acceptanceCriteria,
|
|
1030
|
-
),
|
|
1031
|
-
risk: (["readonly", "low", "medium", "high", "destructive"].includes(String(task.risk))
|
|
1032
|
-
? String(task.risk)
|
|
1033
|
-
: "medium") as TaskRisk,
|
|
1034
|
-
recommendedAgent:
|
|
1035
|
-
parseControllerAgent(task.agent ?? task.recommendedAgent) ?? undefined,
|
|
1036
|
-
}));
|
|
1037
|
-
}
|
|
1038
422
|
|
|
1039
423
|
function renderPrdFromIdeaBody(args: Record<string, unknown>): string {
|
|
1040
424
|
const title = String(args.title ?? "Untitled PRD").trim() || "Untitled PRD";
|
|
@@ -1750,78 +1134,6 @@ export function buildMcpToolDefinitions(
|
|
|
1750
1134
|
inputSchema: EMPTY_SCHEMA,
|
|
1751
1135
|
annotations: readOnly,
|
|
1752
1136
|
},
|
|
1753
|
-
{
|
|
1754
|
-
name: "list_issues",
|
|
1755
|
-
description: "List controller-managed issues and task summaries.",
|
|
1756
|
-
inputSchema: EMPTY_SCHEMA,
|
|
1757
|
-
annotations: readOnly,
|
|
1758
|
-
},
|
|
1759
|
-
{
|
|
1760
|
-
name: "get_project_board",
|
|
1761
|
-
description: "Return the bounded Requirement Board by default. Explicit detail/full mode returns bounded Execution Diagnostics without exposing the full legacy Task/Run/Process ledger.",
|
|
1762
|
-
inputSchema: {
|
|
1763
|
-
type: "object",
|
|
1764
|
-
properties: {
|
|
1765
|
-
detail_level: { type: "string", enum: ["summary", "detail", "full"] },
|
|
1766
|
-
requirement_id: { type: "string" },
|
|
1767
|
-
},
|
|
1768
|
-
additionalProperties: false,
|
|
1769
|
-
},
|
|
1770
|
-
annotations: readOnly,
|
|
1771
|
-
},
|
|
1772
|
-
{
|
|
1773
|
-
name: "get_project_governance",
|
|
1774
|
-
description:
|
|
1775
|
-
"Return the current execution focus, evidence-driven execution queue, dead dependencies, pending review/acceptance, duplicate active Issues, and closeout anomalies.",
|
|
1776
|
-
inputSchema: EMPTY_SCHEMA,
|
|
1777
|
-
annotations: readOnly,
|
|
1778
|
-
},
|
|
1779
|
-
{
|
|
1780
|
-
name: "reconcile_project_governance",
|
|
1781
|
-
description:
|
|
1782
|
-
"Apply only safe governance repairs: replace superseded dependencies, return failed attempts to a retryable Task state, close fully completed Issues, and select the sole active Issue as focus.",
|
|
1783
|
-
inputSchema: EMPTY_SCHEMA,
|
|
1784
|
-
annotations: write,
|
|
1785
|
-
},
|
|
1786
|
-
{
|
|
1787
|
-
name: "get_project_state",
|
|
1788
|
-
description: "Return the current execution focus and Issue creation policy.",
|
|
1789
|
-
inputSchema: EMPTY_SCHEMA,
|
|
1790
|
-
annotations: readOnly,
|
|
1791
|
-
},
|
|
1792
|
-
{
|
|
1793
|
-
name: "set_current_issue",
|
|
1794
|
-
description: "Select the one active Issue that is allowed to drive the execution queue.",
|
|
1795
|
-
inputSchema: {
|
|
1796
|
-
type: "object",
|
|
1797
|
-
properties: { issue_id: { type: "string" } },
|
|
1798
|
-
required: ["issue_id"],
|
|
1799
|
-
additionalProperties: false,
|
|
1800
|
-
},
|
|
1801
|
-
annotations: write,
|
|
1802
|
-
},
|
|
1803
|
-
{
|
|
1804
|
-
name: "archive_issue",
|
|
1805
|
-
description: "Move a done or cancelled Issue out of the current workspace while retaining its full evidence history.",
|
|
1806
|
-
inputSchema: {
|
|
1807
|
-
type: "object",
|
|
1808
|
-
properties: { issue_id: { type: "string" } },
|
|
1809
|
-
required: ["issue_id"],
|
|
1810
|
-
additionalProperties: false,
|
|
1811
|
-
},
|
|
1812
|
-
annotations: write,
|
|
1813
|
-
},
|
|
1814
|
-
{
|
|
1815
|
-
name: "restore_issue",
|
|
1816
|
-
description: "Restore an archived Issue to the normal controller views.",
|
|
1817
|
-
inputSchema: {
|
|
1818
|
-
type: "object",
|
|
1819
|
-
properties: { issue_id: { type: "string" } },
|
|
1820
|
-
required: ["issue_id"],
|
|
1821
|
-
additionalProperties: false,
|
|
1822
|
-
},
|
|
1823
|
-
annotations: write,
|
|
1824
|
-
},
|
|
1825
1137
|
{
|
|
1826
1138
|
name: "get_worklog_timeline",
|
|
1827
1139
|
description:
|
|
@@ -1887,20 +1199,6 @@ export function buildMcpToolDefinitions(
|
|
|
1887
1199
|
},
|
|
1888
1200
|
annotations: write,
|
|
1889
1201
|
},
|
|
1890
|
-
{
|
|
1891
|
-
name: "get_issue",
|
|
1892
|
-
description: "Read one controller issue by ID with bounded task summaries by default and full detail on demand.",
|
|
1893
|
-
inputSchema: {
|
|
1894
|
-
type: "object",
|
|
1895
|
-
properties: {
|
|
1896
|
-
issue_id: { type: "string" },
|
|
1897
|
-
detail_level: { type: "string", enum: ["summary", "full"] },
|
|
1898
|
-
},
|
|
1899
|
-
required: ["issue_id"],
|
|
1900
|
-
additionalProperties: false,
|
|
1901
|
-
},
|
|
1902
|
-
annotations: readOnly,
|
|
1903
|
-
},
|
|
1904
1202
|
{
|
|
1905
1203
|
name: "github_status",
|
|
1906
1204
|
description:
|
|
@@ -1913,362 +1211,167 @@ export function buildMcpToolDefinitions(
|
|
|
1913
1211
|
annotations: readOnly,
|
|
1914
1212
|
},
|
|
1915
1213
|
{
|
|
1916
|
-
name: "
|
|
1214
|
+
name: "begin_edit_session",
|
|
1917
1215
|
description:
|
|
1918
|
-
"
|
|
1216
|
+
"Open a bounded direct-edit session tied to a purpose and optional Issue/Task.",
|
|
1919
1217
|
inputSchema: {
|
|
1920
1218
|
type: "object",
|
|
1921
1219
|
properties: {
|
|
1220
|
+
purpose: { type: "string" },
|
|
1221
|
+
work_id: { type: "string", description: "Optional existing WorkContract. Omit to create lightweight direct-control Work lineage." },
|
|
1922
1222
|
issue_id: { type: "string" },
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1223
|
+
task_id: { type: "string" },
|
|
1224
|
+
allowed_paths: { type: "array", items: { type: "string" } },
|
|
1225
|
+
max_files: { type: "number" },
|
|
1226
|
+
max_changed_lines: { type: "number" },
|
|
1227
|
+
checks: { type: "array", items: { type: "string" }, description: "Named checks to run before finalization." },
|
|
1928
1228
|
},
|
|
1929
|
-
required: ["
|
|
1930
|
-
additionalProperties: false,
|
|
1931
|
-
},
|
|
1932
|
-
annotations: {
|
|
1933
|
-
readOnlyHint: false,
|
|
1934
|
-
openWorldHint: true,
|
|
1935
|
-
destructiveHint: false,
|
|
1936
|
-
},
|
|
1937
|
-
},
|
|
1938
|
-
{
|
|
1939
|
-
name: "refresh_github_issue",
|
|
1940
|
-
description:
|
|
1941
|
-
"Refresh the linked GitHub Issue metadata and remote state.",
|
|
1942
|
-
inputSchema: {
|
|
1943
|
-
type: "object",
|
|
1944
|
-
properties: { issue_id: { type: "string" } },
|
|
1945
|
-
required: ["issue_id"],
|
|
1229
|
+
required: ["purpose"],
|
|
1946
1230
|
additionalProperties: false,
|
|
1947
1231
|
},
|
|
1948
|
-
annotations:
|
|
1232
|
+
annotations: write,
|
|
1949
1233
|
},
|
|
1950
1234
|
{
|
|
1951
|
-
name: "
|
|
1235
|
+
name: "apply_patch",
|
|
1952
1236
|
description:
|
|
1953
|
-
"
|
|
1237
|
+
"Append one bounded patch batch to an active edit session. Prefer batches of 100 operations or fewer and pass expected_revision to avoid stale revisions.",
|
|
1954
1238
|
inputSchema: {
|
|
1955
1239
|
type: "object",
|
|
1956
|
-
properties: {
|
|
1957
|
-
|
|
1240
|
+
properties: {
|
|
1241
|
+
session_id: { type: "string" },
|
|
1242
|
+
expected_revision: { type: "number", description: "Optional current revision precondition for the edit session." },
|
|
1243
|
+
operations: {
|
|
1244
|
+
type: "array",
|
|
1245
|
+
items: EDIT_OPERATION_INPUT_SCHEMA,
|
|
1246
|
+
},
|
|
1247
|
+
},
|
|
1248
|
+
required: ["session_id", "operations"],
|
|
1958
1249
|
additionalProperties: false,
|
|
1959
1250
|
},
|
|
1960
1251
|
annotations: {
|
|
1961
1252
|
readOnlyHint: false,
|
|
1962
|
-
openWorldHint:
|
|
1963
|
-
destructiveHint:
|
|
1253
|
+
openWorldHint: false,
|
|
1254
|
+
destructiveHint: false,
|
|
1964
1255
|
},
|
|
1965
1256
|
},
|
|
1966
1257
|
{
|
|
1967
|
-
name: "
|
|
1968
|
-
description:
|
|
1969
|
-
"Score launch readiness, list blockers and warnings, and summarize executable Tasks. Agent selection happens at dispatch time.",
|
|
1258
|
+
name: "get_edit_session",
|
|
1259
|
+
description: "Read an edit session, revisions, savepoints, and operation manifest.",
|
|
1970
1260
|
inputSchema: {
|
|
1971
1261
|
type: "object",
|
|
1972
|
-
properties: {
|
|
1973
|
-
required: ["
|
|
1262
|
+
properties: { session_id: { type: "string" } },
|
|
1263
|
+
required: ["session_id"],
|
|
1974
1264
|
additionalProperties: false,
|
|
1975
1265
|
},
|
|
1976
1266
|
annotations: readOnly,
|
|
1977
1267
|
},
|
|
1978
1268
|
{
|
|
1979
|
-
name: "
|
|
1980
|
-
description:
|
|
1981
|
-
"Evaluate launch readiness for one Task only. Other Issues, focus selection, unrelated Tasks, and missing named checks do not block it.",
|
|
1269
|
+
name: "list_edit_sessions",
|
|
1270
|
+
description: "List recent direct-edit sessions with revision, changed-file, and check summaries.",
|
|
1982
1271
|
inputSchema: {
|
|
1983
1272
|
type: "object",
|
|
1984
|
-
properties: {
|
|
1985
|
-
issue_id: { type: "string" },
|
|
1986
|
-
task_id: { type: "string" },
|
|
1987
|
-
retry_from_run_id: { type: "string" },
|
|
1988
|
-
approve_destructive: { type: "boolean" },
|
|
1989
|
-
},
|
|
1990
|
-
required: ["issue_id", "task_id"],
|
|
1273
|
+
properties: { limit: { type: "number" } },
|
|
1991
1274
|
additionalProperties: false,
|
|
1992
1275
|
},
|
|
1993
1276
|
annotations: readOnly,
|
|
1994
1277
|
},
|
|
1995
1278
|
{
|
|
1996
|
-
name: "
|
|
1997
|
-
description:
|
|
1998
|
-
"Return an Issue Launcher preview without starting any agent session.",
|
|
1279
|
+
name: "get_edit_session_diff",
|
|
1280
|
+
description: "Read the current aggregate localized diff for one direct-edit session.",
|
|
1999
1281
|
inputSchema: {
|
|
2000
1282
|
type: "object",
|
|
2001
|
-
properties: {
|
|
2002
|
-
|
|
2003
|
-
max_parallel: { type: "number" },
|
|
2004
|
-
},
|
|
2005
|
-
required: ["issue_id"],
|
|
1283
|
+
properties: { session_id: { type: "string" } },
|
|
1284
|
+
required: ["session_id"],
|
|
2006
1285
|
additionalProperties: false,
|
|
2007
1286
|
},
|
|
2008
1287
|
annotations: readOnly,
|
|
2009
1288
|
},
|
|
2010
1289
|
{
|
|
2011
|
-
name: "
|
|
2012
|
-
description:
|
|
2013
|
-
"Create a durable Issue only for broad, long-running, dependency-aware, parallel, protected-path, or high-risk work. File discovery by itself is not a reason to create an Issue or start an Agent; search first and use bounded direct edits when practical.",
|
|
1290
|
+
name: "create_edit_savepoint",
|
|
1291
|
+
description: "Name the current edit revision so later patch batches can be rolled back to it.",
|
|
2014
1292
|
inputSchema: {
|
|
2015
1293
|
type: "object",
|
|
2016
1294
|
properties: {
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
type: "string",
|
|
2020
|
-
enum: ["bug", "feature", "governance", "investigation"],
|
|
2021
|
-
},
|
|
2022
|
-
summary: { type: "string" },
|
|
2023
|
-
goals: { type: "array", items: { type: "string" } },
|
|
2024
|
-
non_goals: { type: "array", items: { type: "string" } },
|
|
2025
|
-
acceptance_criteria: { type: "array", items: { type: "string" } },
|
|
2026
|
-
related_artifacts: { type: "array", items: { type: "string" } },
|
|
2027
|
-
tasks: { type: "array", items: taskItemSchema },
|
|
2028
|
-
allow_while_focused: { type: "boolean", description: "Explicitly allow a separate Issue while another Issue is the current execution focus." },
|
|
2029
|
-
allow_duplicate: { type: "boolean", description: "Explicitly allow an active Issue with the same normalized title." },
|
|
2030
|
-
allow_when_paused: { type: "boolean", description: "Explicitly override a paused Issue-creation policy." },
|
|
1295
|
+
session_id: { type: "string" },
|
|
1296
|
+
name: { type: "string" },
|
|
2031
1297
|
},
|
|
2032
|
-
required: ["
|
|
1298
|
+
required: ["session_id", "name"],
|
|
2033
1299
|
additionalProperties: false,
|
|
2034
1300
|
},
|
|
2035
1301
|
annotations: write,
|
|
2036
1302
|
},
|
|
2037
1303
|
{
|
|
2038
|
-
name: "
|
|
2039
|
-
description: "
|
|
1304
|
+
name: "verify_edit_session",
|
|
1305
|
+
description: "Verify the current edit revision through persisted Process Runtime checks. Retries with the same request_id reuse the original process and terminal receipts are attached to the exact revision.",
|
|
2040
1306
|
inputSchema: {
|
|
2041
1307
|
type: "object",
|
|
2042
1308
|
properties: {
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
enum: [
|
|
2049
|
-
"backlog",
|
|
2050
|
-
"analysis",
|
|
2051
|
-
"planned",
|
|
2052
|
-
"launch_blocked",
|
|
2053
|
-
"in_progress",
|
|
2054
|
-
"review",
|
|
2055
|
-
"done",
|
|
2056
|
-
"cancelled",
|
|
2057
|
-
],
|
|
2058
|
-
},
|
|
2059
|
-
goals: { type: "array", items: { type: "string" } },
|
|
2060
|
-
non_goals: { type: "array", items: { type: "string" } },
|
|
2061
|
-
acceptance_criteria: { type: "array", items: { type: "string" } },
|
|
2062
|
-
related_artifacts: { type: "array", items: { type: "string" } },
|
|
1309
|
+
session_id: { type: "string" },
|
|
1310
|
+
check_ids: { type: "array", items: { type: "string" } },
|
|
1311
|
+
request_id: { type: "string" },
|
|
1312
|
+
reviewer: { type: "string" },
|
|
1313
|
+
note: { type: "string" },
|
|
2063
1314
|
},
|
|
2064
|
-
required: ["
|
|
1315
|
+
required: ["session_id"],
|
|
2065
1316
|
additionalProperties: false,
|
|
2066
1317
|
},
|
|
2067
1318
|
annotations: write,
|
|
2068
1319
|
},
|
|
2069
1320
|
{
|
|
2070
|
-
name: "
|
|
1321
|
+
name: "rollback_edit_session",
|
|
2071
1322
|
description:
|
|
2072
|
-
"
|
|
1323
|
+
"Rollback an active edit session completely, to a prior revision, or to a named savepoint.",
|
|
2073
1324
|
inputSchema: {
|
|
2074
1325
|
type: "object",
|
|
2075
1326
|
properties: {
|
|
2076
|
-
|
|
2077
|
-
|
|
1327
|
+
session_id: { type: "string" },
|
|
1328
|
+
to_revision: { type: "number", description: "Keep revisions up to this value; omit for full rollback." },
|
|
1329
|
+
savepoint: { type: "string", description: "Rollback to a named savepoint." },
|
|
2078
1330
|
},
|
|
2079
|
-
required: ["
|
|
1331
|
+
required: ["session_id"],
|
|
2080
1332
|
additionalProperties: false,
|
|
2081
1333
|
},
|
|
2082
|
-
annotations:
|
|
1334
|
+
annotations: {
|
|
1335
|
+
readOnlyHint: false,
|
|
1336
|
+
openWorldHint: false,
|
|
1337
|
+
destructiveHint: false,
|
|
1338
|
+
},
|
|
2083
1339
|
},
|
|
2084
1340
|
{
|
|
2085
|
-
name: "
|
|
1341
|
+
name: "finalize_edit_session",
|
|
2086
1342
|
description:
|
|
2087
|
-
"
|
|
2088
|
-
inputSchema: {
|
|
2089
|
-
type: "object",
|
|
2090
|
-
properties: { issue_id: { type: "string" }, task: taskItemSchema },
|
|
2091
|
-
required: ["issue_id", "task"],
|
|
2092
|
-
additionalProperties: false,
|
|
2093
|
-
},
|
|
2094
|
-
annotations: write,
|
|
2095
|
-
},
|
|
2096
|
-
{
|
|
2097
|
-
name: "split_task",
|
|
2098
|
-
description:
|
|
2099
|
-
"Supersede one not-running Task with two or more smaller replacement Tasks while preserving downstream dependencies.",
|
|
2100
|
-
inputSchema: {
|
|
2101
|
-
type: "object",
|
|
2102
|
-
properties: {
|
|
2103
|
-
issue_id: { type: "string" },
|
|
2104
|
-
task_id: { type: "string" },
|
|
2105
|
-
tasks: { type: "array", minItems: 2, items: taskItemSchema },
|
|
2106
|
-
},
|
|
2107
|
-
required: ["issue_id", "task_id", "tasks"],
|
|
2108
|
-
additionalProperties: false,
|
|
2109
|
-
},
|
|
2110
|
-
annotations: write,
|
|
2111
|
-
},
|
|
2112
|
-
{
|
|
2113
|
-
name: "supersede_task",
|
|
2114
|
-
description:
|
|
2115
|
-
"Mark a Task as superseded and optionally link replacement Task IDs.",
|
|
2116
|
-
inputSchema: {
|
|
2117
|
-
type: "object",
|
|
2118
|
-
properties: {
|
|
2119
|
-
issue_id: { type: "string" },
|
|
2120
|
-
task_id: { type: "string" },
|
|
2121
|
-
replacement_task_ids: { type: "array", items: { type: "string" } },
|
|
2122
|
-
},
|
|
2123
|
-
required: ["issue_id", "task_id"],
|
|
2124
|
-
additionalProperties: false,
|
|
2125
|
-
},
|
|
2126
|
-
annotations: write,
|
|
2127
|
-
},
|
|
2128
|
-
{
|
|
2129
|
-
name: "set_task_dependencies",
|
|
2130
|
-
description:
|
|
2131
|
-
"Replace one Task dependency list and revalidate the Issue DAG.",
|
|
2132
|
-
inputSchema: {
|
|
2133
|
-
type: "object",
|
|
2134
|
-
properties: {
|
|
2135
|
-
issue_id: { type: "string" },
|
|
2136
|
-
task_id: { type: "string" },
|
|
2137
|
-
depends_on: { type: "array", items: { type: "string" } },
|
|
2138
|
-
},
|
|
2139
|
-
required: ["issue_id", "task_id", "depends_on"],
|
|
2140
|
-
additionalProperties: false,
|
|
2141
|
-
},
|
|
2142
|
-
annotations: write,
|
|
2143
|
-
},
|
|
2144
|
-
{
|
|
2145
|
-
name: "update_task",
|
|
2146
|
-
description: "Update one task status or append a controller note.",
|
|
1343
|
+
"Close an active edit session. Configured checks must pass; sessions without configured checks finalize directly.",
|
|
2147
1344
|
inputSchema: {
|
|
2148
1345
|
type: "object",
|
|
2149
1346
|
properties: {
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
status: {
|
|
2153
|
-
type: "string",
|
|
2154
|
-
enum: [
|
|
2155
|
-
"backlog",
|
|
2156
|
-
"analysis",
|
|
2157
|
-
"planned",
|
|
2158
|
-
"ready",
|
|
2159
|
-
"launch_blocked",
|
|
2160
|
-
"running",
|
|
2161
|
-
"blocked",
|
|
2162
|
-
"review",
|
|
2163
|
-
"integrated",
|
|
2164
|
-
"verifying",
|
|
2165
|
-
"changes_requested",
|
|
2166
|
-
"verified",
|
|
2167
|
-
"done",
|
|
2168
|
-
"cancelled",
|
|
2169
|
-
"superseded",
|
|
2170
|
-
],
|
|
2171
|
-
},
|
|
1347
|
+
session_id: { type: "string" },
|
|
1348
|
+
reviewer: { type: "string" },
|
|
2172
1349
|
note: { type: "string" },
|
|
2173
|
-
|
|
1350
|
+
confirm_authorization: { type: "boolean", description: "Required when post-diff assurance detects protected paths or high semantic risk." },
|
|
2174
1351
|
},
|
|
2175
|
-
required: ["
|
|
1352
|
+
required: ["session_id"],
|
|
2176
1353
|
additionalProperties: false,
|
|
2177
1354
|
},
|
|
2178
1355
|
annotations: write,
|
|
2179
1356
|
},
|
|
1357
|
+
);
|
|
1358
|
+
}
|
|
1359
|
+
|
|
1360
|
+
if (policy.execution.fixedWorkflowCheck) {
|
|
1361
|
+
tools.push({
|
|
1362
|
+
name: "run_workflow_check",
|
|
1363
|
+
description: "Run the fixed forge strict workflow check.",
|
|
1364
|
+
inputSchema: EMPTY_SCHEMA,
|
|
1365
|
+
annotations: write,
|
|
1366
|
+
});
|
|
1367
|
+
}
|
|
1368
|
+
if (opts.enableChatgptBrowser === true) {
|
|
1369
|
+
tools.push(
|
|
2180
1370
|
{
|
|
2181
|
-
name: "
|
|
2182
|
-
description:
|
|
2183
|
-
"Execute one Task with an agent selected at dispatch time. Task definitions remain executor-neutral; Codex, Claude, and GitHub Copilot are optional implementation tools.",
|
|
2184
|
-
inputSchema: {
|
|
2185
|
-
type: "object",
|
|
2186
|
-
properties: {
|
|
2187
|
-
issue_id: { type: "string" },
|
|
2188
|
-
task_id: { type: "string" },
|
|
2189
|
-
request_id: { type: "string" },
|
|
2190
|
-
agent: {
|
|
2191
|
-
type: "string",
|
|
2192
|
-
enum: ["codex", "claude", "github-copilot"],
|
|
2193
|
-
},
|
|
2194
|
-
timeout_ms: agentTimeoutSchema,
|
|
2195
|
-
isolate: {
|
|
2196
|
-
type: "boolean",
|
|
2197
|
-
description:
|
|
2198
|
-
"Optional. Omit for automatic placement: use the current workspace when no local Run is active and a worktree only for concurrency. false forces the current workspace and is rejected during concurrency; true forces a worktree.",
|
|
2199
|
-
},
|
|
2200
|
-
github_repo: { type: "string" },
|
|
2201
|
-
base_ref: { type: "string" },
|
|
2202
|
-
model: { type: "string" },
|
|
2203
|
-
create_pull_request: { type: "boolean" },
|
|
2204
|
-
approve_destructive: { type: "boolean", description: "Explicit same-request authorization for destructive or irreversible execution." },
|
|
2205
|
-
},
|
|
2206
|
-
required: ["issue_id", "task_id"],
|
|
2207
|
-
additionalProperties: false,
|
|
2208
|
-
},
|
|
2209
|
-
annotations: {
|
|
2210
|
-
readOnlyHint: false,
|
|
2211
|
-
openWorldHint: false,
|
|
2212
|
-
destructiveHint: false,
|
|
2213
|
-
},
|
|
2214
|
-
},
|
|
2215
|
-
{
|
|
2216
|
-
name: "launch_issue",
|
|
2217
|
-
description:
|
|
2218
|
-
"Execute independent Tasks from one Issue with an optional runtime agent override. Task definitions do not permanently bind an agent.",
|
|
2219
|
-
inputSchema: {
|
|
2220
|
-
type: "object",
|
|
2221
|
-
properties: {
|
|
2222
|
-
issue_id: { type: "string" },
|
|
2223
|
-
max_parallel: { type: "number" },
|
|
2224
|
-
request_id: { type: "string" },
|
|
2225
|
-
agent: { type: "string", enum: ["codex", "claude", "github-copilot"], description: "Optional runtime executor override for selected Tasks." },
|
|
2226
|
-
timeout_ms: agentTimeoutSchema,
|
|
2227
|
-
isolate: {
|
|
2228
|
-
type: "boolean",
|
|
2229
|
-
description:
|
|
2230
|
-
"Optional. Omit for automatic placement: use the current workspace when no local Run is active and a worktree only for concurrency. false forces the current workspace and is rejected during concurrency; true forces a worktree.",
|
|
2231
|
-
},
|
|
2232
|
-
github_repo: { type: "string" },
|
|
2233
|
-
base_ref: { type: "string" },
|
|
2234
|
-
model: { type: "string" },
|
|
2235
|
-
create_pull_request: { type: "boolean" },
|
|
2236
|
-
approve_destructive: { type: "boolean", description: "Explicit same-request authorization for destructive or irreversible execution." },
|
|
2237
|
-
},
|
|
2238
|
-
required: ["issue_id"],
|
|
2239
|
-
additionalProperties: false,
|
|
2240
|
-
},
|
|
2241
|
-
annotations: {
|
|
2242
|
-
readOnlyHint: false,
|
|
2243
|
-
openWorldHint: true,
|
|
2244
|
-
destructiveHint: false,
|
|
2245
|
-
},
|
|
2246
|
-
},
|
|
2247
|
-
{
|
|
2248
|
-
name: "dispatch_ready_tasks",
|
|
1371
|
+
name: "run_chatgpt_browser_consult",
|
|
2249
1372
|
description:
|
|
2250
|
-
"
|
|
2251
|
-
inputSchema:
|
|
2252
|
-
type: "object",
|
|
2253
|
-
properties: {
|
|
2254
|
-
issue_id: { type: "string" },
|
|
2255
|
-
max_parallel: { type: "number" },
|
|
2256
|
-
request_id: { type: "string" },
|
|
2257
|
-
agent: { type: "string", enum: ["codex", "claude", "github-copilot"], description: "Optional runtime executor override for selected Tasks." },
|
|
2258
|
-
timeout_ms: agentTimeoutSchema,
|
|
2259
|
-
isolate: {
|
|
2260
|
-
type: "boolean",
|
|
2261
|
-
description:
|
|
2262
|
-
"Optional. Omit for automatic placement: use the current workspace when no local Run is active and a worktree only for concurrency. false forces the current workspace and is rejected during concurrency; true forces a worktree.",
|
|
2263
|
-
},
|
|
2264
|
-
github_repo: { type: "string" },
|
|
2265
|
-
base_ref: { type: "string" },
|
|
2266
|
-
model: { type: "string" },
|
|
2267
|
-
create_pull_request: { type: "boolean" },
|
|
2268
|
-
approve_destructive: { type: "boolean", description: "Explicit same-request authorization for destructive or irreversible execution." },
|
|
2269
|
-
},
|
|
2270
|
-
additionalProperties: false,
|
|
2271
|
-
},
|
|
1373
|
+
"Run a local ChatGPT Web browser consult through forge. This may create a real ChatGPT Web conversation unless dryRun is true.",
|
|
1374
|
+
inputSchema: browserRunSchema,
|
|
2272
1375
|
annotations: {
|
|
2273
1376
|
readOnlyHint: false,
|
|
2274
1377
|
openWorldHint: true,
|
|
@@ -2276,503 +1379,54 @@ export function buildMcpToolDefinitions(
|
|
|
2276
1379
|
},
|
|
2277
1380
|
},
|
|
2278
1381
|
{
|
|
2279
|
-
name: "
|
|
2280
|
-
description:
|
|
2281
|
-
"Read one persistent agent run with current status, bounded output tails, and opt-in path details.",
|
|
2282
|
-
inputSchema: {
|
|
2283
|
-
type: "object",
|
|
2284
|
-
properties: {
|
|
2285
|
-
run_id: { type: "string" },
|
|
2286
|
-
include_paths: { type: "boolean", description: "Opt in to full local filesystem paths and PIDs." },
|
|
2287
|
-
include_output: { type: "boolean", description: "Return bounded stdout/stderr tails. Defaults to true." },
|
|
2288
|
-
tail_chars: { type: "number", description: "Per-stream character budget for the bounded tails; capped at 16384." },
|
|
2289
|
-
},
|
|
2290
|
-
required: ["run_id"],
|
|
2291
|
-
additionalProperties: false,
|
|
2292
|
-
},
|
|
2293
|
-
annotations: readOnly,
|
|
2294
|
-
},
|
|
2295
|
-
{
|
|
2296
|
-
name: "get_task_run_events",
|
|
1382
|
+
name: "read_chatgpt_browser_session",
|
|
2297
1383
|
description:
|
|
2298
|
-
"Read
|
|
2299
|
-
inputSchema:
|
|
2300
|
-
type: "object",
|
|
2301
|
-
properties: {
|
|
2302
|
-
run_id: { type: "string" },
|
|
2303
|
-
limit: { type: "number" },
|
|
2304
|
-
since_event_index: { type: "number" },
|
|
2305
|
-
include_heartbeats: { type: "boolean" },
|
|
2306
|
-
},
|
|
2307
|
-
required: ["run_id"],
|
|
2308
|
-
additionalProperties: false,
|
|
2309
|
-
},
|
|
1384
|
+
"Read a saved forge ChatGPT browser consult session.",
|
|
1385
|
+
inputSchema: browserSessionSchema,
|
|
2310
1386
|
annotations: readOnly,
|
|
2311
1387
|
},
|
|
2312
1388
|
{
|
|
2313
|
-
name: "
|
|
1389
|
+
name: "list_chatgpt_browser_sessions",
|
|
2314
1390
|
description:
|
|
2315
|
-
"
|
|
1391
|
+
"List saved forge ChatGPT browser consult sessions.",
|
|
2316
1392
|
inputSchema: {
|
|
2317
1393
|
type: "object",
|
|
2318
|
-
properties: {
|
|
2319
|
-
run_id: { type: "string" },
|
|
2320
|
-
max_bytes: { type: "number", description: "Bounded log tail size; capped at 1 MiB." },
|
|
2321
|
-
},
|
|
2322
|
-
required: ["run_id"],
|
|
1394
|
+
properties: { limit: { type: "number" } },
|
|
2323
1395
|
additionalProperties: false,
|
|
2324
1396
|
},
|
|
2325
1397
|
annotations: readOnly,
|
|
2326
1398
|
},
|
|
2327
1399
|
{
|
|
2328
|
-
name: "
|
|
1400
|
+
name: "open_chatgpt_browser_session",
|
|
2329
1401
|
description:
|
|
2330
|
-
"
|
|
2331
|
-
inputSchema:
|
|
2332
|
-
type: "object",
|
|
2333
|
-
properties: {
|
|
2334
|
-
run_id: { type: "string" },
|
|
2335
|
-
max_bytes: { type: "number" },
|
|
2336
|
-
},
|
|
2337
|
-
required: ["run_id"],
|
|
2338
|
-
additionalProperties: false,
|
|
2339
|
-
},
|
|
1402
|
+
"Return the ChatGPT conversation URL for a saved browser session. The MCP tool does not launch the local browser.",
|
|
1403
|
+
inputSchema: browserSessionSchema,
|
|
2340
1404
|
annotations: readOnly,
|
|
2341
1405
|
},
|
|
2342
1406
|
{
|
|
2343
|
-
name: "
|
|
2344
|
-
description:
|
|
2345
|
-
"Explicitly integrate one completed isolated Task Run into this dispatch checkout through a rollback-capable edit session.",
|
|
2346
|
-
inputSchema: {
|
|
2347
|
-
type: "object",
|
|
2348
|
-
properties: { run_id: { type: "string" } },
|
|
2349
|
-
required: ["run_id"],
|
|
2350
|
-
additionalProperties: false,
|
|
2351
|
-
},
|
|
2352
|
-
annotations: {
|
|
2353
|
-
readOnlyHint: false,
|
|
2354
|
-
openWorldHint: false,
|
|
2355
|
-
destructiveHint: true,
|
|
2356
|
-
},
|
|
2357
|
-
},
|
|
2358
|
-
{
|
|
2359
|
-
name: "finish_task_run",
|
|
1407
|
+
name: "continue_chatgpt_browser_session",
|
|
2360
1408
|
description:
|
|
2361
|
-
"
|
|
1409
|
+
"Create a follow-up ChatGPT browser consult record linked to an existing session.",
|
|
2362
1410
|
inputSchema: {
|
|
2363
1411
|
type: "object",
|
|
2364
1412
|
properties: {
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
enum: ["auto", "approve_and_finish", "request_changes", "discard"],
|
|
2369
|
-
},
|
|
2370
|
-
reviewer: { type: "string" },
|
|
2371
|
-
note: { type: "string" },
|
|
2372
|
-
cleanup: { type: "boolean" },
|
|
2373
|
-
commit: { type: "boolean", description: "Create a selected-path Git commit after successful finish. Refuses to mix with pre-staged changes." },
|
|
1413
|
+
sessionId: { type: "string" },
|
|
1414
|
+
prompt: { type: "string" },
|
|
1415
|
+
dryRun: { type: "boolean" },
|
|
2374
1416
|
},
|
|
2375
|
-
required: ["
|
|
1417
|
+
required: ["sessionId", "prompt"],
|
|
2376
1418
|
additionalProperties: false,
|
|
2377
1419
|
},
|
|
2378
1420
|
annotations: {
|
|
2379
1421
|
readOnlyHint: false,
|
|
2380
|
-
openWorldHint:
|
|
1422
|
+
openWorldHint: true,
|
|
2381
1423
|
destructiveHint: false,
|
|
2382
1424
|
},
|
|
2383
1425
|
},
|
|
2384
|
-
{
|
|
2385
|
-
name: "list_task_runs",
|
|
2386
|
-
description: "List recent persistent agent runs.",
|
|
2387
|
-
inputSchema: {
|
|
2388
|
-
type: "object",
|
|
2389
|
-
properties: {
|
|
2390
|
-
limit: { type: "number" },
|
|
2391
|
-
include_paths: { type: "boolean", description: "Opt in to full local filesystem paths and PIDs." },
|
|
2392
|
-
},
|
|
2393
|
-
additionalProperties: false,
|
|
2394
|
-
},
|
|
2395
|
-
annotations: readOnly,
|
|
2396
|
-
},
|
|
2397
|
-
{
|
|
2398
|
-
name: "cancel_task_run",
|
|
2399
|
-
description: "Cancel a queued or running local agent task.",
|
|
2400
|
-
inputSchema: {
|
|
2401
|
-
type: "object",
|
|
2402
|
-
properties: { run_id: { type: "string" } },
|
|
2403
|
-
required: ["run_id"],
|
|
2404
|
-
additionalProperties: false,
|
|
2405
|
-
},
|
|
2406
|
-
annotations: {
|
|
2407
|
-
readOnlyHint: false,
|
|
2408
|
-
openWorldHint: false,
|
|
2409
|
-
destructiveHint: true,
|
|
2410
|
-
},
|
|
2411
|
-
},
|
|
2412
|
-
{
|
|
2413
|
-
name: "retry_task_run",
|
|
2414
|
-
description:
|
|
2415
|
-
"Create a new run for a failed, blocked, cancelled, or change-requested task.",
|
|
2416
|
-
inputSchema: {
|
|
2417
|
-
type: "object",
|
|
2418
|
-
properties: {
|
|
2419
|
-
run_id: { type: "string" },
|
|
2420
|
-
timeout_ms: agentTimeoutSchema,
|
|
2421
|
-
isolate: {
|
|
2422
|
-
type: "boolean",
|
|
2423
|
-
description:
|
|
2424
|
-
"Optional. Omit for automatic placement: use the current workspace when no local Run is active and a worktree only for concurrency. false forces the current workspace and is rejected during concurrency; true forces a worktree.",
|
|
2425
|
-
},
|
|
2426
|
-
},
|
|
2427
|
-
required: ["run_id"],
|
|
2428
|
-
additionalProperties: false,
|
|
2429
|
-
},
|
|
2430
|
-
annotations: write,
|
|
2431
|
-
},
|
|
2432
|
-
{
|
|
2433
|
-
name: "verify_task",
|
|
2434
|
-
description:
|
|
2435
|
-
"Record task-local completion evidence. Declared checks execute through persisted Process Runtime receipts; retries reuse the same processes and reported command evidence can satisfy risk-adaptive verification.",
|
|
2436
|
-
inputSchema: {
|
|
2437
|
-
type: "object",
|
|
2438
|
-
properties: {
|
|
2439
|
-
issue_id: { type: "string" },
|
|
2440
|
-
task_id: { type: "string" },
|
|
2441
|
-
run_id: { type: "string" },
|
|
2442
|
-
integrated_revision: { type: "string" },
|
|
2443
|
-
reviewed_diff_hash: { type: "string" },
|
|
2444
|
-
reviewer: { type: "string" },
|
|
2445
|
-
request_id: { type: "string", description: "Stable idempotency key for Process Runtime checks; retries reuse the original processes." },
|
|
2446
|
-
check_results: {
|
|
2447
|
-
type: "array",
|
|
2448
|
-
items: {
|
|
2449
|
-
type: "object",
|
|
2450
|
-
properties: {
|
|
2451
|
-
check_id: { type: "string" },
|
|
2452
|
-
ok: { type: "boolean", description: "Deprecated compatibility field. The Controller executes the named check and uses the actual outcome." },
|
|
2453
|
-
summary: { type: "string" },
|
|
2454
|
-
},
|
|
2455
|
-
required: ["check_id"],
|
|
2456
|
-
additionalProperties: false,
|
|
2457
|
-
},
|
|
2458
|
-
},
|
|
2459
|
-
reported_commands: {
|
|
2460
|
-
type: "array",
|
|
2461
|
-
items: {
|
|
2462
|
-
type: "object",
|
|
2463
|
-
properties: {
|
|
2464
|
-
command: { type: "array", items: { type: "string" }, minItems: 1 },
|
|
2465
|
-
cwd: { type: "string" },
|
|
2466
|
-
ok: { type: "boolean" },
|
|
2467
|
-
exit_code: { type: "number" },
|
|
2468
|
-
stdout: { type: "string" },
|
|
2469
|
-
stderr: { type: "string" },
|
|
2470
|
-
artifact_path: { type: "string" },
|
|
2471
|
-
executed_at: { type: "string" },
|
|
2472
|
-
},
|
|
2473
|
-
required: ["command", "ok"],
|
|
2474
|
-
additionalProperties: false,
|
|
2475
|
-
},
|
|
2476
|
-
},
|
|
2477
|
-
acceptance_results: {
|
|
2478
|
-
type: "array",
|
|
2479
|
-
items: {
|
|
2480
|
-
type: "object",
|
|
2481
|
-
properties: {
|
|
2482
|
-
criterion: { type: "string" },
|
|
2483
|
-
ok: { type: "boolean" },
|
|
2484
|
-
evidence: { type: "string" },
|
|
2485
|
-
},
|
|
2486
|
-
required: ["criterion", "ok"],
|
|
2487
|
-
additionalProperties: false,
|
|
2488
|
-
},
|
|
2489
|
-
},
|
|
2490
|
-
},
|
|
2491
|
-
required: ["issue_id", "task_id", "reviewer"],
|
|
2492
|
-
additionalProperties: false,
|
|
2493
|
-
},
|
|
2494
|
-
annotations: write,
|
|
2495
|
-
},
|
|
2496
|
-
{
|
|
2497
|
-
name: "accept_task",
|
|
2498
|
-
description:
|
|
2499
|
-
"Accept a verified task, mark it done, and unlock dependent tasks.",
|
|
2500
|
-
inputSchema: {
|
|
2501
|
-
type: "object",
|
|
2502
|
-
properties: {
|
|
2503
|
-
issue_id: { type: "string" },
|
|
2504
|
-
task_id: { type: "string" },
|
|
2505
|
-
work_id: { type: "string" },
|
|
2506
|
-
note: { type: "string" },
|
|
2507
|
-
},
|
|
2508
|
-
required: ["issue_id", "task_id"],
|
|
2509
|
-
additionalProperties: false,
|
|
2510
|
-
},
|
|
2511
|
-
annotations: write,
|
|
2512
|
-
},
|
|
2513
|
-
{
|
|
2514
|
-
name: "request_task_changes",
|
|
2515
|
-
description:
|
|
2516
|
-
"Return a reviewed task for another focused execution run.",
|
|
2517
|
-
inputSchema: {
|
|
2518
|
-
type: "object",
|
|
2519
|
-
properties: {
|
|
2520
|
-
issue_id: { type: "string" },
|
|
2521
|
-
task_id: { type: "string" },
|
|
2522
|
-
note: { type: "string" },
|
|
2523
|
-
},
|
|
2524
|
-
required: ["issue_id", "task_id", "note"],
|
|
2525
|
-
additionalProperties: false,
|
|
2526
|
-
},
|
|
2527
|
-
annotations: write,
|
|
2528
|
-
},
|
|
2529
|
-
{
|
|
2530
|
-
name: "begin_edit_session",
|
|
2531
|
-
description:
|
|
2532
|
-
"Open a bounded direct-edit session tied to a purpose and optional Issue/Task.",
|
|
2533
|
-
inputSchema: {
|
|
2534
|
-
type: "object",
|
|
2535
|
-
properties: {
|
|
2536
|
-
purpose: { type: "string" },
|
|
2537
|
-
work_id: { type: "string", description: "Optional existing WorkContract. Omit to create lightweight direct-control Work lineage." },
|
|
2538
|
-
issue_id: { type: "string" },
|
|
2539
|
-
task_id: { type: "string" },
|
|
2540
|
-
allowed_paths: { type: "array", items: { type: "string" } },
|
|
2541
|
-
max_files: { type: "number" },
|
|
2542
|
-
max_changed_lines: { type: "number" },
|
|
2543
|
-
checks: { type: "array", items: { type: "string" }, description: "Named checks to run before finalization." },
|
|
2544
|
-
},
|
|
2545
|
-
required: ["purpose"],
|
|
2546
|
-
additionalProperties: false,
|
|
2547
|
-
},
|
|
2548
|
-
annotations: write,
|
|
2549
|
-
},
|
|
2550
|
-
{
|
|
2551
|
-
name: "apply_patch",
|
|
2552
|
-
description:
|
|
2553
|
-
"Append one bounded patch batch to an active edit session. Prefer batches of 100 operations or fewer and pass expected_revision to avoid stale revisions.",
|
|
2554
|
-
inputSchema: {
|
|
2555
|
-
type: "object",
|
|
2556
|
-
properties: {
|
|
2557
|
-
session_id: { type: "string" },
|
|
2558
|
-
expected_revision: { type: "number", description: "Optional current revision precondition for the edit session." },
|
|
2559
|
-
operations: {
|
|
2560
|
-
type: "array",
|
|
2561
|
-
items: EDIT_OPERATION_INPUT_SCHEMA,
|
|
2562
|
-
},
|
|
2563
|
-
},
|
|
2564
|
-
required: ["session_id", "operations"],
|
|
2565
|
-
additionalProperties: false,
|
|
2566
|
-
},
|
|
2567
|
-
annotations: {
|
|
2568
|
-
readOnlyHint: false,
|
|
2569
|
-
openWorldHint: false,
|
|
2570
|
-
destructiveHint: false,
|
|
2571
|
-
},
|
|
2572
|
-
},
|
|
2573
|
-
{
|
|
2574
|
-
name: "get_edit_session",
|
|
2575
|
-
description: "Read an edit session, revisions, savepoints, and operation manifest.",
|
|
2576
|
-
inputSchema: {
|
|
2577
|
-
type: "object",
|
|
2578
|
-
properties: { session_id: { type: "string" } },
|
|
2579
|
-
required: ["session_id"],
|
|
2580
|
-
additionalProperties: false,
|
|
2581
|
-
},
|
|
2582
|
-
annotations: readOnly,
|
|
2583
|
-
},
|
|
2584
|
-
{
|
|
2585
|
-
name: "list_edit_sessions",
|
|
2586
|
-
description: "List recent direct-edit sessions with revision, changed-file, and check summaries.",
|
|
2587
|
-
inputSchema: {
|
|
2588
|
-
type: "object",
|
|
2589
|
-
properties: { limit: { type: "number" } },
|
|
2590
|
-
additionalProperties: false,
|
|
2591
|
-
},
|
|
2592
|
-
annotations: readOnly,
|
|
2593
|
-
},
|
|
2594
|
-
{
|
|
2595
|
-
name: "get_edit_session_diff",
|
|
2596
|
-
description: "Read the current aggregate localized diff for one direct-edit session.",
|
|
2597
|
-
inputSchema: {
|
|
2598
|
-
type: "object",
|
|
2599
|
-
properties: { session_id: { type: "string" } },
|
|
2600
|
-
required: ["session_id"],
|
|
2601
|
-
additionalProperties: false,
|
|
2602
|
-
},
|
|
2603
|
-
annotations: readOnly,
|
|
2604
|
-
},
|
|
2605
|
-
{
|
|
2606
|
-
name: "create_edit_savepoint",
|
|
2607
|
-
description: "Name the current edit revision so later patch batches can be rolled back to it.",
|
|
2608
|
-
inputSchema: {
|
|
2609
|
-
type: "object",
|
|
2610
|
-
properties: {
|
|
2611
|
-
session_id: { type: "string" },
|
|
2612
|
-
name: { type: "string" },
|
|
2613
|
-
},
|
|
2614
|
-
required: ["session_id", "name"],
|
|
2615
|
-
additionalProperties: false,
|
|
2616
|
-
},
|
|
2617
|
-
annotations: write,
|
|
2618
|
-
},
|
|
2619
|
-
{
|
|
2620
|
-
name: "verify_edit_session",
|
|
2621
|
-
description: "Verify the current edit revision through persisted Process Runtime checks. Retries with the same request_id reuse the original process and terminal receipts are attached to the exact revision.",
|
|
2622
|
-
inputSchema: {
|
|
2623
|
-
type: "object",
|
|
2624
|
-
properties: {
|
|
2625
|
-
session_id: { type: "string" },
|
|
2626
|
-
check_ids: { type: "array", items: { type: "string" } },
|
|
2627
|
-
request_id: { type: "string" },
|
|
2628
|
-
reviewer: { type: "string" },
|
|
2629
|
-
note: { type: "string" },
|
|
2630
|
-
},
|
|
2631
|
-
required: ["session_id"],
|
|
2632
|
-
additionalProperties: false,
|
|
2633
|
-
},
|
|
2634
|
-
annotations: write,
|
|
2635
|
-
},
|
|
2636
|
-
{
|
|
2637
|
-
name: "rollback_edit_session",
|
|
2638
|
-
description:
|
|
2639
|
-
"Rollback an active edit session completely, to a prior revision, or to a named savepoint.",
|
|
2640
|
-
inputSchema: {
|
|
2641
|
-
type: "object",
|
|
2642
|
-
properties: {
|
|
2643
|
-
session_id: { type: "string" },
|
|
2644
|
-
to_revision: { type: "number", description: "Keep revisions up to this value; omit for full rollback." },
|
|
2645
|
-
savepoint: { type: "string", description: "Rollback to a named savepoint." },
|
|
2646
|
-
},
|
|
2647
|
-
required: ["session_id"],
|
|
2648
|
-
additionalProperties: false,
|
|
2649
|
-
},
|
|
2650
|
-
annotations: {
|
|
2651
|
-
readOnlyHint: false,
|
|
2652
|
-
openWorldHint: false,
|
|
2653
|
-
destructiveHint: false,
|
|
2654
|
-
},
|
|
2655
|
-
},
|
|
2656
|
-
{
|
|
2657
|
-
name: "finalize_edit_session",
|
|
2658
|
-
description:
|
|
2659
|
-
"Close an active edit session. Configured checks must pass; sessions without configured checks finalize directly.",
|
|
2660
|
-
inputSchema: {
|
|
2661
|
-
type: "object",
|
|
2662
|
-
properties: {
|
|
2663
|
-
session_id: { type: "string" },
|
|
2664
|
-
reviewer: { type: "string" },
|
|
2665
|
-
note: { type: "string" },
|
|
2666
|
-
confirm_authorization: { type: "boolean", description: "Required when post-diff assurance detects protected paths or high semantic risk." },
|
|
2667
|
-
},
|
|
2668
|
-
required: ["session_id"],
|
|
2669
|
-
additionalProperties: false,
|
|
2670
|
-
},
|
|
2671
|
-
annotations: write,
|
|
2672
|
-
},
|
|
2673
|
-
{
|
|
2674
|
-
name: "finish_edit_session",
|
|
2675
|
-
description:
|
|
2676
|
-
"Verify, finalize, selected-path commit, receipt, and complete the Task bound to one Direct Edit session.",
|
|
2677
|
-
inputSchema: {
|
|
2678
|
-
type: "object",
|
|
2679
|
-
properties: {
|
|
2680
|
-
session_id: { type: "string" },
|
|
2681
|
-
decision: { type: "string", enum: ["auto", "approve_and_finish", "request_changes", "discard"] },
|
|
2682
|
-
reviewer: { type: "string" },
|
|
2683
|
-
note: { type: "string" },
|
|
2684
|
-
check_ids: { type: "array", items: { type: "string" } },
|
|
2685
|
-
commit: { type: "boolean" },
|
|
2686
|
-
},
|
|
2687
|
-
required: ["session_id"],
|
|
2688
|
-
additionalProperties: false,
|
|
2689
|
-
},
|
|
2690
|
-
annotations: write,
|
|
2691
|
-
},
|
|
2692
1426
|
);
|
|
2693
1427
|
}
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
tools.push({
|
|
2697
|
-
name: "run_workflow_check",
|
|
2698
|
-
description: "Run the fixed forge strict workflow check.",
|
|
2699
|
-
inputSchema: EMPTY_SCHEMA,
|
|
2700
|
-
annotations: write,
|
|
2701
|
-
});
|
|
2702
|
-
}
|
|
2703
|
-
if (opts.enableChatgptBrowser === true) {
|
|
2704
|
-
tools.push(
|
|
2705
|
-
{
|
|
2706
|
-
name: "run_chatgpt_browser_consult",
|
|
2707
|
-
description:
|
|
2708
|
-
"Run a local ChatGPT Web browser consult through forge. This may create a real ChatGPT Web conversation unless dryRun is true.",
|
|
2709
|
-
inputSchema: browserRunSchema,
|
|
2710
|
-
annotations: {
|
|
2711
|
-
readOnlyHint: false,
|
|
2712
|
-
openWorldHint: true,
|
|
2713
|
-
destructiveHint: false,
|
|
2714
|
-
},
|
|
2715
|
-
},
|
|
2716
|
-
{
|
|
2717
|
-
name: "read_chatgpt_browser_session",
|
|
2718
|
-
description:
|
|
2719
|
-
"Read a saved forge ChatGPT browser consult session.",
|
|
2720
|
-
inputSchema: browserSessionSchema,
|
|
2721
|
-
annotations: readOnly,
|
|
2722
|
-
},
|
|
2723
|
-
{
|
|
2724
|
-
name: "list_chatgpt_browser_sessions",
|
|
2725
|
-
description:
|
|
2726
|
-
"List saved forge ChatGPT browser consult sessions.",
|
|
2727
|
-
inputSchema: {
|
|
2728
|
-
type: "object",
|
|
2729
|
-
properties: { limit: { type: "number" } },
|
|
2730
|
-
additionalProperties: false,
|
|
2731
|
-
},
|
|
2732
|
-
annotations: readOnly,
|
|
2733
|
-
},
|
|
2734
|
-
{
|
|
2735
|
-
name: "open_chatgpt_browser_session",
|
|
2736
|
-
description:
|
|
2737
|
-
"Return the ChatGPT conversation URL for a saved browser session. The MCP tool does not launch the local browser.",
|
|
2738
|
-
inputSchema: browserSessionSchema,
|
|
2739
|
-
annotations: readOnly,
|
|
2740
|
-
},
|
|
2741
|
-
{
|
|
2742
|
-
name: "continue_chatgpt_browser_session",
|
|
2743
|
-
description:
|
|
2744
|
-
"Create a follow-up ChatGPT browser consult record linked to an existing session.",
|
|
2745
|
-
inputSchema: {
|
|
2746
|
-
type: "object",
|
|
2747
|
-
properties: {
|
|
2748
|
-
sessionId: { type: "string" },
|
|
2749
|
-
prompt: { type: "string" },
|
|
2750
|
-
dryRun: { type: "boolean" },
|
|
2751
|
-
},
|
|
2752
|
-
required: ["sessionId", "prompt"],
|
|
2753
|
-
additionalProperties: false,
|
|
2754
|
-
},
|
|
2755
|
-
annotations: {
|
|
2756
|
-
readOnlyHint: false,
|
|
2757
|
-
openWorldHint: true,
|
|
2758
|
-
destructiveHint: false,
|
|
2759
|
-
},
|
|
2760
|
-
},
|
|
2761
|
-
);
|
|
2762
|
-
}
|
|
2763
|
-
return tools.filter((tool) => !RETIRED_LEGACY_PUBLIC_TOOLS.has(tool.name));
|
|
2764
|
-
}
|
|
2765
|
-
|
|
2766
|
-
export function controllerExpectedToolNames(
|
|
2767
|
-
policy: McpPolicy,
|
|
2768
|
-
opts: { enableChatgptBrowser?: boolean } = {},
|
|
2769
|
-
): string[] {
|
|
2770
|
-
const names = buildMcpToolDefinitions(policy, opts).map((tool) => tool.name);
|
|
2771
|
-
if (policy.profile !== "controller") return names;
|
|
2772
|
-
// The Connector readiness expectation must match the bounded default schema
|
|
2773
|
-
// served by toolset.ts. Full compatibility tools are intentionally omitted.
|
|
2774
|
-
return [...DEFAULT_CONTROLLER_TOOL_NAMES];
|
|
2775
|
-
}
|
|
1428
|
+
return tools.filter((tool) => !RETIRED_LEGACY_PUBLIC_TOOLS.has(tool.name));
|
|
1429
|
+
}
|
|
2776
1430
|
|
|
2777
1431
|
const RETIRED_LEGACY_PUBLIC_TOOLS = new Set([
|
|
2778
1432
|
"list_issues",
|
|
@@ -2830,1539 +1484,645 @@ export async function callMcpTool(
|
|
|
2830
1484
|
}
|
|
2831
1485
|
switch (name) {
|
|
2832
1486
|
case "harness_status": {
|
|
2833
|
-
const roots = [
|
|
2834
|
-
"docs/spec.md",
|
|
2835
|
-
"plans",
|
|
2836
|
-
"tasks/current.md",
|
|
2837
|
-
".ai/harness/session",
|
|
2838
|
-
".ai/harness/controller/packets",
|
|
2839
|
-
".ai/harness/projections",
|
|
2840
|
-
".ai/harness/checks",
|
|
2841
|
-
];
|
|
2842
|
-
audit(ctx, name, "ok", args);
|
|
2843
|
-
return textResult({
|
|
2844
|
-
repoRoot: ctx.repoRoot,
|
|
2845
|
-
adopted: isForgeAdopted(ctx.repoRoot),
|
|
2846
|
-
profile: ctx.policy.profile,
|
|
2847
|
-
branch: currentGitBranch(ctx.repoRoot),
|
|
2848
|
-
workflowRoots: roots.map((path) => ({
|
|
2849
|
-
path,
|
|
2850
|
-
exists: existsSync(join(ctx.repoRoot, path)),
|
|
2851
|
-
})),
|
|
2852
|
-
});
|
|
2853
|
-
}
|
|
2854
|
-
case "harness_doctor": {
|
|
2855
|
-
const localConfig = existsSync(
|
|
2856
|
-
join(ctx.repoRoot, ".forge", "mcp.local.json"),
|
|
2857
|
-
);
|
|
2858
|
-
const codexConfig = existsSync(
|
|
2859
|
-
join(ctx.repoRoot, ".codex", "config.toml"),
|
|
2860
|
-
);
|
|
2861
|
-
audit(ctx, name, "ok", args);
|
|
2862
|
-
return textResult({
|
|
2863
|
-
status: isForgeAdopted(ctx.repoRoot)
|
|
2864
|
-
? "ready_local"
|
|
2865
|
-
: "not_adopted",
|
|
2866
|
-
repo: ctx.repoRoot,
|
|
2867
|
-
profile: ctx.policy.profile,
|
|
2868
|
-
mcp: {
|
|
2869
|
-
localConfig,
|
|
2870
|
-
policy: "builtin",
|
|
2871
|
-
profile: ctx.policy.profile,
|
|
2872
|
-
toolSurface:
|
|
2873
|
-
ctx.policy.profile === "controller"
|
|
2874
|
-
? FORGE_TOOL_SURFACE
|
|
2875
|
-
: `${ctx.policy.profile}-legacy-v1`,
|
|
2876
|
-
toolCount: buildMcpToolDefinitions(ctx.policy, {
|
|
2877
|
-
enableChatgptBrowser: ctx.enableChatgptBrowser === true,
|
|
2878
|
-
}).length,
|
|
2879
|
-
repositoryRead:
|
|
2880
|
-
ctx.policy.profile === "controller"
|
|
2881
|
-
? "ordinary repository source and document files are readable; immutable secret and runtime paths remain denied"
|
|
2882
|
-
: "workflow-scoped",
|
|
2883
|
-
deniedPaths: ctx.policy.denyGlobs.length,
|
|
2884
|
-
runner: {
|
|
2885
|
-
enabled: ctx.policy.execution.agentRunner,
|
|
2886
|
-
defaultTimeoutMs: ctx.policy.execution.runnerTimeoutMs,
|
|
2887
|
-
maxTimeoutMs: ctx.policy.execution.runnerMaxTimeoutMs,
|
|
2888
|
-
},
|
|
2889
|
-
},
|
|
2890
|
-
codex: {
|
|
2891
|
-
configured: codexConfig,
|
|
2892
|
-
fix: codexConfig
|
|
2893
|
-
? null
|
|
2894
|
-
: "forge mcp setup codex --repo . --scope project",
|
|
2895
|
-
},
|
|
2896
|
-
chatgpt: {
|
|
2897
|
-
localEndpoint: "http://127.0.0.1:8765/mcp",
|
|
2898
|
-
manualStepsRequired: true,
|
|
2899
|
-
guide: "docs/forge-chatgpt-mcp-setup.md",
|
|
2900
|
-
},
|
|
2901
|
-
});
|
|
2902
|
-
}
|
|
2903
|
-
case "controller_capabilities": {
|
|
2904
|
-
if (ctx.policy.profile !== "controller")
|
|
2905
|
-
return errorResult(
|
|
2906
|
-
"TOOL_DISABLED",
|
|
2907
|
-
"controller_capabilities requires the controller profile",
|
|
2908
|
-
);
|
|
2909
|
-
const expectedTools = controllerExpectedToolNames(ctx.policy, {
|
|
2910
|
-
enableChatgptBrowser: ctx.enableChatgptBrowser === true,
|
|
2911
|
-
});
|
|
2912
|
-
const preferredTools: string[] = [...PREFERRED_FACADE_TOOL_NAMES];
|
|
2913
|
-
const capabilityDescriptors = listCapabilityDescriptors();
|
|
2914
|
-
const connectorSnapshot = Array.isArray(args.connector_tool_names)
|
|
2915
|
-
? args.connector_tool_names.map(String)
|
|
2916
|
-
: Array.isArray(args.connectorToolNames)
|
|
2917
|
-
? args.connectorToolNames.map(String)
|
|
2918
|
-
: undefined;
|
|
2919
|
-
const connectorFreshness = await buildLocalConnectorStatusForRepo({
|
|
2920
|
-
repoRoot: ctx.repoRoot,
|
|
2921
|
-
expectedTools,
|
|
2922
|
-
connectorToolNames: connectorSnapshot,
|
|
2923
|
-
});
|
|
2924
|
-
// staleConnector only when ChatGPT snapshot is supplied and missing rh_*.
|
|
2925
|
-
const missingFacadeTools = connectorFreshness.missingConnectorTools;
|
|
2926
|
-
const staleConnector = connectorFreshness.status === "chatgpt_snapshot_missing_facade";
|
|
2927
|
-
const payload = {
|
|
2928
|
-
schemaVersion: FORGE_MCP_SCHEMA_VERSION,
|
|
2929
|
-
toolSurface: FORGE_TOOL_SURFACE,
|
|
2930
|
-
version: FORGE_VERSION,
|
|
2931
|
-
toolSurfaceFingerprint: forgeToolSurfaceFingerprint(expectedTools),
|
|
2932
|
-
executionModel: "chatgpt-controller-execution-bridge",
|
|
2933
|
-
profile: ctx.policy.profile,
|
|
2934
|
-
capabilities: {
|
|
2935
|
-
repositoryInspection: true,
|
|
2936
|
-
issueLauncher: true,
|
|
2937
|
-
githubIssuesAndProjects: true,
|
|
2938
|
-
localAgentRuns: ctx.policy.execution.agentRunner,
|
|
2939
|
-
githubCopilotSessions: true,
|
|
2940
|
-
dynamicTaskGraph: true,
|
|
2941
|
-
runtimeAgentSelection: true,
|
|
2942
|
-
taskAgentBinding: false,
|
|
2943
|
-
structuredRunEvents: true,
|
|
2944
|
-
boundedDirectEdits: true,
|
|
2945
|
-
multiRevisionDirectEdits: true,
|
|
2946
|
-
editSavepoints: true,
|
|
2947
|
-
partialEditRollback: true,
|
|
2948
|
-
directEditFirstRouting: true,
|
|
2949
|
-
persistedEditDiffs: true,
|
|
2950
|
-
verifiedEditFinalization: true,
|
|
2951
|
-
verificationGate: true,
|
|
2952
|
-
localRunWatch: true,
|
|
2953
|
-
localVisualController: true,
|
|
2954
|
-
hierarchicalControllerUI: true,
|
|
2955
|
-
localApprovalQueue: false,
|
|
2956
|
-
localRiskApprovalGate: false,
|
|
2957
|
-
quickAgentSessions: true,
|
|
2958
|
-
persistentTimeouts: true,
|
|
2959
|
-
timeoutProgress: true,
|
|
2960
|
-
connectorSchemaDiagnostics: true,
|
|
2961
|
-
projectProgressAggregation: true,
|
|
2962
|
-
effectiveTaskStatus: true,
|
|
2963
|
-
unifiedWorklogTimeline: true,
|
|
2964
|
-
exportableWorklogReports: true,
|
|
2965
|
-
githubPluginConfiguration: true,
|
|
2966
|
-
serverSentEventDashboard: true,
|
|
2967
|
-
singleExecutionFocus: false,
|
|
2968
|
-
taskLocalReadiness: true,
|
|
2969
|
-
riskAdaptiveVerification: true,
|
|
2970
|
-
ephemeralQuickAgentSessions: true,
|
|
2971
|
-
automaticRunContinuation: true,
|
|
2972
|
-
reportedCommandEvidence: true,
|
|
2973
|
-
governanceDiagnostics: true,
|
|
2974
|
-
safeGovernanceReconciliation: true,
|
|
2975
|
-
evidenceGateProgress: true,
|
|
2976
|
-
directTaskActions: true,
|
|
2977
|
-
issueArchiving: true,
|
|
2978
|
-
retryableRunFailures: true,
|
|
2979
|
-
issueCreationGuardrails: true,
|
|
2980
|
-
persistedCheckEvidence: true,
|
|
2981
|
-
persistedCheckReuse: true,
|
|
2982
|
-
directVerification: true,
|
|
2983
|
-
controllerContextAggregation: true,
|
|
2984
|
-
taskLedgerProjection: true,
|
|
2985
|
-
assistantPluginRuntime: true,
|
|
2986
|
-
chatgptFacadeTools: true,
|
|
2987
|
-
goalWorkloop: true,
|
|
2988
|
-
handoffInbox: true,
|
|
2989
|
-
codexGrokDelegation: true,
|
|
2990
|
-
selfHealingRepair: true,
|
|
2991
|
-
},
|
|
2992
|
-
runner: {
|
|
2993
|
-
enabled: ctx.policy.execution.agentRunner,
|
|
2994
|
-
allowedAgents: ctx.policy.execution.allowedAgents,
|
|
2995
|
-
defaultTimeoutMs: ctx.policy.execution.runnerTimeoutMs,
|
|
2996
|
-
defaultTimeout: formatDurationMs(
|
|
2997
|
-
ctx.policy.execution.runnerTimeoutMs,
|
|
2998
|
-
),
|
|
2999
|
-
maxTimeoutMs: ctx.policy.execution.runnerMaxTimeoutMs,
|
|
3000
|
-
maxTimeout: formatDurationMs(
|
|
3001
|
-
ctx.policy.execution.runnerMaxTimeoutMs,
|
|
3002
|
-
),
|
|
3003
|
-
note: "Requested timeout values are validated explicitly and are never silently reduced to the default.",
|
|
3004
|
-
},
|
|
3005
|
-
preferredTools,
|
|
3006
|
-
capabilityGroups: summarizeCapabilityGroups(),
|
|
3007
|
-
toolArchitecture: {
|
|
3008
|
-
facadeTools: preferredTools,
|
|
3009
|
-
atomicTypedToolsRetained: true,
|
|
3010
|
-
internalHandlersRetained: true,
|
|
3011
|
-
domainSchemaLoading: 'static_stable_surface',
|
|
3012
|
-
dynamicDomainSchemaLoadingSupported: false,
|
|
3013
|
-
capabilityDescriptorCount: capabilityDescriptors.length,
|
|
3014
|
-
},
|
|
3015
|
-
toolExposure: {
|
|
3016
|
-
preferred: preferredTools,
|
|
3017
|
-
note: "Prefer the five rh_* workflow facades for orchestration. Typed repository, Git, Issue/Task, plugin, browser, iOS, artifact, and recovery tools remain available because the current MCP transport cannot load domain schemas per conversation.",
|
|
3018
|
-
advanced: expectedTools.filter((tool) => !preferredTools.includes(tool)).slice(0, 40),
|
|
3019
|
-
compatibilityRetained: true,
|
|
3020
|
-
},
|
|
3021
|
-
expectedTools,
|
|
3022
|
-
facadeToolsPresentInExpectedTools: preferredTools.every((tool: string) => expectedTools.includes(tool)),
|
|
3023
|
-
staleConnector,
|
|
3024
|
-
missingFacadeTools,
|
|
3025
|
-
connectorFreshness: {
|
|
3026
|
-
status: connectorFreshness.status,
|
|
3027
|
-
severity: connectorFreshness.severity,
|
|
3028
|
-
summary: connectorFreshness.summary,
|
|
3029
|
-
missingLocalTools: connectorFreshness.missingLocalTools,
|
|
3030
|
-
missingConnectorTools: connectorFreshness.missingConnectorTools,
|
|
3031
|
-
expectedFacadeTools: connectorFreshness.expectedFacadeTools,
|
|
3032
|
-
restartRecommended: connectorFreshness.restartRecommended,
|
|
3033
|
-
reconnectRecommended: connectorFreshness.reconnectRecommended,
|
|
3034
|
-
suggestedActions: connectorFreshness.suggestedActions,
|
|
3035
|
-
},
|
|
3036
|
-
docs: [
|
|
3037
|
-
"docs/architecture/current/README.md",
|
|
3038
|
-
"docs/architecture/history.md",
|
|
3039
|
-
"docs/operations/controller-reliability-runbook.md",
|
|
3040
|
-
"docs/forge-github-issue-launcher.md",
|
|
3041
|
-
"docs/forge-chatgpt-mcp-setup.md",
|
|
3042
|
-
],
|
|
3043
|
-
staleConnectorHint: staleConnector
|
|
3044
|
-
? `Stale ChatGPT connector tool snapshot is missing facade tools: ${missingFacadeTools.join(", ")}. Refresh/reconnect MCP so tools/list reloads rh_status, rh_inbox, rh_context, and rh_work.`
|
|
3045
|
-
: connectorFreshness.status === "unable_to_verify_chatgpt_snapshot"
|
|
3046
|
-
? `Local MCP tool surface includes preferred facade tools. ChatGPT connector snapshot was not supplied, so freshness cannot be verified from this call. If ChatGPT tools/list is missing rh_status/rh_inbox/rh_context/rh_work, reconnect MCP. Pass connector_tool_names for an exact check.`
|
|
3047
|
-
: connectorFreshness.status === "stale_fingerprint"
|
|
3048
|
-
? `Local MCP process fingerprint/version may be stale. Restart controller/MCP, then reconnect ChatGPT if rh_* are still missing.`
|
|
3049
|
-
: connectorFreshness.status === "local_mcp_missing_facade"
|
|
3050
|
-
? `Local MCP tool surface is missing facade tools: ${connectorFreshness.missingLocalTools.join(", ")}. Restart controller/MCP.`
|
|
3051
|
-
: `Connector clients must match ${FORGE_TOOL_SURFACE} version ${FORGE_VERSION} / schema ${FORGE_MCP_SCHEMA_VERSION}. Prefer rh_status/rh_inbox/rh_context/rh_work. Pass connector_tool_names to detect a stale ChatGPT tool snapshot precisely.`,
|
|
3052
|
-
};
|
|
3053
|
-
audit(ctx, name, "ok", args);
|
|
3054
|
-
return textResult(payload);
|
|
3055
|
-
}
|
|
3056
|
-
case "local_bridge_status": {
|
|
3057
|
-
if (ctx.policy.profile !== "controller")
|
|
3058
|
-
return errorResult(
|
|
3059
|
-
"TOOL_DISABLED",
|
|
3060
|
-
"local_bridge_status requires the controller profile",
|
|
3061
|
-
);
|
|
3062
|
-
const detailLevel = args.detail_level === "detail" || args.detail === true
|
|
3063
|
-
? "detail"
|
|
3064
|
-
: "summary";
|
|
3065
|
-
const controllerHome = resolveRepoPreferredControllerHome(ctx.repoRoot);
|
|
3066
|
-
const surface = resolveLocalBridgeSurface({
|
|
3067
|
-
controllerHome,
|
|
3068
|
-
repoRoot: ctx.repoRoot,
|
|
3069
|
-
allowProcessScan: detailLevel === "detail",
|
|
3070
|
-
});
|
|
3071
|
-
const jobs = listLocalBridgeJobs(ctx.repoRoot, detailLevel === "detail" ? 12 : 20);
|
|
3072
|
-
const { activeJobCount, recentJobSummary } = summarizeRecentJobs(jobs);
|
|
3073
|
-
// Prefer authoritative surface; never invent a legacy 8766 endpoint.
|
|
3074
|
-
const endpoint = surface.endpoint ?? null;
|
|
3075
|
-
const running = surface.enabled
|
|
3076
|
-
&& (surface.processRunning === true
|
|
3077
|
-
|| (surface.endpointConfigured && surface.mode === "embedded"));
|
|
3078
|
-
if (detailLevel === "summary") {
|
|
3079
|
-
const payload = {
|
|
3080
|
-
detailLevel: "summary",
|
|
3081
|
-
repoId: ctx.repoId,
|
|
3082
|
-
running,
|
|
3083
|
-
ready: surface.enabled ? (surface.processRunning !== false) : true,
|
|
3084
|
-
health: surface.enabled
|
|
3085
|
-
? (surface.processRunning === true || surface.mode === "embedded" ? "ready" : "warning")
|
|
3086
|
-
: "disabled",
|
|
3087
|
-
mode: surface.mode,
|
|
3088
|
-
endpoint,
|
|
3089
|
-
endpointConfigured: surface.endpointConfigured,
|
|
3090
|
-
endpointReachable: null as boolean | null,
|
|
3091
|
-
processRunning: surface.processRunning ?? null,
|
|
3092
|
-
expectedSurface: surface.expectedSurface,
|
|
3093
|
-
requiredForReadiness: surface.requiredForReadiness,
|
|
3094
|
-
warnings: surface.error && surface.requiredForReadiness
|
|
3095
|
-
? [{ code: "LOCAL_BRIDGE_ERROR", message: surface.error.slice(0, 300) }]
|
|
3096
|
-
: [],
|
|
3097
|
-
activeJobCount,
|
|
3098
|
-
recentJobSummary,
|
|
3099
|
-
statusSource: surface.source,
|
|
3100
|
-
nonBlocking: !surface.requiredForReadiness,
|
|
3101
|
-
approvalQueue: false,
|
|
3102
|
-
};
|
|
3103
|
-
audit(ctx, name, "ok", args);
|
|
3104
|
-
return textResult(payload);
|
|
3105
|
-
}
|
|
3106
|
-
const reconciliation = reconcileLocalBridgeJobs(ctx.repoRoot);
|
|
3107
|
-
const payload = {
|
|
3108
|
-
detailLevel: "detail",
|
|
3109
|
-
endpoint,
|
|
3110
|
-
endpointConfigured: surface.endpointConfigured,
|
|
3111
|
-
running,
|
|
3112
|
-
error: surface.error,
|
|
3113
|
-
mode: surface.mode,
|
|
3114
|
-
requiredForReadiness: surface.requiredForReadiness,
|
|
3115
|
-
statusSource: surface.source,
|
|
3116
|
-
counts: recentJobSummary,
|
|
3117
|
-
activeJobCount,
|
|
3118
|
-
recentJobSummary,
|
|
3119
|
-
approvalQueue: false,
|
|
3120
|
-
reconciliation,
|
|
3121
|
-
recentJobs: jobs.map((job) => ({
|
|
3122
|
-
jobId: job.jobId,
|
|
3123
|
-
action: job.action,
|
|
3124
|
-
status: job.status,
|
|
3125
|
-
checkId: job.action === "run-check" ? (job.payload as { checkId?: string }).checkId : undefined,
|
|
3126
|
-
runId: job.runId,
|
|
3127
|
-
issueId: job.issueId,
|
|
3128
|
-
taskId: job.taskId,
|
|
3129
|
-
createdAt: job.createdAt,
|
|
3130
|
-
updatedAt: job.updatedAt,
|
|
3131
|
-
finishedAt: job.finishedAt,
|
|
3132
|
-
revision: job.revision,
|
|
3133
|
-
deadlineAt: job.deadlineAt,
|
|
3134
|
-
error: job.error?.slice(0, 300),
|
|
3135
|
-
})),
|
|
3136
|
-
nonBlocking: !surface.requiredForReadiness,
|
|
3137
|
-
fallback:
|
|
3138
|
-
"Open the localhost Local Controller to launch work or inspect execution when a ChatGPT write action is unavailable.",
|
|
3139
|
-
};
|
|
3140
|
-
audit(ctx, name, "ok", args);
|
|
3141
|
-
return textResult(payload);
|
|
3142
|
-
}
|
|
3143
|
-
case "controller_context": {
|
|
3144
|
-
if (ctx.policy.profile !== "controller")
|
|
3145
|
-
return errorResult(
|
|
3146
|
-
"TOOL_DISABLED",
|
|
3147
|
-
"controller_context requires the controller profile",
|
|
3148
|
-
);
|
|
3149
|
-
const controllerHome = resolveRepoPreferredControllerHome(ctx.repoRoot);
|
|
3150
|
-
const requirementBoard = buildRequirementBoard({ controllerHome, repoId: ctx.repoId });
|
|
3151
|
-
const executionDiagnostics = buildExecutionDiagnostics({
|
|
3152
|
-
controllerHome,
|
|
3153
|
-
repoId: ctx.repoId,
|
|
3154
|
-
detailLevel: "detail",
|
|
3155
|
-
});
|
|
3156
|
-
const runs = listAgentJobs(ctx.repoRoot, 8);
|
|
3157
|
-
const reconciliation = reconcileLocalBridgeJobs(ctx.repoRoot);
|
|
3158
|
-
const jobs = listLocalBridgeJobs(ctx.repoRoot, 8);
|
|
3159
|
-
const checks = listControllerChecks(ctx.repoRoot).map((check) => ({
|
|
3160
|
-
id: check.id,
|
|
3161
|
-
description: check.description,
|
|
3162
|
-
timeoutMs: check.timeoutMs,
|
|
3163
|
-
source: check.source,
|
|
3164
|
-
}));
|
|
3165
|
-
const payload = {
|
|
3166
|
-
git: gitSnapshot(ctx.repoRoot),
|
|
3167
|
-
requirementBoard,
|
|
3168
|
-
executionDiagnostics,
|
|
3169
|
-
legacyCompatibility: {
|
|
3170
|
-
currentIssue: "deprecated_frozen",
|
|
3171
|
-
issueTaskBoard: "deprecated_frozen",
|
|
3172
|
-
authority: "controller-home-sqlite",
|
|
3173
|
-
},
|
|
3174
|
-
activeRuns: runs.map((run) => ({
|
|
3175
|
-
runId: run.runId,
|
|
3176
|
-
issueId: run.issueId,
|
|
3177
|
-
taskId: run.taskId,
|
|
3178
|
-
status: run.status,
|
|
3179
|
-
agent: run.agent,
|
|
3180
|
-
provider: run.provider,
|
|
3181
|
-
executionMode: run.executionMode,
|
|
3182
|
-
progress: run.progress,
|
|
3183
|
-
lastHeartbeatAt: run.lastHeartbeatAt,
|
|
3184
|
-
error: run.error?.slice(0, 300),
|
|
3185
|
-
})),
|
|
3186
|
-
localBridge: {
|
|
3187
|
-
reconciliation,
|
|
3188
|
-
recentJobs: jobs.map((job) => ({
|
|
3189
|
-
jobId: job.jobId,
|
|
3190
|
-
action: job.action,
|
|
3191
|
-
status: job.status,
|
|
3192
|
-
checkId: job.action === "run-check" ? (job.payload as { checkId?: string }).checkId : undefined,
|
|
3193
|
-
issueId: job.issueId,
|
|
3194
|
-
taskId: job.taskId,
|
|
3195
|
-
updatedAt: job.updatedAt,
|
|
3196
|
-
})),
|
|
3197
|
-
},
|
|
3198
|
-
checks,
|
|
3199
|
-
};
|
|
3200
|
-
audit(ctx, name, "ok", args);
|
|
3201
|
-
return textResult(payload);
|
|
3202
|
-
}
|
|
3203
|
-
case "quick_agent_session":
|
|
3204
|
-
case "submit_local_job": {
|
|
3205
|
-
if (ctx.policy.profile !== "controller") {
|
|
3206
|
-
return errorResult("TOOL_DISABLED", `${name} requires the controller profile`);
|
|
3207
|
-
}
|
|
3208
|
-
audit(ctx, name, "blocked", args);
|
|
3209
|
-
return errorResult(
|
|
3210
|
-
name === "quick_agent_session" ? "AGENT_RUN_DEPRECATED" : "LOCAL_BRIDGE_JOB_RETIRED",
|
|
3211
|
-
"Kernel-managed Agent and Local Bridge Jobs are retired. Create or claim WorkContract and use Process Runtime or the Thin Launcher.",
|
|
3212
|
-
);
|
|
3213
|
-
}
|
|
3214
|
-
case "list_local_jobs": {
|
|
3215
|
-
if (ctx.policy.profile !== "controller")
|
|
3216
|
-
return errorResult(
|
|
3217
|
-
"TOOL_DISABLED",
|
|
3218
|
-
"list_local_jobs requires the controller profile",
|
|
3219
|
-
);
|
|
3220
|
-
const limit = typeof args.limit === "number" ? args.limit : 50;
|
|
3221
|
-
const jobs = listLocalBridgeJobs(ctx.repoRoot, limit);
|
|
3222
|
-
audit(ctx, name, "ok", args);
|
|
3223
|
-
return textResult({ jobs });
|
|
3224
|
-
}
|
|
3225
|
-
case "get_local_job": {
|
|
3226
|
-
if (ctx.policy.profile !== "controller")
|
|
3227
|
-
return errorResult(
|
|
3228
|
-
"TOOL_DISABLED",
|
|
3229
|
-
"get_local_job requires the controller profile",
|
|
3230
|
-
);
|
|
3231
|
-
const jobId = String(args.job_id ?? "").trim();
|
|
3232
|
-
const includeEvents = args.include_events === true;
|
|
3233
|
-
const includeOutput = args.include_output === true;
|
|
3234
|
-
const snapshot = getLocalBridgeJobSnapshot(ctx.repoRoot, jobId);
|
|
3235
|
-
const payload = {
|
|
3236
|
-
job: snapshot.job,
|
|
3237
|
-
...(snapshot.status !== "ok" ? { lookup: snapshot } : {}),
|
|
3238
|
-
...(includeEvents && snapshot.status === "ok" ? { events: getLocalBridgeJobEvents(ctx.repoRoot, jobId) } : {}),
|
|
3239
|
-
...(includeOutput
|
|
3240
|
-
? {
|
|
3241
|
-
output: readLocalBridgeJobOutput(ctx.repoRoot, jobId, {
|
|
3242
|
-
stream: args.stream === "stderr" ? "stderr" : "stdout",
|
|
3243
|
-
maxBytes: typeof args.max_bytes === "number" ? args.max_bytes : undefined,
|
|
3244
|
-
}),
|
|
3245
|
-
}
|
|
3246
|
-
: {}),
|
|
3247
|
-
};
|
|
3248
|
-
audit(ctx, name, "ok", args);
|
|
3249
|
-
return textResult(payload);
|
|
3250
|
-
}
|
|
3251
|
-
case "get_local_job_output": {
|
|
3252
|
-
if (ctx.policy.profile !== "controller")
|
|
3253
|
-
return errorResult(
|
|
3254
|
-
"TOOL_DISABLED",
|
|
3255
|
-
"get_local_job_output requires the controller profile",
|
|
3256
|
-
);
|
|
3257
|
-
const jobId = String(args.job_id ?? "").trim();
|
|
3258
|
-
const payload = readLocalBridgeJobOutput(ctx.repoRoot, jobId, {
|
|
3259
|
-
stream: args.stream === "stderr" ? "stderr" : "stdout",
|
|
3260
|
-
maxBytes: typeof args.max_bytes === "number" ? args.max_bytes : undefined,
|
|
3261
|
-
});
|
|
3262
|
-
audit(ctx, name, "ok", args);
|
|
3263
|
-
return textResult(payload);
|
|
3264
|
-
}
|
|
3265
|
-
case "project_snapshot": {
|
|
3266
|
-
if (ctx.policy.profile !== "controller") return errorResult("TOOL_DISABLED", "project_snapshot requires the controller profile");
|
|
3267
|
-
const markerPaths = [
|
|
3268
|
-
".ai/harness/active-plan",
|
|
3269
|
-
".ai/harness/active-sprint",
|
|
3270
|
-
".ai/harness/active-worktree",
|
|
3271
|
-
"tasks/current.md",
|
|
3272
|
-
];
|
|
3273
|
-
const markers = markerPaths.map((path) => {
|
|
3274
|
-
const absolute = join(ctx.repoRoot, path);
|
|
3275
|
-
if (!existsSync(absolute)) return { path, exists: false };
|
|
3276
|
-
const raw = readFileSync(absolute, "utf-8");
|
|
3277
|
-
return {
|
|
3278
|
-
path,
|
|
3279
|
-
exists: true,
|
|
3280
|
-
bytes: Buffer.byteLength(raw),
|
|
3281
|
-
lines: raw.split(/\r?\n/).length,
|
|
3282
|
-
sha256: createHash("sha256").update(raw).digest("hex").slice(0, 16),
|
|
3283
|
-
preview: redactMcpText(raw).text.slice(0, 400),
|
|
3284
|
-
};
|
|
3285
|
-
});
|
|
3286
|
-
const controllerHome = resolveRepoPreferredControllerHome(ctx.repoRoot);
|
|
3287
|
-
const requirementBoard = buildRequirementBoard({ controllerHome, repoId: ctx.repoId });
|
|
3288
|
-
const executionDiagnostics = buildExecutionDiagnostics({
|
|
3289
|
-
controllerHome,
|
|
3290
|
-
repoId: ctx.repoId,
|
|
3291
|
-
detailLevel: "detail",
|
|
3292
|
-
});
|
|
3293
|
-
const snapshotRuns = listAgentJobs(ctx.repoRoot, 11);
|
|
3294
|
-
const runs = snapshotRuns.slice(0, 10).map((run) => ({
|
|
3295
|
-
runId: run.runId,
|
|
3296
|
-
issueId: run.issueId,
|
|
3297
|
-
taskId: run.taskId,
|
|
3298
|
-
agent: run.agent,
|
|
3299
|
-
provider: run.provider,
|
|
3300
|
-
executionMode: run.executionMode,
|
|
3301
|
-
status: run.status,
|
|
3302
|
-
progress: run.progress,
|
|
3303
|
-
createdAt: run.createdAt,
|
|
3304
|
-
finishedAt: run.finishedAt,
|
|
3305
|
-
error: run.error?.slice(0, 300),
|
|
3306
|
-
}));
|
|
3307
|
-
const payload = {
|
|
3308
|
-
git: gitSnapshot(ctx.repoRoot),
|
|
3309
|
-
requirementBoard,
|
|
3310
|
-
executionDiagnostics,
|
|
3311
|
-
legacyCompatibility: {
|
|
3312
|
-
currentIssue: "deprecated_frozen",
|
|
3313
|
-
issueTaskBoard: "deprecated_frozen",
|
|
3314
|
-
authority: "controller-home-sqlite",
|
|
3315
|
-
},
|
|
3316
|
-
runs,
|
|
3317
|
-
markers,
|
|
3318
|
-
truncated: {
|
|
3319
|
-
runs: snapshotRuns.length > 10,
|
|
3320
|
-
},
|
|
3321
|
-
};
|
|
3322
|
-
audit(ctx, name, "ok", args);
|
|
3323
|
-
return textResult(payload);
|
|
3324
|
-
}
|
|
3325
|
-
case "search_repository": {
|
|
3326
|
-
if (ctx.policy.profile !== "controller")
|
|
3327
|
-
return errorResult(
|
|
3328
|
-
"TOOL_DISABLED",
|
|
3329
|
-
"search_repository requires the controller profile",
|
|
3330
|
-
);
|
|
3331
|
-
const session = ctx.sessionId && ctx.repoId
|
|
3332
|
-
? {
|
|
3333
|
-
sessionId: ctx.sessionId,
|
|
3334
|
-
repoId: ctx.repoId,
|
|
3335
|
-
checkoutId: ctx.checkoutId ?? ctx.repoId,
|
|
3336
|
-
}
|
|
3337
|
-
: undefined;
|
|
3338
|
-
const result = searchRepository(ctx.repoRoot, ctx.policy, {
|
|
3339
|
-
query: String(args.query ?? ""),
|
|
3340
|
-
includeGlobs: stringList(args.include_globs),
|
|
3341
|
-
excludeGlobs: stringList(args.exclude_globs),
|
|
3342
|
-
maxResults:
|
|
3343
|
-
typeof args.max_results === "number" ? args.max_results : undefined,
|
|
3344
|
-
maxFiles:
|
|
3345
|
-
typeof args.max_files === "number" ? args.max_files : undefined,
|
|
3346
|
-
caseSensitive: args.case_sensitive === true,
|
|
3347
|
-
session,
|
|
3348
|
-
});
|
|
3349
|
-
audit(ctx, name, "ok", args);
|
|
3350
|
-
return textResult(result);
|
|
3351
|
-
}
|
|
3352
|
-
case "get_git_diff": {
|
|
3353
|
-
if (ctx.policy.profile !== "controller")
|
|
3354
|
-
return errorResult(
|
|
3355
|
-
"TOOL_DISABLED",
|
|
3356
|
-
"get_git_diff requires the controller profile",
|
|
3357
|
-
);
|
|
3358
|
-
const path =
|
|
3359
|
-
typeof args.path === "string" && args.path.trim()
|
|
3360
|
-
? args.path.trim()
|
|
3361
|
-
: undefined;
|
|
3362
|
-
if (path) {
|
|
3363
|
-
const decision = resolveMcpPath(
|
|
3364
|
-
ctx.repoRoot,
|
|
3365
|
-
path,
|
|
3366
|
-
ctx.policy,
|
|
3367
|
-
"read",
|
|
3368
|
-
);
|
|
3369
|
-
if (!decision.ok)
|
|
3370
|
-
return errorResult(
|
|
3371
|
-
"POLICY_DENIED",
|
|
3372
|
-
decision.reason ?? "path denied",
|
|
3373
|
-
{ path },
|
|
3374
|
-
);
|
|
3375
|
-
}
|
|
3376
|
-
const maxBytes =
|
|
3377
|
-
typeof args.max_bytes === "number"
|
|
3378
|
-
? Math.min(Math.max(Math.trunc(args.max_bytes), 1024), 512 * 1024)
|
|
3379
|
-
: 128 * 1024;
|
|
3380
|
-
const result = gitDiff(ctx.repoRoot, path, maxBytes);
|
|
3381
|
-
const redacted = redactMcpText(result.diff);
|
|
3382
|
-
audit(ctx, name, "ok", args, path);
|
|
3383
|
-
return textResult({
|
|
3384
|
-
...result,
|
|
3385
|
-
diff: redacted.text,
|
|
3386
|
-
redactions: redacted.redactions,
|
|
3387
|
-
});
|
|
3388
|
-
}
|
|
3389
|
-
case "list_checks": {
|
|
3390
|
-
if (ctx.policy.profile !== "controller")
|
|
3391
|
-
return errorResult(
|
|
3392
|
-
"TOOL_DISABLED",
|
|
3393
|
-
"list_checks requires the controller profile",
|
|
3394
|
-
);
|
|
3395
|
-
const checks = listControllerChecks(ctx.repoRoot);
|
|
3396
|
-
audit(ctx, name, "ok", args);
|
|
3397
|
-
return textResult({ checks });
|
|
3398
|
-
}
|
|
3399
|
-
case "list_issues": {
|
|
3400
|
-
if (ctx.policy.profile !== "controller")
|
|
3401
|
-
return errorResult(
|
|
3402
|
-
"TOOL_DISABLED",
|
|
3403
|
-
"list_issues requires the controller profile",
|
|
3404
|
-
);
|
|
3405
|
-
const projectState = loadControllerProjectState(ctx.repoRoot);
|
|
3406
|
-
const issues = listIssueEffectiveViews(ctx.repoRoot).map((issue) => {
|
|
3407
|
-
const readiness = inspectIssueReadiness(ctx.repoRoot, issue.id);
|
|
3408
|
-
return {
|
|
3409
|
-
id: issue.id,
|
|
3410
|
-
title: issue.title,
|
|
3411
|
-
kind: issue.kind,
|
|
3412
|
-
status: issue.status,
|
|
3413
|
-
lifecycleStatus: issue.lifecycleStatus,
|
|
3414
|
-
archivedAt: issue.archivedAt,
|
|
3415
|
-
isCurrent: projectState.currentIssueId === issue.id,
|
|
3416
|
-
updatedAt: issue.updatedAt,
|
|
3417
|
-
taskCount: issue.tasks.length,
|
|
3418
|
-
taskCounts: issue.tasks.reduce<Record<string, number>>((counts, task) => {
|
|
3419
|
-
counts[task.effectiveStatus] = (counts[task.effectiveStatus] ?? 0) + 1;
|
|
3420
|
-
return counts;
|
|
3421
|
-
}, {}),
|
|
3422
|
-
declaredTaskCounts: issue.tasks.reduce<Record<string, number>>((counts, task) => {
|
|
3423
|
-
counts[task.declaredStatus] = (counts[task.declaredStatus] ?? 0) + 1;
|
|
3424
|
-
return counts;
|
|
3425
|
-
}, {}),
|
|
3426
|
-
readyTaskIds: readiness.readyTaskIds,
|
|
3427
|
-
queueableTaskIds: readiness.queueableTaskIds,
|
|
3428
|
-
approvalPendingTaskIds: readiness.approvalPendingTaskIds,
|
|
3429
|
-
};
|
|
3430
|
-
});
|
|
3431
|
-
audit(ctx, name, "ok", args);
|
|
3432
|
-
return textResult({ issues });
|
|
3433
|
-
}
|
|
3434
|
-
case "get_project_board": {
|
|
3435
|
-
if (ctx.policy.profile !== "controller")
|
|
3436
|
-
return errorResult(
|
|
3437
|
-
"TOOL_DISABLED",
|
|
3438
|
-
"get_project_board requires the controller profile",
|
|
3439
|
-
);
|
|
3440
|
-
const startedAt = performance.now();
|
|
3441
|
-
const controllerHome = resolveRepoPreferredControllerHome(ctx.repoRoot);
|
|
3442
|
-
const detailLevel = args.detail_level === "full" ? "full" : args.detail_level === "detail" ? "detail" : "summary";
|
|
3443
|
-
const payload = detailLevel === "summary"
|
|
3444
|
-
? buildRequirementBoard({ controllerHome, repoId: ctx.repoId })
|
|
3445
|
-
: buildExecutionDiagnostics({
|
|
3446
|
-
controllerHome,
|
|
3447
|
-
repoId: ctx.repoId,
|
|
3448
|
-
detailLevel,
|
|
3449
|
-
requirementId: typeof args.requirement_id === "string" ? args.requirement_id : undefined,
|
|
3450
|
-
});
|
|
3451
|
-
audit(ctx, name, "ok", args);
|
|
3452
|
-
const response = {
|
|
3453
|
-
...payload,
|
|
3454
|
-
responseMeta: {
|
|
3455
|
-
serverDurationMs: Number((performance.now() - startedAt).toFixed(2)),
|
|
3456
|
-
structuredPayloadBytes: 0,
|
|
3457
|
-
},
|
|
3458
|
-
};
|
|
3459
|
-
response.responseMeta.structuredPayloadBytes = Buffer.byteLength(JSON.stringify(response), "utf8");
|
|
3460
|
-
return textResult(response);
|
|
3461
|
-
}
|
|
3462
|
-
case "get_project_governance": {
|
|
3463
|
-
if (ctx.policy.profile !== "controller")
|
|
3464
|
-
return errorResult("TOOL_DISABLED", "get_project_governance requires the controller profile");
|
|
3465
|
-
const governance = inspectProjectGovernance(ctx.repoRoot);
|
|
3466
|
-
audit(ctx, name, "ok", args);
|
|
3467
|
-
return textResult(governance);
|
|
3468
|
-
}
|
|
3469
|
-
case "reconcile_project_governance": {
|
|
3470
|
-
if (ctx.policy.profile !== "controller")
|
|
3471
|
-
return errorResult("TOOL_DISABLED", "reconcile_project_governance requires the controller profile");
|
|
3472
|
-
const result = reconcileProjectGovernance(ctx.repoRoot);
|
|
3473
|
-
audit(ctx, name, "ok", args, ".ai/harness/controller/project-state.json");
|
|
3474
|
-
return textResult(result);
|
|
3475
|
-
}
|
|
3476
|
-
case "get_project_state": {
|
|
3477
|
-
if (ctx.policy.profile !== "controller")
|
|
3478
|
-
return errorResult("TOOL_DISABLED", "get_project_state requires the controller profile");
|
|
3479
|
-
const state = loadControllerProjectState(ctx.repoRoot);
|
|
3480
|
-
audit(ctx, name, "ok", args);
|
|
3481
|
-
return textResult(state);
|
|
3482
|
-
}
|
|
3483
|
-
case "set_current_issue": {
|
|
3484
|
-
if (ctx.policy.profile !== "controller")
|
|
3485
|
-
return errorResult("TOOL_DISABLED", "set_current_issue requires the controller profile");
|
|
3486
|
-
const issue = getIssue(ctx.repoRoot, String(args.issue_id ?? ""));
|
|
3487
|
-
if (issue.archivedAt || ["done", "cancelled"].includes(issue.status))
|
|
3488
|
-
return errorResult("ISSUE_NOT_ACTIVE", `only an active, non-archived Issue can be selected (current: ${issue.status})`);
|
|
3489
|
-
const state = saveControllerProjectState(ctx.repoRoot, { currentIssueId: issue.id }, "chatgpt-controller");
|
|
3490
|
-
audit(ctx, name, "ok", args, ".ai/harness/controller/project-state.json");
|
|
3491
|
-
return textResult(state);
|
|
3492
|
-
}
|
|
3493
|
-
case "archive_issue": {
|
|
3494
|
-
if (ctx.policy.profile !== "controller")
|
|
3495
|
-
return errorResult("TOOL_DISABLED", "archive_issue requires the controller profile");
|
|
3496
|
-
const issue = archiveIssue(ctx.repoRoot, String(args.issue_id ?? ""));
|
|
3497
|
-
audit(ctx, name, "ok", args, `tasks/issues/${issue.id}`);
|
|
3498
|
-
return textResult(projectIssueEffectiveView(ctx.repoRoot, issue));
|
|
3499
|
-
}
|
|
3500
|
-
case "restore_issue": {
|
|
3501
|
-
if (ctx.policy.profile !== "controller")
|
|
3502
|
-
return errorResult("TOOL_DISABLED", "restore_issue requires the controller profile");
|
|
3503
|
-
const issue = restoreIssue(ctx.repoRoot, String(args.issue_id ?? ""));
|
|
3504
|
-
audit(ctx, name, "ok", args, `tasks/issues/${issue.id}`);
|
|
3505
|
-
return textResult(projectIssueEffectiveView(ctx.repoRoot, issue));
|
|
3506
|
-
}
|
|
3507
|
-
case "get_worklog_timeline": {
|
|
3508
|
-
if (ctx.policy.profile !== "controller")
|
|
3509
|
-
return errorResult("TOOL_DISABLED", "get_worklog_timeline requires the controller profile");
|
|
3510
|
-
const events = listControllerWorklogEvents(ctx.repoRoot, {
|
|
3511
|
-
category: parseWorklogCategory(args.category),
|
|
3512
|
-
issueId: typeof args.issue_id === "string" ? args.issue_id : undefined,
|
|
3513
|
-
taskId: typeof args.task_id === "string" ? args.task_id : undefined,
|
|
3514
|
-
runId: typeof args.run_id === "string" ? args.run_id : undefined,
|
|
3515
|
-
editSessionId: typeof args.edit_session_id === "string" ? args.edit_session_id : undefined,
|
|
3516
|
-
since: typeof args.since === "string" ? args.since : undefined,
|
|
3517
|
-
until: typeof args.until === "string" ? args.until : undefined,
|
|
3518
|
-
limit: typeof args.limit === "number" ? Math.trunc(args.limit) : undefined,
|
|
3519
|
-
});
|
|
3520
|
-
audit(ctx, name, "ok", args);
|
|
3521
|
-
return textResult({ events });
|
|
3522
|
-
}
|
|
3523
|
-
case "export_worklog": {
|
|
3524
|
-
if (ctx.policy.profile !== "controller")
|
|
3525
|
-
return errorResult("TOOL_DISABLED", "export_worklog requires the controller profile");
|
|
3526
|
-
const result = exportControllerWorklog(ctx.repoRoot, {
|
|
3527
|
-
format: args.format === "json" ? "json" : "markdown",
|
|
3528
|
-
outputPath: typeof args.output_path === "string" ? args.output_path : undefined,
|
|
3529
|
-
filter: {
|
|
3530
|
-
issueId: typeof args.issue_id === "string" ? args.issue_id : undefined,
|
|
3531
|
-
taskId: typeof args.task_id === "string" ? args.task_id : undefined,
|
|
3532
|
-
runId: typeof args.run_id === "string" ? args.run_id : undefined,
|
|
3533
|
-
editSessionId: typeof args.edit_session_id === "string" ? args.edit_session_id : undefined,
|
|
3534
|
-
},
|
|
3535
|
-
});
|
|
3536
|
-
audit(ctx, name, "ok", args, result.path);
|
|
3537
|
-
return textResult(result);
|
|
3538
|
-
}
|
|
3539
|
-
case "get_github_plugin_status": {
|
|
3540
|
-
if (ctx.policy.profile !== "controller")
|
|
3541
|
-
return errorResult("TOOL_DISABLED", "get_github_plugin_status requires the controller profile");
|
|
3542
|
-
const status = getGitHubPluginStatus(ctx.repoRoot);
|
|
3543
|
-
audit(ctx, name, "ok", args, status.repository);
|
|
3544
|
-
return textResult(status);
|
|
3545
|
-
}
|
|
3546
|
-
case "configure_github_plugin": {
|
|
3547
|
-
if (ctx.policy.profile !== "controller")
|
|
3548
|
-
return errorResult("TOOL_DISABLED", "configure_github_plugin requires the controller profile");
|
|
3549
|
-
const config = saveGitHubPluginConfig(ctx.repoRoot, {
|
|
3550
|
-
enabled: typeof args.enabled === "boolean" ? args.enabled : undefined,
|
|
3551
|
-
repository: args.clear_repository === true ? "" : typeof args.repository === "string" ? args.repository : undefined,
|
|
3552
|
-
syncMode: args.sync_mode === "checkpoint" ? "checkpoint" : args.sync_mode === "manual" ? "manual" : undefined,
|
|
3553
|
-
includeTasks: typeof args.include_tasks === "boolean" ? args.include_tasks : undefined,
|
|
3554
|
-
projectOwner: args.clear_project === true ? "" : typeof args.project_owner === "string" ? args.project_owner : undefined,
|
|
3555
|
-
projectNumber: args.clear_project === true ? null : typeof args.project_number === "number" ? args.project_number : undefined,
|
|
1487
|
+
const roots = [
|
|
1488
|
+
"docs/spec.md",
|
|
1489
|
+
"plans",
|
|
1490
|
+
"tasks/current.md",
|
|
1491
|
+
".ai/harness/session",
|
|
1492
|
+
".ai/harness/controller/packets",
|
|
1493
|
+
".ai/harness/projections",
|
|
1494
|
+
".ai/harness/checks",
|
|
1495
|
+
];
|
|
1496
|
+
audit(ctx, name, "ok", args);
|
|
1497
|
+
return textResult({
|
|
1498
|
+
repoRoot: ctx.repoRoot,
|
|
1499
|
+
adopted: isForgeAdopted(ctx.repoRoot),
|
|
1500
|
+
profile: ctx.policy.profile,
|
|
1501
|
+
branch: currentGitBranch(ctx.repoRoot),
|
|
1502
|
+
workflowRoots: roots.map((path) => ({
|
|
1503
|
+
path,
|
|
1504
|
+
exists: existsSync(join(ctx.repoRoot, path)),
|
|
1505
|
+
})),
|
|
3556
1506
|
});
|
|
3557
|
-
audit(ctx, name, "ok", args, "repository-registry:github");
|
|
3558
|
-
return textResult(config);
|
|
3559
|
-
}
|
|
3560
|
-
case "get_issue": {
|
|
3561
|
-
if (ctx.policy.profile !== "controller")
|
|
3562
|
-
return errorResult(
|
|
3563
|
-
"TOOL_DISABLED",
|
|
3564
|
-
"get_issue requires the controller profile",
|
|
3565
|
-
);
|
|
3566
|
-
const issue = getIssueReadView(
|
|
3567
|
-
ctx.repoRoot,
|
|
3568
|
-
String(args.issue_id ?? ""),
|
|
3569
|
-
args.detail_level === "full" ? "full" : "summary",
|
|
3570
|
-
);
|
|
3571
|
-
audit(ctx, name, "ok", args, `tasks/issues/${issue.id}`);
|
|
3572
|
-
return textResult(issue);
|
|
3573
|
-
}
|
|
3574
|
-
case "github_status": {
|
|
3575
|
-
if (ctx.policy.profile !== "controller")
|
|
3576
|
-
return errorResult(
|
|
3577
|
-
"TOOL_DISABLED",
|
|
3578
|
-
"github_status requires the controller profile",
|
|
3579
|
-
);
|
|
3580
|
-
const status = getGitHubStatus(
|
|
3581
|
-
ctx.repoRoot,
|
|
3582
|
-
typeof args.repo === "string" ? args.repo : undefined,
|
|
3583
|
-
);
|
|
3584
|
-
audit(
|
|
3585
|
-
ctx,
|
|
3586
|
-
name,
|
|
3587
|
-
status.available && status.authenticated ? "ok" : "failed",
|
|
3588
|
-
args,
|
|
3589
|
-
status.repository?.nameWithOwner,
|
|
3590
|
-
status.errors.join("; "),
|
|
3591
|
-
);
|
|
3592
|
-
return textResult(status);
|
|
3593
1507
|
}
|
|
3594
|
-
case "
|
|
3595
|
-
|
|
3596
|
-
|
|
3597
|
-
"TOOL_DISABLED",
|
|
3598
|
-
"publish_issue_to_github requires the controller profile",
|
|
3599
|
-
);
|
|
3600
|
-
const issue = publishIssueToGitHub(
|
|
3601
|
-
ctx.repoRoot,
|
|
3602
|
-
String(args.issue_id ?? ""),
|
|
3603
|
-
{
|
|
3604
|
-
repo: typeof args.repo === "string" ? args.repo : undefined,
|
|
3605
|
-
labels: stringList(args.labels),
|
|
3606
|
-
includeTasks: args.include_tasks === true,
|
|
3607
|
-
projectOwner:
|
|
3608
|
-
typeof args.project_owner === "string"
|
|
3609
|
-
? args.project_owner
|
|
3610
|
-
: undefined,
|
|
3611
|
-
projectNumber:
|
|
3612
|
-
typeof args.project_number === "number"
|
|
3613
|
-
? Math.trunc(args.project_number)
|
|
3614
|
-
: undefined,
|
|
3615
|
-
},
|
|
1508
|
+
case "harness_doctor": {
|
|
1509
|
+
const localConfig = existsSync(
|
|
1510
|
+
join(ctx.repoRoot, ".forge", "mcp.local.json"),
|
|
3616
1511
|
);
|
|
3617
|
-
|
|
3618
|
-
|
|
3619
|
-
}
|
|
3620
|
-
case "refresh_github_issue": {
|
|
3621
|
-
if (ctx.policy.profile !== "controller")
|
|
3622
|
-
return errorResult(
|
|
3623
|
-
"TOOL_DISABLED",
|
|
3624
|
-
"refresh_github_issue requires the controller profile",
|
|
3625
|
-
);
|
|
3626
|
-
const result = refreshGitHubIssue(
|
|
3627
|
-
ctx.repoRoot,
|
|
3628
|
-
String(args.issue_id ?? ""),
|
|
1512
|
+
const codexConfig = existsSync(
|
|
1513
|
+
join(ctx.repoRoot, ".codex", "config.toml"),
|
|
3629
1514
|
);
|
|
3630
|
-
audit(ctx, name, "ok", args
|
|
1515
|
+
audit(ctx, name, "ok", args);
|
|
3631
1516
|
return textResult({
|
|
3632
|
-
|
|
3633
|
-
|
|
1517
|
+
status: isForgeAdopted(ctx.repoRoot)
|
|
1518
|
+
? "ready_local"
|
|
1519
|
+
: "not_adopted",
|
|
1520
|
+
repo: ctx.repoRoot,
|
|
1521
|
+
profile: ctx.policy.profile,
|
|
1522
|
+
mcp: {
|
|
1523
|
+
localConfig,
|
|
1524
|
+
policy: "builtin",
|
|
1525
|
+
profile: ctx.policy.profile,
|
|
1526
|
+
toolSurface:
|
|
1527
|
+
ctx.policy.profile === "controller"
|
|
1528
|
+
? FORGE_TOOL_SURFACE
|
|
1529
|
+
: `${ctx.policy.profile}-legacy-v1`,
|
|
1530
|
+
toolCount: buildMcpToolDefinitions(ctx.policy, {
|
|
1531
|
+
enableChatgptBrowser: ctx.enableChatgptBrowser === true,
|
|
1532
|
+
}).length,
|
|
1533
|
+
repositoryRead:
|
|
1534
|
+
ctx.policy.profile === "controller"
|
|
1535
|
+
? "ordinary repository source and document files are readable; immutable secret and runtime paths remain denied"
|
|
1536
|
+
: "workflow-scoped",
|
|
1537
|
+
deniedPaths: ctx.policy.denyGlobs.length,
|
|
1538
|
+
runner: {
|
|
1539
|
+
enabled: ctx.policy.execution.agentRunner,
|
|
1540
|
+
defaultTimeoutMs: ctx.policy.execution.runnerTimeoutMs,
|
|
1541
|
+
maxTimeoutMs: ctx.policy.execution.runnerMaxTimeoutMs,
|
|
1542
|
+
},
|
|
1543
|
+
},
|
|
1544
|
+
codex: {
|
|
1545
|
+
configured: codexConfig,
|
|
1546
|
+
fix: codexConfig
|
|
1547
|
+
? null
|
|
1548
|
+
: "forge mcp setup codex --repo . --scope project",
|
|
1549
|
+
},
|
|
1550
|
+
chatgpt: {
|
|
1551
|
+
localEndpoint: "http://127.0.0.1:8765/mcp",
|
|
1552
|
+
manualStepsRequired: true,
|
|
1553
|
+
guide: "docs/forge-chatgpt-mcp-setup.md",
|
|
1554
|
+
},
|
|
3634
1555
|
});
|
|
3635
1556
|
}
|
|
3636
|
-
case "
|
|
3637
|
-
if (ctx.policy.profile !== "controller")
|
|
3638
|
-
return errorResult(
|
|
3639
|
-
"TOOL_DISABLED",
|
|
3640
|
-
"close_github_issue requires the controller profile",
|
|
3641
|
-
);
|
|
3642
|
-
const issue = closeGitHubIssue(
|
|
3643
|
-
ctx.repoRoot,
|
|
3644
|
-
String(args.issue_id ?? ""),
|
|
3645
|
-
);
|
|
3646
|
-
audit(ctx, name, "ok", args, issue.github?.url);
|
|
3647
|
-
return textResult(projectIssueEffectiveView(ctx.repoRoot, issue));
|
|
3648
|
-
}
|
|
3649
|
-
case "inspect_issue_readiness": {
|
|
1557
|
+
case "controller_capabilities": {
|
|
3650
1558
|
if (ctx.policy.profile !== "controller")
|
|
3651
1559
|
return errorResult(
|
|
3652
1560
|
"TOOL_DISABLED",
|
|
3653
|
-
"
|
|
1561
|
+
"controller_capabilities requires the controller profile",
|
|
3654
1562
|
);
|
|
3655
|
-
const
|
|
3656
|
-
|
|
3657
|
-
|
|
3658
|
-
|
|
3659
|
-
audit(
|
|
3660
|
-
ctx,
|
|
3661
|
-
name,
|
|
3662
|
-
readiness.ready ? "ok" : "blocked",
|
|
3663
|
-
args,
|
|
3664
|
-
`tasks/issues/${readiness.issueId}`,
|
|
3665
|
-
[...readiness.blockers, ...readiness.taskBlockers].map((item) => item.message).join("; "),
|
|
1563
|
+
const expectedTools = ctx.publishedToolNames;
|
|
1564
|
+
if (!expectedTools) return errorResult(
|
|
1565
|
+
'MCP_TOOL_SURFACE_UNAVAILABLE',
|
|
1566
|
+
'Controller capability diagnostics require the authoritative MCP registry projection.',
|
|
3666
1567
|
);
|
|
3667
|
-
|
|
3668
|
-
|
|
3669
|
-
|
|
3670
|
-
|
|
3671
|
-
|
|
3672
|
-
|
|
3673
|
-
|
|
3674
|
-
|
|
3675
|
-
|
|
3676
|
-
|
|
3677
|
-
|
|
3678
|
-
|
|
3679
|
-
|
|
3680
|
-
|
|
3681
|
-
|
|
1568
|
+
const preferredTools: string[] = [...PREFERRED_FACADE_TOOL_NAMES];
|
|
1569
|
+
const capabilityDescriptors = listCapabilityDescriptors();
|
|
1570
|
+
const connectorSnapshot = Array.isArray(args.connector_tool_names)
|
|
1571
|
+
? args.connector_tool_names.map(String)
|
|
1572
|
+
: Array.isArray(args.connectorToolNames)
|
|
1573
|
+
? args.connectorToolNames.map(String)
|
|
1574
|
+
: undefined;
|
|
1575
|
+
const connectorFreshness = await buildLocalConnectorStatusForRepo({
|
|
1576
|
+
repoRoot: ctx.repoRoot,
|
|
1577
|
+
expectedTools,
|
|
1578
|
+
connectorToolNames: connectorSnapshot,
|
|
1579
|
+
});
|
|
1580
|
+
// staleConnector only when ChatGPT snapshot is supplied and missing rh_*.
|
|
1581
|
+
const missingFacadeTools = connectorFreshness.missingConnectorTools;
|
|
1582
|
+
const staleConnector = connectorFreshness.status === "chatgpt_snapshot_missing_facade";
|
|
1583
|
+
const payload = {
|
|
1584
|
+
schemaVersion: FORGE_MCP_SCHEMA_VERSION,
|
|
1585
|
+
toolSurface: FORGE_TOOL_SURFACE,
|
|
1586
|
+
version: FORGE_VERSION,
|
|
1587
|
+
toolSurfaceFingerprint: forgeToolSurfaceFingerprint(expectedTools),
|
|
1588
|
+
executionModel: "chatgpt-controller-execution-bridge",
|
|
1589
|
+
profile: ctx.policy.profile,
|
|
1590
|
+
capabilities: {
|
|
1591
|
+
repositoryInspection: true,
|
|
1592
|
+
issueLauncher: true,
|
|
1593
|
+
githubIssuesAndProjects: true,
|
|
1594
|
+
localAgentRuns: ctx.policy.execution.agentRunner,
|
|
1595
|
+
githubCopilotSessions: true,
|
|
1596
|
+
dynamicTaskGraph: true,
|
|
1597
|
+
runtimeAgentSelection: true,
|
|
1598
|
+
taskAgentBinding: false,
|
|
1599
|
+
structuredRunEvents: true,
|
|
1600
|
+
boundedDirectEdits: true,
|
|
1601
|
+
multiRevisionDirectEdits: true,
|
|
1602
|
+
editSavepoints: true,
|
|
1603
|
+
partialEditRollback: true,
|
|
1604
|
+
directEditFirstRouting: true,
|
|
1605
|
+
persistedEditDiffs: true,
|
|
1606
|
+
verifiedEditFinalization: true,
|
|
1607
|
+
verificationGate: true,
|
|
1608
|
+
localRunWatch: true,
|
|
1609
|
+
localVisualController: true,
|
|
1610
|
+
hierarchicalControllerUI: true,
|
|
1611
|
+
localApprovalQueue: false,
|
|
1612
|
+
localRiskApprovalGate: false,
|
|
1613
|
+
quickAgentSessions: true,
|
|
1614
|
+
persistentTimeouts: true,
|
|
1615
|
+
timeoutProgress: true,
|
|
1616
|
+
connectorSchemaDiagnostics: true,
|
|
1617
|
+
projectProgressAggregation: true,
|
|
1618
|
+
effectiveTaskStatus: true,
|
|
1619
|
+
unifiedWorklogTimeline: true,
|
|
1620
|
+
exportableWorklogReports: true,
|
|
1621
|
+
githubPluginConfiguration: true,
|
|
1622
|
+
serverSentEventDashboard: true,
|
|
1623
|
+
singleExecutionFocus: false,
|
|
1624
|
+
taskLocalReadiness: true,
|
|
1625
|
+
riskAdaptiveVerification: true,
|
|
1626
|
+
ephemeralQuickAgentSessions: true,
|
|
1627
|
+
automaticRunContinuation: true,
|
|
1628
|
+
reportedCommandEvidence: true,
|
|
1629
|
+
governanceDiagnostics: true,
|
|
1630
|
+
safeGovernanceReconciliation: true,
|
|
1631
|
+
evidenceGateProgress: true,
|
|
1632
|
+
directTaskActions: true,
|
|
1633
|
+
issueArchiving: true,
|
|
1634
|
+
retryableRunFailures: true,
|
|
1635
|
+
issueCreationGuardrails: true,
|
|
1636
|
+
persistedCheckEvidence: true,
|
|
1637
|
+
persistedCheckReuse: true,
|
|
1638
|
+
directVerification: true,
|
|
1639
|
+
controllerContextAggregation: true,
|
|
1640
|
+
taskLedgerProjection: true,
|
|
1641
|
+
assistantPluginRuntime: true,
|
|
1642
|
+
chatgptFacadeTools: true,
|
|
1643
|
+
goalWorkloop: true,
|
|
1644
|
+
handoffInbox: true,
|
|
1645
|
+
codexGrokDelegation: true,
|
|
1646
|
+
selfHealingRepair: true,
|
|
3682
1647
|
},
|
|
3683
|
-
|
|
3684
|
-
|
|
3685
|
-
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
|
|
3692
|
-
|
|
3693
|
-
|
|
3694
|
-
|
|
3695
|
-
|
|
3696
|
-
|
|
3697
|
-
|
|
3698
|
-
|
|
3699
|
-
|
|
3700
|
-
|
|
3701
|
-
|
|
3702
|
-
|
|
3703
|
-
|
|
3704
|
-
|
|
3705
|
-
|
|
3706
|
-
|
|
3707
|
-
|
|
3708
|
-
|
|
3709
|
-
|
|
3710
|
-
|
|
3711
|
-
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
|
|
3716
|
-
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
issue: {
|
|
3726
|
-
id: issue.id,
|
|
3727
|
-
title: issue.title,
|
|
3728
|
-
status: issue.status,
|
|
3729
|
-
github: issue.github,
|
|
1648
|
+
runner: {
|
|
1649
|
+
enabled: ctx.policy.execution.agentRunner,
|
|
1650
|
+
allowedAgents: ctx.policy.execution.allowedAgents,
|
|
1651
|
+
defaultTimeoutMs: ctx.policy.execution.runnerTimeoutMs,
|
|
1652
|
+
defaultTimeout: formatDurationMs(
|
|
1653
|
+
ctx.policy.execution.runnerTimeoutMs,
|
|
1654
|
+
),
|
|
1655
|
+
maxTimeoutMs: ctx.policy.execution.runnerMaxTimeoutMs,
|
|
1656
|
+
maxTimeout: formatDurationMs(
|
|
1657
|
+
ctx.policy.execution.runnerMaxTimeoutMs,
|
|
1658
|
+
),
|
|
1659
|
+
note: "Requested timeout values are validated explicitly and are never silently reduced to the default.",
|
|
1660
|
+
},
|
|
1661
|
+
preferredTools,
|
|
1662
|
+
capabilityGroups: summarizeCapabilityGroups(),
|
|
1663
|
+
toolArchitecture: {
|
|
1664
|
+
facadeTools: preferredTools,
|
|
1665
|
+
atomicTypedToolsRetained: true,
|
|
1666
|
+
internalHandlersRetained: true,
|
|
1667
|
+
domainSchemaLoading: 'static_stable_surface',
|
|
1668
|
+
dynamicDomainSchemaLoadingSupported: false,
|
|
1669
|
+
capabilityDescriptorCount: capabilityDescriptors.length,
|
|
1670
|
+
},
|
|
1671
|
+
toolExposure: {
|
|
1672
|
+
preferred: preferredTools,
|
|
1673
|
+
note: "Prefer the five rh_* workflow facades for orchestration. Typed repository, Git, Issue/Task, plugin, browser, iOS, artifact, and recovery tools remain available because the current MCP transport cannot load domain schemas per conversation.",
|
|
1674
|
+
exposedToolCount: expectedTools.length,
|
|
1675
|
+
},
|
|
1676
|
+
expectedTools,
|
|
1677
|
+
facadeToolsPresentInExpectedTools: preferredTools.every((tool: string) => expectedTools.includes(tool)),
|
|
1678
|
+
staleConnector,
|
|
1679
|
+
missingFacadeTools,
|
|
1680
|
+
connectorFreshness: {
|
|
1681
|
+
status: connectorFreshness.status,
|
|
1682
|
+
severity: connectorFreshness.severity,
|
|
1683
|
+
summary: connectorFreshness.summary,
|
|
1684
|
+
missingLocalTools: connectorFreshness.missingLocalTools,
|
|
1685
|
+
missingConnectorTools: connectorFreshness.missingConnectorTools,
|
|
1686
|
+
expectedFacadeTools: connectorFreshness.expectedFacadeTools,
|
|
1687
|
+
restartRecommended: connectorFreshness.restartRecommended,
|
|
1688
|
+
reconnectRecommended: connectorFreshness.reconnectRecommended,
|
|
1689
|
+
suggestedActions: connectorFreshness.suggestedActions,
|
|
3730
1690
|
},
|
|
3731
|
-
|
|
3732
|
-
|
|
3733
|
-
|
|
1691
|
+
docs: [
|
|
1692
|
+
"docs/architecture/current/README.md",
|
|
1693
|
+
"docs/architecture/history.md",
|
|
1694
|
+
"docs/operations/controller-reliability-runbook.md",
|
|
1695
|
+
"docs/forge-github-issue-launcher.md",
|
|
1696
|
+
"docs/forge-chatgpt-mcp-setup.md",
|
|
1697
|
+
],
|
|
1698
|
+
staleConnectorHint: staleConnector
|
|
1699
|
+
? `Stale ChatGPT connector tool snapshot is missing facade tools: ${missingFacadeTools.join(", ")}. Refresh/reconnect MCP so tools/list reloads rh_status, rh_inbox, rh_context, and rh_work.`
|
|
1700
|
+
: connectorFreshness.status === "unable_to_verify_chatgpt_snapshot"
|
|
1701
|
+
? `Local MCP tool surface includes preferred facade tools. ChatGPT connector snapshot was not supplied, so freshness cannot be verified from this call. If ChatGPT tools/list is missing rh_status/rh_inbox/rh_context/rh_work, reconnect MCP. Pass connector_tool_names for an exact check.`
|
|
1702
|
+
: connectorFreshness.status === "stale_fingerprint"
|
|
1703
|
+
? `Local MCP process fingerprint/version may be stale. Restart controller/MCP, then reconnect ChatGPT if rh_* are still missing.`
|
|
1704
|
+
: connectorFreshness.status === "local_mcp_missing_facade"
|
|
1705
|
+
? `Local MCP tool surface is missing facade tools: ${connectorFreshness.missingLocalTools.join(", ")}. Restart controller/MCP.`
|
|
1706
|
+
: `Connector clients must match ${FORGE_TOOL_SURFACE} version ${FORGE_VERSION} / schema ${FORGE_MCP_SCHEMA_VERSION}. Prefer rh_status/rh_inbox/rh_context/rh_work. Pass connector_tool_names to detect a stale ChatGPT tool snapshot precisely.`,
|
|
3734
1707
|
};
|
|
3735
|
-
audit(
|
|
3736
|
-
|
|
3737
|
-
name,
|
|
3738
|
-
readiness.queueable ? "ok" : "blocked",
|
|
3739
|
-
args,
|
|
3740
|
-
`tasks/issues/${issue.id}`,
|
|
3741
|
-
);
|
|
3742
|
-
return textResult(preview);
|
|
3743
|
-
}
|
|
3744
|
-
case "create_issue": {
|
|
3745
|
-
if (ctx.policy.profile !== "controller")
|
|
3746
|
-
return errorResult(
|
|
3747
|
-
"TOOL_DISABLED",
|
|
3748
|
-
"create_issue requires the controller profile",
|
|
3749
|
-
);
|
|
3750
|
-
const issue = createIssue(ctx.repoRoot, {
|
|
3751
|
-
title: String(args.title ?? ""),
|
|
3752
|
-
kind: (args.kind ? String(args.kind) : "feature") as IssueKind,
|
|
3753
|
-
summary: typeof args.summary === "string" ? args.summary : undefined,
|
|
3754
|
-
goals: stringList(args.goals),
|
|
3755
|
-
nonGoals: stringList(args.non_goals),
|
|
3756
|
-
acceptanceCriteria: stringList(args.acceptance_criteria),
|
|
3757
|
-
relatedArtifacts: stringList(args.related_artifacts),
|
|
3758
|
-
tasks: controllerTaskDrafts(args.tasks),
|
|
3759
|
-
allowWhileFocused: args.allow_while_focused === true,
|
|
3760
|
-
allowDuplicate: args.allow_duplicate === true,
|
|
3761
|
-
allowWhenPaused: args.allow_when_paused === true,
|
|
3762
|
-
});
|
|
3763
|
-
audit(ctx, name, "ok", args, `tasks/issues/${issue.id}`);
|
|
3764
|
-
return textResult(projectIssueEffectiveView(ctx.repoRoot, issue));
|
|
1708
|
+
audit(ctx, name, "ok", args);
|
|
1709
|
+
return textResult(payload);
|
|
3765
1710
|
}
|
|
3766
|
-
case "
|
|
1711
|
+
case "local_bridge_status": {
|
|
3767
1712
|
if (ctx.policy.profile !== "controller")
|
|
3768
1713
|
return errorResult(
|
|
3769
1714
|
"TOOL_DISABLED",
|
|
3770
|
-
"
|
|
1715
|
+
"local_bridge_status requires the controller profile",
|
|
3771
1716
|
);
|
|
3772
|
-
const
|
|
3773
|
-
|
|
3774
|
-
|
|
3775
|
-
|
|
3776
|
-
|
|
3777
|
-
|
|
3778
|
-
|
|
3779
|
-
|
|
3780
|
-
goals: Array.isArray(args.goals) ? stringList(args.goals) : undefined,
|
|
3781
|
-
nonGoals: Array.isArray(args.non_goals)
|
|
3782
|
-
? stringList(args.non_goals)
|
|
3783
|
-
: undefined,
|
|
3784
|
-
acceptanceCriteria: Array.isArray(args.acceptance_criteria)
|
|
3785
|
-
? stringList(args.acceptance_criteria)
|
|
3786
|
-
: undefined,
|
|
3787
|
-
relatedArtifacts: Array.isArray(args.related_artifacts)
|
|
3788
|
-
? stringList(args.related_artifacts)
|
|
3789
|
-
: undefined,
|
|
3790
|
-
};
|
|
3791
|
-
const migrated = applyMigratedIssueDecision(ctx.repoRoot, {
|
|
3792
|
-
issueId,
|
|
3793
|
-
...patch,
|
|
1717
|
+
const detailLevel = args.detail_level === "detail" || args.detail === true
|
|
1718
|
+
? "detail"
|
|
1719
|
+
: "summary";
|
|
1720
|
+
const controllerHome = resolveRepoPreferredControllerHome(ctx.repoRoot);
|
|
1721
|
+
const surface = resolveLocalBridgeSurface({
|
|
1722
|
+
controllerHome,
|
|
1723
|
+
repoRoot: ctx.repoRoot,
|
|
1724
|
+
allowProcessScan: detailLevel === "detail",
|
|
3794
1725
|
});
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
|
|
1726
|
+
const jobs = listLocalBridgeJobs(ctx.repoRoot, detailLevel === "detail" ? 12 : 20);
|
|
1727
|
+
const { activeJobCount, recentJobSummary } = summarizeRecentJobs(jobs);
|
|
1728
|
+
// Prefer authoritative surface; never invent a legacy 8766 endpoint.
|
|
1729
|
+
const endpoint = surface.endpoint ?? null;
|
|
1730
|
+
const running = surface.enabled
|
|
1731
|
+
&& (surface.processRunning === true
|
|
1732
|
+
|| (surface.endpointConfigured && surface.mode === "embedded"));
|
|
1733
|
+
if (detailLevel === "summary") {
|
|
1734
|
+
const payload = {
|
|
1735
|
+
detailLevel: "summary",
|
|
1736
|
+
repoId: ctx.repoId,
|
|
1737
|
+
running,
|
|
1738
|
+
ready: surface.enabled ? (surface.processRunning !== false) : true,
|
|
1739
|
+
health: surface.enabled
|
|
1740
|
+
? (surface.processRunning === true || surface.mode === "embedded" ? "ready" : "warning")
|
|
1741
|
+
: "disabled",
|
|
1742
|
+
mode: surface.mode,
|
|
1743
|
+
endpoint,
|
|
1744
|
+
endpointConfigured: surface.endpointConfigured,
|
|
1745
|
+
endpointReachable: null as boolean | null,
|
|
1746
|
+
processRunning: surface.processRunning ?? null,
|
|
1747
|
+
expectedSurface: surface.expectedSurface,
|
|
1748
|
+
requiredForReadiness: surface.requiredForReadiness,
|
|
1749
|
+
warnings: surface.error && surface.requiredForReadiness
|
|
1750
|
+
? [{ code: "LOCAL_BRIDGE_ERROR", message: surface.error.slice(0, 300) }]
|
|
1751
|
+
: [],
|
|
1752
|
+
activeJobCount,
|
|
1753
|
+
recentJobSummary,
|
|
1754
|
+
statusSource: surface.source,
|
|
1755
|
+
nonBlocking: !surface.requiredForReadiness,
|
|
1756
|
+
approvalQueue: false,
|
|
1757
|
+
};
|
|
1758
|
+
audit(ctx, name, "ok", args);
|
|
1759
|
+
return textResult(payload);
|
|
3798
1760
|
}
|
|
3799
|
-
const
|
|
3800
|
-
|
|
3801
|
-
|
|
1761
|
+
const reconciliation = reconcileLocalBridgeJobs(ctx.repoRoot);
|
|
1762
|
+
const payload = {
|
|
1763
|
+
detailLevel: "detail",
|
|
1764
|
+
endpoint,
|
|
1765
|
+
endpointConfigured: surface.endpointConfigured,
|
|
1766
|
+
running,
|
|
1767
|
+
error: surface.error,
|
|
1768
|
+
mode: surface.mode,
|
|
1769
|
+
requiredForReadiness: surface.requiredForReadiness,
|
|
1770
|
+
statusSource: surface.source,
|
|
1771
|
+
counts: recentJobSummary,
|
|
1772
|
+
activeJobCount,
|
|
1773
|
+
recentJobSummary,
|
|
1774
|
+
approvalQueue: false,
|
|
1775
|
+
reconciliation,
|
|
1776
|
+
recentJobs: jobs.map((job) => ({
|
|
1777
|
+
jobId: job.jobId,
|
|
1778
|
+
action: job.action,
|
|
1779
|
+
status: job.status,
|
|
1780
|
+
checkId: job.action === "run-check" ? (job.payload as { checkId?: string }).checkId : undefined,
|
|
1781
|
+
runId: job.runId,
|
|
1782
|
+
issueId: job.issueId,
|
|
1783
|
+
taskId: job.taskId,
|
|
1784
|
+
createdAt: job.createdAt,
|
|
1785
|
+
updatedAt: job.updatedAt,
|
|
1786
|
+
finishedAt: job.finishedAt,
|
|
1787
|
+
revision: job.revision,
|
|
1788
|
+
deadlineAt: job.deadlineAt,
|
|
1789
|
+
error: job.error?.slice(0, 300),
|
|
1790
|
+
})),
|
|
1791
|
+
nonBlocking: !surface.requiredForReadiness,
|
|
1792
|
+
fallback:
|
|
1793
|
+
"Open the localhost Local Controller to launch work or inspect execution when a ChatGPT write action is unavailable.",
|
|
1794
|
+
};
|
|
1795
|
+
audit(ctx, name, "ok", args);
|
|
1796
|
+
return textResult(payload);
|
|
3802
1797
|
}
|
|
3803
|
-
case "
|
|
1798
|
+
case "controller_context": {
|
|
3804
1799
|
if (ctx.policy.profile !== "controller")
|
|
3805
1800
|
return errorResult(
|
|
3806
1801
|
"TOOL_DISABLED",
|
|
3807
|
-
"
|
|
1802
|
+
"controller_context requires the controller profile",
|
|
3808
1803
|
);
|
|
3809
|
-
const
|
|
3810
|
-
|
|
3811
|
-
|
|
3812
|
-
|
|
3813
|
-
|
|
3814
|
-
|
|
3815
|
-
|
|
1804
|
+
const controllerHome = resolveRepoPreferredControllerHome(ctx.repoRoot);
|
|
1805
|
+
const requirementBoard = buildRequirementBoard({ controllerHome, repoId: ctx.repoId });
|
|
1806
|
+
const executionDiagnostics = buildExecutionDiagnostics({
|
|
1807
|
+
controllerHome,
|
|
1808
|
+
repoId: ctx.repoId,
|
|
1809
|
+
detailLevel: "detail",
|
|
1810
|
+
});
|
|
1811
|
+
const runs = listAgentJobs(ctx.repoRoot, 8);
|
|
1812
|
+
const reconciliation = reconcileLocalBridgeJobs(ctx.repoRoot);
|
|
1813
|
+
const jobs = listLocalBridgeJobs(ctx.repoRoot, 8);
|
|
1814
|
+
const checks = listControllerChecks(ctx.repoRoot).map((check) => ({
|
|
1815
|
+
id: check.id,
|
|
1816
|
+
description: check.description,
|
|
1817
|
+
timeoutMs: check.timeoutMs,
|
|
1818
|
+
source: check.source,
|
|
1819
|
+
}));
|
|
1820
|
+
const payload = {
|
|
1821
|
+
git: gitSnapshot(ctx.repoRoot),
|
|
1822
|
+
requirementBoard,
|
|
1823
|
+
executionDiagnostics,
|
|
1824
|
+
legacyCompatibility: {
|
|
1825
|
+
currentIssue: "deprecated_frozen",
|
|
1826
|
+
issueTaskBoard: "deprecated_frozen",
|
|
1827
|
+
authority: "controller-home-sqlite",
|
|
1828
|
+
},
|
|
1829
|
+
activeRuns: runs.map((run) => ({
|
|
1830
|
+
runId: run.runId,
|
|
1831
|
+
issueId: run.issueId,
|
|
1832
|
+
taskId: run.taskId,
|
|
1833
|
+
status: run.status,
|
|
1834
|
+
agent: run.agent,
|
|
1835
|
+
provider: run.provider,
|
|
1836
|
+
executionMode: run.executionMode,
|
|
1837
|
+
progress: run.progress,
|
|
1838
|
+
lastHeartbeatAt: run.lastHeartbeatAt,
|
|
1839
|
+
error: run.error?.slice(0, 300),
|
|
1840
|
+
})),
|
|
1841
|
+
localBridge: {
|
|
1842
|
+
reconciliation,
|
|
1843
|
+
recentJobs: jobs.map((job) => ({
|
|
1844
|
+
jobId: job.jobId,
|
|
1845
|
+
action: job.action,
|
|
1846
|
+
status: job.status,
|
|
1847
|
+
checkId: job.action === "run-check" ? (job.payload as { checkId?: string }).checkId : undefined,
|
|
1848
|
+
issueId: job.issueId,
|
|
1849
|
+
taskId: job.taskId,
|
|
1850
|
+
updatedAt: job.updatedAt,
|
|
1851
|
+
})),
|
|
1852
|
+
},
|
|
1853
|
+
checks,
|
|
1854
|
+
};
|
|
1855
|
+
audit(ctx, name, "ok", args);
|
|
1856
|
+
return textResult(payload);
|
|
3816
1857
|
}
|
|
3817
|
-
case "
|
|
3818
|
-
|
|
3819
|
-
|
|
3820
|
-
|
|
3821
|
-
|
|
3822
|
-
|
|
3823
|
-
|
|
3824
|
-
|
|
3825
|
-
|
|
3826
|
-
"INVALID_TASK",
|
|
3827
|
-
"append_task requires one valid task",
|
|
3828
|
-
);
|
|
3829
|
-
const issue = appendTask(
|
|
3830
|
-
ctx.repoRoot,
|
|
3831
|
-
String(args.issue_id ?? ""),
|
|
3832
|
-
drafts[0],
|
|
1858
|
+
case "quick_agent_session":
|
|
1859
|
+
case "submit_local_job": {
|
|
1860
|
+
if (ctx.policy.profile !== "controller") {
|
|
1861
|
+
return errorResult("TOOL_DISABLED", `${name} requires the controller profile`);
|
|
1862
|
+
}
|
|
1863
|
+
audit(ctx, name, "blocked", args);
|
|
1864
|
+
return errorResult(
|
|
1865
|
+
name === "quick_agent_session" ? "AGENT_RUN_DEPRECATED" : "LOCAL_BRIDGE_JOB_RETIRED",
|
|
1866
|
+
"Kernel-managed Agent and Local Bridge Jobs are retired. Create or claim WorkContract and use Process Runtime or the Thin Launcher.",
|
|
3833
1867
|
);
|
|
3834
|
-
audit(ctx, name, "ok", args, `tasks/issues/${issue.id}`);
|
|
3835
|
-
return textResult(projectIssueEffectiveView(ctx.repoRoot, issue));
|
|
3836
1868
|
}
|
|
3837
|
-
case "
|
|
1869
|
+
case "list_local_jobs": {
|
|
3838
1870
|
if (ctx.policy.profile !== "controller")
|
|
3839
1871
|
return errorResult(
|
|
3840
1872
|
"TOOL_DISABLED",
|
|
3841
|
-
"
|
|
1873
|
+
"list_local_jobs requires the controller profile",
|
|
3842
1874
|
);
|
|
3843
|
-
const
|
|
3844
|
-
|
|
3845
|
-
|
|
3846
|
-
|
|
3847
|
-
controllerTaskDrafts(args.tasks),
|
|
3848
|
-
);
|
|
3849
|
-
audit(ctx, name, "ok", args, `tasks/issues/${issue.id}`);
|
|
3850
|
-
return textResult(projectIssueEffectiveView(ctx.repoRoot, issue));
|
|
1875
|
+
const limit = typeof args.limit === "number" ? args.limit : 50;
|
|
1876
|
+
const jobs = listLocalBridgeJobs(ctx.repoRoot, limit);
|
|
1877
|
+
audit(ctx, name, "ok", args);
|
|
1878
|
+
return textResult({ jobs });
|
|
3851
1879
|
}
|
|
3852
|
-
case "
|
|
1880
|
+
case "get_local_job": {
|
|
3853
1881
|
if (ctx.policy.profile !== "controller")
|
|
3854
1882
|
return errorResult(
|
|
3855
1883
|
"TOOL_DISABLED",
|
|
3856
|
-
"
|
|
1884
|
+
"get_local_job requires the controller profile",
|
|
3857
1885
|
);
|
|
3858
|
-
const
|
|
3859
|
-
|
|
3860
|
-
|
|
3861
|
-
|
|
3862
|
-
|
|
3863
|
-
|
|
3864
|
-
|
|
3865
|
-
|
|
1886
|
+
const jobId = String(args.job_id ?? "").trim();
|
|
1887
|
+
const includeEvents = args.include_events === true;
|
|
1888
|
+
const includeOutput = args.include_output === true;
|
|
1889
|
+
const snapshot = getLocalBridgeJobSnapshot(ctx.repoRoot, jobId);
|
|
1890
|
+
const payload = {
|
|
1891
|
+
job: snapshot.job,
|
|
1892
|
+
...(snapshot.status !== "ok" ? { lookup: snapshot } : {}),
|
|
1893
|
+
...(includeEvents && snapshot.status === "ok" ? { events: getLocalBridgeJobEvents(ctx.repoRoot, jobId) } : {}),
|
|
1894
|
+
...(includeOutput
|
|
1895
|
+
? {
|
|
1896
|
+
output: readLocalBridgeJobOutput(ctx.repoRoot, jobId, {
|
|
1897
|
+
stream: args.stream === "stderr" ? "stderr" : "stdout",
|
|
1898
|
+
maxBytes: typeof args.max_bytes === "number" ? args.max_bytes : undefined,
|
|
1899
|
+
}),
|
|
1900
|
+
}
|
|
1901
|
+
: {}),
|
|
1902
|
+
};
|
|
1903
|
+
audit(ctx, name, "ok", args);
|
|
1904
|
+
return textResult(payload);
|
|
3866
1905
|
}
|
|
3867
|
-
case "
|
|
1906
|
+
case "get_local_job_output": {
|
|
3868
1907
|
if (ctx.policy.profile !== "controller")
|
|
3869
1908
|
return errorResult(
|
|
3870
1909
|
"TOOL_DISABLED",
|
|
3871
|
-
"
|
|
1910
|
+
"get_local_job_output requires the controller profile",
|
|
3872
1911
|
);
|
|
3873
|
-
const
|
|
3874
|
-
|
|
3875
|
-
|
|
3876
|
-
|
|
3877
|
-
|
|
3878
|
-
);
|
|
3879
|
-
|
|
3880
|
-
return textResult(projectIssueEffectiveView(ctx.repoRoot, issue));
|
|
1912
|
+
const jobId = String(args.job_id ?? "").trim();
|
|
1913
|
+
const payload = readLocalBridgeJobOutput(ctx.repoRoot, jobId, {
|
|
1914
|
+
stream: args.stream === "stderr" ? "stderr" : "stdout",
|
|
1915
|
+
maxBytes: typeof args.max_bytes === "number" ? args.max_bytes : undefined,
|
|
1916
|
+
});
|
|
1917
|
+
audit(ctx, name, "ok", args);
|
|
1918
|
+
return textResult(payload);
|
|
3881
1919
|
}
|
|
3882
|
-
case "
|
|
3883
|
-
if (ctx.policy.profile !== "controller")
|
|
3884
|
-
|
|
3885
|
-
|
|
3886
|
-
|
|
3887
|
-
|
|
3888
|
-
|
|
3889
|
-
|
|
3890
|
-
const
|
|
3891
|
-
ctx.repoRoot,
|
|
3892
|
-
|
|
3893
|
-
|
|
3894
|
-
{
|
|
3895
|
-
|
|
3896
|
-
|
|
3897
|
-
|
|
3898
|
-
|
|
3899
|
-
|
|
3900
|
-
|
|
3901
|
-
|
|
3902
|
-
|
|
3903
|
-
|
|
3904
|
-
|
|
1920
|
+
case "project_snapshot": {
|
|
1921
|
+
if (ctx.policy.profile !== "controller") return errorResult("TOOL_DISABLED", "project_snapshot requires the controller profile");
|
|
1922
|
+
const markerPaths = [
|
|
1923
|
+
".ai/harness/active-plan",
|
|
1924
|
+
".ai/harness/active-sprint",
|
|
1925
|
+
".ai/harness/active-worktree",
|
|
1926
|
+
"tasks/current.md",
|
|
1927
|
+
];
|
|
1928
|
+
const markers = markerPaths.map((path) => {
|
|
1929
|
+
const absolute = join(ctx.repoRoot, path);
|
|
1930
|
+
if (!existsSync(absolute)) return { path, exists: false };
|
|
1931
|
+
const raw = readFileSync(absolute, "utf-8");
|
|
1932
|
+
return {
|
|
1933
|
+
path,
|
|
1934
|
+
exists: true,
|
|
1935
|
+
bytes: Buffer.byteLength(raw),
|
|
1936
|
+
lines: raw.split(/\r?\n/).length,
|
|
1937
|
+
sha256: createHash("sha256").update(raw).digest("hex").slice(0, 16),
|
|
1938
|
+
preview: redactMcpText(raw).text.slice(0, 400),
|
|
1939
|
+
};
|
|
1940
|
+
});
|
|
1941
|
+
const controllerHome = resolveRepoPreferredControllerHome(ctx.repoRoot);
|
|
1942
|
+
const requirementBoard = buildRequirementBoard({ controllerHome, repoId: ctx.repoId });
|
|
1943
|
+
const executionDiagnostics = buildExecutionDiagnostics({
|
|
1944
|
+
controllerHome,
|
|
1945
|
+
repoId: ctx.repoId,
|
|
1946
|
+
detailLevel: "detail",
|
|
1947
|
+
});
|
|
1948
|
+
const snapshotRuns = listAgentJobs(ctx.repoRoot, 11);
|
|
1949
|
+
const runs = snapshotRuns.slice(0, 10).map((run) => ({
|
|
1950
|
+
runId: run.runId,
|
|
1951
|
+
issueId: run.issueId,
|
|
1952
|
+
taskId: run.taskId,
|
|
1953
|
+
agent: run.agent,
|
|
1954
|
+
provider: run.provider,
|
|
1955
|
+
executionMode: run.executionMode,
|
|
1956
|
+
status: run.status,
|
|
1957
|
+
progress: run.progress,
|
|
1958
|
+
createdAt: run.createdAt,
|
|
1959
|
+
finishedAt: run.finishedAt,
|
|
1960
|
+
error: run.error?.slice(0, 300),
|
|
1961
|
+
}));
|
|
3905
1962
|
const payload = {
|
|
3906
|
-
|
|
3907
|
-
|
|
3908
|
-
|
|
3909
|
-
|
|
1963
|
+
git: gitSnapshot(ctx.repoRoot),
|
|
1964
|
+
requirementBoard,
|
|
1965
|
+
executionDiagnostics,
|
|
1966
|
+
legacyCompatibility: {
|
|
1967
|
+
currentIssue: "deprecated_frozen",
|
|
1968
|
+
issueTaskBoard: "deprecated_frozen",
|
|
1969
|
+
authority: "controller-home-sqlite",
|
|
1970
|
+
},
|
|
1971
|
+
runs,
|
|
1972
|
+
markers,
|
|
1973
|
+
truncated: {
|
|
1974
|
+
runs: snapshotRuns.length > 10,
|
|
3910
1975
|
},
|
|
3911
1976
|
};
|
|
3912
|
-
|
|
1977
|
+
audit(ctx, name, "ok", args);
|
|
3913
1978
|
return textResult(payload);
|
|
3914
1979
|
}
|
|
3915
|
-
case "
|
|
3916
|
-
if (ctx.policy.profile !== "controller") return errorResult("TOOL_DISABLED", "dispatch_task requires the controller profile");
|
|
3917
|
-
audit(ctx, name, "blocked", args);
|
|
3918
|
-
return errorResult(
|
|
3919
|
-
"AGENT_RUN_DEPRECATED",
|
|
3920
|
-
"Kernel-managed Agent Runs are retired. Create or claim a WorkContract through rh_work, then start an external SuperController session through the Thin Launcher.",
|
|
3921
|
-
);
|
|
3922
|
-
}
|
|
3923
|
-
case "launch_issue": {
|
|
3924
|
-
if (ctx.policy.profile !== "controller") return errorResult("TOOL_DISABLED", "launch_issue requires the controller profile");
|
|
3925
|
-
audit(ctx, name, "blocked", args);
|
|
3926
|
-
return errorResult(
|
|
3927
|
-
"AGENT_RUN_DEPRECATED",
|
|
3928
|
-
"Kernel-managed Agent Runs are retired. Convert the Issue tasks to WorkContracts and claim them through rh_work.",
|
|
3929
|
-
);
|
|
3930
|
-
}
|
|
3931
|
-
case "dispatch_ready_tasks": {
|
|
3932
|
-
if (ctx.policy.profile !== "controller") return errorResult("TOOL_DISABLED", "dispatch_ready_tasks requires the controller profile");
|
|
3933
|
-
audit(ctx, name, "blocked", args);
|
|
3934
|
-
return errorResult(
|
|
3935
|
-
"AGENT_RUN_DEPRECATED",
|
|
3936
|
-
"Kernel-managed batch dispatch is retired. Convert ready Tasks into WorkContracts, claim them explicitly, and launch peer external Controllers through the Thin Launcher.",
|
|
3937
|
-
{
|
|
3938
|
-
requestedIssue: typeof args.issue_id === "string" && args.issue_id.trim() ? args.issue_id.trim() : undefined,
|
|
3939
|
-
currentFocus: loadControllerProjectState(ctx.repoRoot).currentIssueId,
|
|
3940
|
-
focusIsInformational: true,
|
|
3941
|
-
},
|
|
3942
|
-
);
|
|
3943
|
-
}
|
|
3944
|
-
case "get_task_run": {
|
|
3945
|
-
if (ctx.policy.profile !== "controller")
|
|
3946
|
-
return errorResult(
|
|
3947
|
-
"TOOL_DISABLED",
|
|
3948
|
-
"get_task_run requires the controller profile",
|
|
3949
|
-
);
|
|
3950
|
-
const run = getAgentJob(ctx.repoRoot, String(args.run_id ?? ""));
|
|
3951
|
-
audit(ctx, name, "ok", args, run.promptPath);
|
|
3952
|
-
return textResult(summarizeAgentRun(ctx.repoRoot, run as unknown as Record<string, unknown>, {
|
|
3953
|
-
includePaths: args.include_paths === true,
|
|
3954
|
-
includeOutput: args.include_output !== false,
|
|
3955
|
-
tailChars: typeof args.tail_chars === "number" ? Math.trunc(args.tail_chars) : undefined,
|
|
3956
|
-
}));
|
|
3957
|
-
}
|
|
3958
|
-
case "get_task_run_events": {
|
|
1980
|
+
case "search_repository": {
|
|
3959
1981
|
if (ctx.policy.profile !== "controller")
|
|
3960
1982
|
return errorResult(
|
|
3961
1983
|
"TOOL_DISABLED",
|
|
3962
|
-
"
|
|
1984
|
+
"search_repository requires the controller profile",
|
|
3963
1985
|
);
|
|
3964
|
-
const
|
|
3965
|
-
?
|
|
1986
|
+
const session = ctx.sessionId && ctx.repoId
|
|
1987
|
+
? {
|
|
1988
|
+
sessionId: ctx.sessionId,
|
|
1989
|
+
repoId: ctx.repoId,
|
|
1990
|
+
checkoutId: ctx.checkoutId ?? ctx.repoId,
|
|
1991
|
+
}
|
|
3966
1992
|
: undefined;
|
|
3967
|
-
const
|
|
3968
|
-
|
|
3969
|
-
|
|
3970
|
-
|
|
3971
|
-
|
|
3972
|
-
|
|
3973
|
-
|
|
3974
|
-
|
|
3975
|
-
|
|
3976
|
-
|
|
3977
|
-
return textResult({
|
|
3978
|
-
events,
|
|
3979
|
-
// Keep an existing cursor stable when no new event is available. New events
|
|
3980
|
-
// that race with a read are returned on the next call instead of being lost.
|
|
3981
|
-
nextSinceEventIndex: events.at(-1)?.data?.eventIndex ?? sinceEventIndex,
|
|
3982
|
-
heartbeatsCollapsed: args.include_heartbeats !== true,
|
|
1993
|
+
const result = searchRepository(ctx.repoRoot, ctx.policy, {
|
|
1994
|
+
query: String(args.query ?? ""),
|
|
1995
|
+
includeGlobs: stringList(args.include_globs),
|
|
1996
|
+
excludeGlobs: stringList(args.exclude_globs),
|
|
1997
|
+
maxResults:
|
|
1998
|
+
typeof args.max_results === "number" ? args.max_results : undefined,
|
|
1999
|
+
maxFiles:
|
|
2000
|
+
typeof args.max_files === "number" ? args.max_files : undefined,
|
|
2001
|
+
caseSensitive: args.case_sensitive === true,
|
|
2002
|
+
session,
|
|
3983
2003
|
});
|
|
2004
|
+
audit(ctx, name, "ok", args);
|
|
2005
|
+
return textResult(result);
|
|
3984
2006
|
}
|
|
3985
|
-
case "
|
|
2007
|
+
case "get_git_diff": {
|
|
3986
2008
|
if (ctx.policy.profile !== "controller")
|
|
3987
2009
|
return errorResult(
|
|
3988
2010
|
"TOOL_DISABLED",
|
|
3989
|
-
"
|
|
2011
|
+
"get_git_diff requires the controller profile",
|
|
3990
2012
|
);
|
|
3991
|
-
const
|
|
3992
|
-
|
|
3993
|
-
|
|
3994
|
-
|
|
3995
|
-
|
|
3996
|
-
|
|
3997
|
-
|
|
3998
|
-
|
|
3999
|
-
|
|
4000
|
-
|
|
4001
|
-
|
|
4002
|
-
|
|
4003
|
-
|
|
4004
|
-
|
|
4005
|
-
|
|
4006
|
-
|
|
4007
|
-
|
|
4008
|
-
|
|
4009
|
-
|
|
4010
|
-
|
|
4011
|
-
|
|
4012
|
-
|
|
4013
|
-
|
|
4014
|
-
);
|
|
2013
|
+
const path =
|
|
2014
|
+
typeof args.path === "string" && args.path.trim()
|
|
2015
|
+
? args.path.trim()
|
|
2016
|
+
: undefined;
|
|
2017
|
+
if (path) {
|
|
2018
|
+
const decision = resolveMcpPath(
|
|
2019
|
+
ctx.repoRoot,
|
|
2020
|
+
path,
|
|
2021
|
+
ctx.policy,
|
|
2022
|
+
"read",
|
|
2023
|
+
);
|
|
2024
|
+
if (!decision.ok)
|
|
2025
|
+
return errorResult(
|
|
2026
|
+
"POLICY_DENIED",
|
|
2027
|
+
decision.reason ?? "path denied",
|
|
2028
|
+
{ path },
|
|
2029
|
+
);
|
|
2030
|
+
}
|
|
2031
|
+
const maxBytes =
|
|
2032
|
+
typeof args.max_bytes === "number"
|
|
2033
|
+
? Math.min(Math.max(Math.trunc(args.max_bytes), 1024), 512 * 1024)
|
|
2034
|
+
: 128 * 1024;
|
|
2035
|
+
const result = gitDiff(ctx.repoRoot, path, maxBytes);
|
|
2036
|
+
const redacted = redactMcpText(result.diff);
|
|
2037
|
+
audit(ctx, name, "ok", args, path);
|
|
4015
2038
|
return textResult({
|
|
4016
|
-
...
|
|
4017
|
-
|
|
4018
|
-
|
|
4019
|
-
redactions: bounded.redactions,
|
|
4020
|
-
next: bounded.truncated
|
|
4021
|
-
? "Call get_task_run_log with a larger max_bytes value for more retained tail."
|
|
4022
|
-
: undefined,
|
|
2039
|
+
...result,
|
|
2040
|
+
diff: redacted.text,
|
|
2041
|
+
redactions: redacted.redactions,
|
|
4023
2042
|
});
|
|
4024
2043
|
}
|
|
4025
|
-
case "
|
|
2044
|
+
case "list_checks": {
|
|
4026
2045
|
if (ctx.policy.profile !== "controller")
|
|
4027
2046
|
return errorResult(
|
|
4028
2047
|
"TOOL_DISABLED",
|
|
4029
|
-
"
|
|
2048
|
+
"list_checks requires the controller profile",
|
|
4030
2049
|
);
|
|
4031
|
-
const
|
|
4032
|
-
|
|
4033
|
-
|
|
4034
|
-
typeof args.max_bytes === "number"
|
|
4035
|
-
? Math.min(Math.max(Math.trunc(args.max_bytes), 1024), 1024 * 1024)
|
|
4036
|
-
: undefined,
|
|
4037
|
-
);
|
|
4038
|
-
audit(ctx, name, "ok", args, diff.worktree);
|
|
4039
|
-
return textResult(diff);
|
|
2050
|
+
const checks = listControllerChecks(ctx.repoRoot);
|
|
2051
|
+
audit(ctx, name, "ok", args);
|
|
2052
|
+
return textResult({ checks });
|
|
4040
2053
|
}
|
|
4041
|
-
case "
|
|
2054
|
+
case "get_worklog_timeline": {
|
|
4042
2055
|
if (ctx.policy.profile !== "controller")
|
|
4043
|
-
return errorResult(
|
|
4044
|
-
|
|
4045
|
-
|
|
4046
|
-
|
|
4047
|
-
|
|
4048
|
-
|
|
4049
|
-
|
|
4050
|
-
|
|
4051
|
-
|
|
4052
|
-
|
|
4053
|
-
|
|
4054
|
-
|
|
4055
|
-
|
|
4056
|
-
args,
|
|
4057
|
-
`controller-home://repositories/${editExecutionIdentity(ctx).repoId}/edit-sessions/${integrated.session.sessionId}`,
|
|
4058
|
-
);
|
|
4059
|
-
return textResult(integrated);
|
|
2056
|
+
return errorResult("TOOL_DISABLED", "get_worklog_timeline requires the controller profile");
|
|
2057
|
+
const events = listControllerWorklogEvents(ctx.repoRoot, {
|
|
2058
|
+
category: parseWorklogCategory(args.category),
|
|
2059
|
+
issueId: typeof args.issue_id === "string" ? args.issue_id : undefined,
|
|
2060
|
+
taskId: typeof args.task_id === "string" ? args.task_id : undefined,
|
|
2061
|
+
runId: typeof args.run_id === "string" ? args.run_id : undefined,
|
|
2062
|
+
editSessionId: typeof args.edit_session_id === "string" ? args.edit_session_id : undefined,
|
|
2063
|
+
since: typeof args.since === "string" ? args.since : undefined,
|
|
2064
|
+
until: typeof args.until === "string" ? args.until : undefined,
|
|
2065
|
+
limit: typeof args.limit === "number" ? Math.trunc(args.limit) : undefined,
|
|
2066
|
+
});
|
|
2067
|
+
audit(ctx, name, "ok", args);
|
|
2068
|
+
return textResult({ events });
|
|
4060
2069
|
}
|
|
4061
|
-
case "
|
|
2070
|
+
case "export_worklog": {
|
|
4062
2071
|
if (ctx.policy.profile !== "controller")
|
|
4063
|
-
return errorResult(
|
|
4064
|
-
|
|
4065
|
-
|
|
4066
|
-
|
|
4067
|
-
|
|
4068
|
-
|
|
4069
|
-
|
|
4070
|
-
|
|
4071
|
-
|
|
4072
|
-
|
|
4073
|
-
runs: summarizeRunList(
|
|
4074
|
-
ctx.repoRoot,
|
|
4075
|
-
runs as unknown as Array<Record<string, unknown>>,
|
|
4076
|
-
args.include_paths === true,
|
|
4077
|
-
),
|
|
2072
|
+
return errorResult("TOOL_DISABLED", "export_worklog requires the controller profile");
|
|
2073
|
+
const result = exportControllerWorklog(ctx.repoRoot, {
|
|
2074
|
+
format: args.format === "json" ? "json" : "markdown",
|
|
2075
|
+
outputPath: typeof args.output_path === "string" ? args.output_path : undefined,
|
|
2076
|
+
filter: {
|
|
2077
|
+
issueId: typeof args.issue_id === "string" ? args.issue_id : undefined,
|
|
2078
|
+
taskId: typeof args.task_id === "string" ? args.task_id : undefined,
|
|
2079
|
+
runId: typeof args.run_id === "string" ? args.run_id : undefined,
|
|
2080
|
+
editSessionId: typeof args.edit_session_id === "string" ? args.edit_session_id : undefined,
|
|
2081
|
+
},
|
|
4078
2082
|
});
|
|
2083
|
+
audit(ctx, name, "ok", args, result.path);
|
|
2084
|
+
return textResult(result);
|
|
4079
2085
|
}
|
|
4080
|
-
case "
|
|
2086
|
+
case "get_github_plugin_status": {
|
|
4081
2087
|
if (ctx.policy.profile !== "controller")
|
|
4082
|
-
return errorResult(
|
|
4083
|
-
|
|
4084
|
-
|
|
4085
|
-
|
|
4086
|
-
const run = cancelAgentJob(ctx.repoRoot, String(args.run_id ?? ""));
|
|
4087
|
-
audit(ctx, name, "ok", args, run.promptPath);
|
|
4088
|
-
return textResult(run);
|
|
2088
|
+
return errorResult("TOOL_DISABLED", "get_github_plugin_status requires the controller profile");
|
|
2089
|
+
const status = getGitHubPluginStatus(ctx.repoRoot);
|
|
2090
|
+
audit(ctx, name, "ok", args, status.repository);
|
|
2091
|
+
return textResult(status);
|
|
4089
2092
|
}
|
|
4090
|
-
case "
|
|
2093
|
+
case "configure_github_plugin": {
|
|
4091
2094
|
if (ctx.policy.profile !== "controller")
|
|
4092
|
-
return errorResult(
|
|
4093
|
-
|
|
4094
|
-
|
|
4095
|
-
|
|
4096
|
-
|
|
4097
|
-
|
|
4098
|
-
|
|
4099
|
-
"
|
|
4100
|
-
"Kernel-managed Agent Run retry is disabled. Create or claim a WorkContract and start an external SuperController through Thin Launcher.",
|
|
4101
|
-
);
|
|
4102
|
-
}
|
|
4103
|
-
case "finish_task_run": {
|
|
4104
|
-
if (ctx.policy.profile !== "controller") return errorResult("TOOL_DISABLED", "finish_task_run requires the controller profile");
|
|
4105
|
-
const runId = String(args.run_id ?? "").trim();
|
|
4106
|
-
if (!runId) return errorResult("RUN_ID_REQUIRED", "finish_task_run requires run_id");
|
|
4107
|
-
const decision = typeof args.decision === "string" ? args.decision : "auto";
|
|
4108
|
-
if (!["auto", "approve_and_finish", "request_changes", "discard"].includes(decision)) {
|
|
4109
|
-
return errorResult("INVALID_DECISION", "decision must be auto, approve_and_finish, request_changes, or discard");
|
|
4110
|
-
}
|
|
4111
|
-
const finished = finishTaskRun(ctx.repoRoot, {
|
|
4112
|
-
runId,
|
|
4113
|
-
decision: decision as "auto" | "approve_and_finish" | "request_changes" | "discard",
|
|
4114
|
-
reviewer: typeof args.reviewer === "string" ? args.reviewer : undefined,
|
|
4115
|
-
note: typeof args.note === "string" ? args.note : undefined,
|
|
4116
|
-
cleanup: typeof args.cleanup === "boolean" ? args.cleanup : undefined,
|
|
4117
|
-
commit: typeof args.commit === "boolean" ? args.commit : undefined,
|
|
4118
|
-
});
|
|
4119
|
-
audit(ctx, name, ["blocked", "needs_decision"].includes(finished.action) ? "failed" : "ok", args, `tasks/issues/${finished.issueId}`, finished.reason);
|
|
4120
|
-
return textResult(finished);
|
|
4121
|
-
}
|
|
4122
|
-
case "verify_task": {
|
|
4123
|
-
if (ctx.policy.profile !== "controller") return errorResult("TOOL_DISABLED", "verify_task requires the controller profile");
|
|
4124
|
-
const issueId = String(args.issue_id ?? "");
|
|
4125
|
-
const taskId = String(args.task_id ?? "");
|
|
4126
|
-
const current = getIssue(ctx.repoRoot, issueId);
|
|
4127
|
-
const task = current.tasks.find((entry) => entry.id === taskId);
|
|
4128
|
-
if (!task) return errorResult("TASK_NOT_FOUND", `task not found: ${issueId}/${taskId}`);
|
|
4129
|
-
const policy = taskExecutionPolicy(task);
|
|
4130
|
-
const evidenceRunId = typeof args.run_id === "string" ? args.run_id : task.runIds.at(-1);
|
|
4131
|
-
let evidenceRun: ReturnType<typeof getAgentJob> | undefined;
|
|
4132
|
-
if (evidenceRunId) {
|
|
4133
|
-
evidenceRun = getAgentJob(ctx.repoRoot, evidenceRunId);
|
|
4134
|
-
if (evidenceRun.issueId !== issueId || evidenceRun.taskId !== taskId) return errorResult("RUN_TASK_MISMATCH", `Run ${evidenceRunId} does not belong to ${issueId}/${taskId}`);
|
|
4135
|
-
if (evidenceRun.status !== "succeeded") return errorResult("RUN_NOT_SUCCEEDED", `verification requires a succeeded Run (current: ${evidenceRun.status})`);
|
|
4136
|
-
if (evidenceRun.provider === "local" && evidenceRun.worktree !== ctx.repoRoot && !evidenceRun.integratedSessionId) return errorResult("RUN_NOT_INTEGRATED", "integrate the isolated local Run before recording verification");
|
|
4137
|
-
if (evidenceRun.provider === "github" && evidenceRun.github?.createPullRequest !== false && !evidenceRun.github?.pullRequestUrl) return errorResult("GITHUB_PR_NOT_READY", "GitHub cloud verification requires the linked pull request to be available");
|
|
4138
|
-
}
|
|
4139
|
-
|
|
4140
|
-
const requestedCheckInputs = taskObjects(args.check_results).map((entry) => ({
|
|
4141
|
-
checkId: String(entry.check_id ?? "").trim(),
|
|
4142
|
-
requestedSummary: typeof entry.summary === "string" ? entry.summary : undefined,
|
|
4143
|
-
})).filter((entry) => entry.checkId);
|
|
4144
|
-
const registryChecks = listControllerChecks(ctx.repoRoot);
|
|
4145
|
-
const requestedCheckIds = task.checks.length > 0 ? task.checks : requestedCheckInputs.map((entry) => entry.checkId);
|
|
4146
|
-
const normalizedChecks = normalizeCheckIds(requestedCheckIds, registryChecks);
|
|
4147
|
-
const checkIds = normalizedChecks.validCheckIds;
|
|
4148
|
-
const checkResults: TaskVerification['checkResults'] = [];
|
|
4149
|
-
if (checkIds.length > 0) {
|
|
4150
|
-
const controllerHome = resolveRepoPreferredControllerHome(ctx.repoRoot);
|
|
4151
|
-
const repository = resolveRepositorySelection({
|
|
4152
|
-
explicitPath: ctx.repoRoot,
|
|
4153
|
-
controllerHome,
|
|
4154
|
-
allowSoleRepository: true,
|
|
4155
|
-
});
|
|
4156
|
-
if (!repository) return errorResult('TASK_CHECK_REPOSITORY_UNRESOLVED', 'verify_task could not resolve the repository checkout');
|
|
4157
|
-
const taskCheckRequestBase = typeof args.request_id === 'string' && args.request_id.trim()
|
|
4158
|
-
? args.request_id.trim()
|
|
4159
|
-
: `verify-task:${issueId}:${taskId}:${createHash('sha256').update(JSON.stringify({ checkIds, revision: args.integrated_revision ?? evidenceRun?.integratedSessionId ?? 'current' })).digest('hex').slice(0, 16)}`;
|
|
4160
|
-
for (const [index, checkId] of checkIds.entries()) {
|
|
4161
|
-
const processRequestId = `${taskCheckRequestBase}:check:${index + 1}:${createHash('sha256').update(checkId).digest('hex').slice(0, 12)}`;
|
|
4162
|
-
const facade = await runPersistedCheckViaProcessRuntime({
|
|
4163
|
-
controllerHome,
|
|
4164
|
-
repoId: repository.repoId,
|
|
4165
|
-
checkoutId: repository.activeCheckoutId,
|
|
4166
|
-
repoRoot: repository.canonicalRoot,
|
|
4167
|
-
executionIdentity: executionIdentityForRepository(repository),
|
|
4168
|
-
checkId,
|
|
4169
|
-
requestId: processRequestId,
|
|
4170
|
-
commandId: processRequestId,
|
|
4171
|
-
verificationBinding: { issueId, taskId },
|
|
4172
|
-
});
|
|
4173
|
-
if (facade.mode === 'durable' || !facade.process) {
|
|
4174
|
-
return errorResult('TASK_CHECK_DURABLE_REQUIRED', facade.durable?.reason ?? `check requires durable workflow: ${checkId}`);
|
|
4175
|
-
}
|
|
4176
|
-
if (!facade.process.completed) {
|
|
4177
|
-
return textResult({
|
|
4178
|
-
accepted: true,
|
|
4179
|
-
status: 'verification_running',
|
|
4180
|
-
issueId,
|
|
4181
|
-
taskId,
|
|
4182
|
-
checkId,
|
|
4183
|
-
requestId: taskCheckRequestBase,
|
|
4184
|
-
processId: facade.process.processId,
|
|
4185
|
-
next: `Retry verify_task with request_id=${taskCheckRequestBase}; the original Process will be reused.`,
|
|
4186
|
-
durableSideEffects: { executionJobCount: 0, localJobCount: 0, workerSpawnCount: 0, projectionUpdateCount: 0 },
|
|
4187
|
-
});
|
|
4188
|
-
}
|
|
4189
|
-
const record = getProcessRecord(controllerHome, repository.repoId, facade.process.processId);
|
|
4190
|
-
if (!record) return errorResult('TASK_CHECK_PROCESS_MISSING', `process record not found: ${facade.process.processId}`);
|
|
4191
|
-
const leaseConflict = taskVerificationLeaseConflict(record);
|
|
4192
|
-
if (leaseConflict) {
|
|
4193
|
-
const retryRequestId = taskVerificationRetryRequestId(
|
|
4194
|
-
taskCheckRequestBase,
|
|
4195
|
-
checkId,
|
|
4196
|
-
facade.process.processId,
|
|
4197
|
-
);
|
|
4198
|
-
return textResult({
|
|
4199
|
-
accepted: true,
|
|
4200
|
-
status: 'verification_deferred',
|
|
4201
|
-
reason: 'repository_resource_busy',
|
|
4202
|
-
issueId,
|
|
4203
|
-
taskId,
|
|
4204
|
-
checkId,
|
|
4205
|
-
requestId: taskCheckRequestBase,
|
|
4206
|
-
processId: facade.process.processId,
|
|
4207
|
-
conflict: {
|
|
4208
|
-
code: 'PROCESS_LEASE_CONFLICT',
|
|
4209
|
-
resourceKey: leaseConflict.resourceKey,
|
|
4210
|
-
blockingProcessId: leaseConflict.blockingProcessId,
|
|
4211
|
-
},
|
|
4212
|
-
retryRequestId,
|
|
4213
|
-
next: `Retry verify_task with request_id=${retryRequestId} after the conflicting repository process settles.`,
|
|
4214
|
-
durableSideEffects: { executionJobCount: 0, localJobCount: 0, workerSpawnCount: 0, projectionUpdateCount: 0 },
|
|
4215
|
-
});
|
|
4216
|
-
}
|
|
4217
|
-
const receipt = processCheckCompletionReceipt(record, {
|
|
4218
|
-
repoId: repository.repoId,
|
|
4219
|
-
checkoutId: repository.activeCheckoutId,
|
|
4220
|
-
issueId,
|
|
4221
|
-
taskId,
|
|
4222
|
-
checkId,
|
|
4223
|
-
processId: facade.process.processId,
|
|
4224
|
-
});
|
|
4225
|
-
checkResults.push({
|
|
4226
|
-
checkId,
|
|
4227
|
-
ok: receipt.ok,
|
|
4228
|
-
summary: receipt.summary,
|
|
4229
|
-
receipt,
|
|
4230
|
-
});
|
|
4231
|
-
}
|
|
4232
|
-
}
|
|
4233
|
-
|
|
4234
|
-
const commandEvidence: TaskCommandEvidence[] = [];
|
|
4235
|
-
for (const entry of taskObjects(args.reported_commands)) {
|
|
4236
|
-
const command = Array.isArray(entry.command) ? entry.command.map(String).map((part) => part.trim()).filter(Boolean) : [];
|
|
4237
|
-
if (command.length === 0) return errorResult("INVALID_COMMAND_EVIDENCE", "reported command evidence requires a non-empty command argv");
|
|
4238
|
-
const cwdInput = typeof entry.cwd === "string" && entry.cwd.trim() ? entry.cwd.trim() : undefined;
|
|
4239
|
-
if (cwdInput && cwdInput !== "." && cwdInput !== "./") {
|
|
4240
|
-
const cwdDecision = resolveMcpPath(ctx.repoRoot, cwdInput, ctx.policy, "read");
|
|
4241
|
-
if (!cwdDecision.ok) {
|
|
4242
|
-
const normalizedCwd = normalizeMcpRelativePath(cwdInput);
|
|
4243
|
-
const missingHarnessEvidence = normalizedCwd.ok &&
|
|
4244
|
-
normalizedCwd.relativePath?.startsWith(".ai/harness/") &&
|
|
4245
|
-
cwdDecision.reason?.startsWith("path does not exist:");
|
|
4246
|
-
if (!missingHarnessEvidence) return errorResult("COMMAND_EVIDENCE_PATH_DENIED", cwdDecision.reason ?? "reported command cwd is denied");
|
|
4247
|
-
}
|
|
4248
|
-
}
|
|
4249
|
-
const artifactInput = typeof entry.artifact_path === "string" && entry.artifact_path.trim() ? entry.artifact_path.trim() : undefined;
|
|
4250
|
-
if (artifactInput) {
|
|
4251
|
-
const artifactDecision = resolveMcpPath(ctx.repoRoot, artifactInput, ctx.policy, "read");
|
|
4252
|
-
if (!artifactDecision.ok) {
|
|
4253
|
-
const normalizedArtifact = normalizeMcpRelativePath(artifactInput);
|
|
4254
|
-
const missingHarnessEvidence = normalizedArtifact.ok &&
|
|
4255
|
-
normalizedArtifact.relativePath?.startsWith(".ai/harness/") &&
|
|
4256
|
-
artifactDecision.reason?.startsWith("path does not exist:");
|
|
4257
|
-
if (!missingHarnessEvidence) return errorResult("COMMAND_EVIDENCE_ARTIFACT_DENIED", artifactDecision.reason ?? "reported command artifact is denied");
|
|
4258
|
-
}
|
|
4259
|
-
}
|
|
4260
|
-
commandEvidence.push({
|
|
4261
|
-
command,
|
|
4262
|
-
cwd: cwdInput,
|
|
4263
|
-
ok: entry.ok === true,
|
|
4264
|
-
exitCode: typeof entry.exit_code === "number" ? Math.trunc(entry.exit_code) : undefined,
|
|
4265
|
-
stdout: typeof entry.stdout === "string" ? entry.stdout.slice(-32 * 1024) : undefined,
|
|
4266
|
-
stderr: typeof entry.stderr === "string" ? entry.stderr.slice(-32 * 1024) : undefined,
|
|
4267
|
-
artifactPath: artifactInput,
|
|
4268
|
-
reportedBy: String(args.reviewer ?? "").trim(),
|
|
4269
|
-
executedAt: typeof entry.executed_at === "string" ? entry.executed_at : new Date().toISOString(),
|
|
4270
|
-
source: "reported",
|
|
4271
|
-
});
|
|
4272
|
-
}
|
|
4273
|
-
|
|
4274
|
-
let acceptanceResults: TaskVerification["acceptanceResults"] = taskObjects(args.acceptance_results).map((entry) => ({
|
|
4275
|
-
criterion: String(entry.criterion ?? ""),
|
|
4276
|
-
ok: entry.ok === true,
|
|
4277
|
-
outcome: entry.ok === true ? "passed" as const : "failed" as const,
|
|
4278
|
-
source: "reported" as const,
|
|
4279
|
-
evidence: typeof entry.evidence === "string" ? entry.evidence : undefined,
|
|
4280
|
-
}));
|
|
4281
|
-
if (acceptanceResults.length === 0 && evidenceRun && task.acceptanceCriteria.length > 0) {
|
|
4282
|
-
acceptanceResults = task.acceptanceCriteria.map((criterion) => ({
|
|
4283
|
-
criterion,
|
|
4284
|
-
ok: false,
|
|
4285
|
-
outcome: "not_evaluated" as const,
|
|
4286
|
-
source: "run_completion" as const,
|
|
4287
|
-
evidence: `Successful Run ${evidenceRun!.runId}; acceptance was not independently evaluated.`,
|
|
4288
|
-
}));
|
|
4289
|
-
}
|
|
4290
|
-
const verification: TaskVerification = {
|
|
4291
|
-
runId: evidenceRunId,
|
|
4292
|
-
integratedRevision: typeof args.integrated_revision === "string" ? args.integrated_revision : evidenceRun?.integratedSessionId,
|
|
4293
|
-
reviewedDiffHash: typeof args.reviewed_diff_hash === "string" ? args.reviewed_diff_hash : undefined,
|
|
4294
|
-
reviewer: String(args.reviewer ?? ""),
|
|
4295
|
-
checkResults,
|
|
4296
|
-
commandEvidence,
|
|
4297
|
-
acceptanceResults,
|
|
4298
|
-
verifiedAt: new Date().toISOString(),
|
|
4299
|
-
};
|
|
4300
|
-
const issue = recordTaskVerification(ctx.repoRoot, issueId, taskId, verification);
|
|
4301
|
-
const issueView = projectIssueEffectiveView(ctx.repoRoot, issue);
|
|
4302
|
-
const verifiedTask = issueView.tasks.find((entry) => entry.id === taskId);
|
|
4303
|
-
audit(ctx, name, ["verified", "done"].includes(verifiedTask?.status ?? "") ? "ok" : "failed", args, `tasks/issues/${issue.id}`);
|
|
4304
|
-
return textResult({
|
|
4305
|
-
issueId,
|
|
4306
|
-
taskId,
|
|
4307
|
-
policy,
|
|
4308
|
-
checkNormalization: {
|
|
4309
|
-
validCheckIds: normalizedChecks.validCheckIds,
|
|
4310
|
-
invalidCheckIds: normalizedChecks.invalidCheckIds,
|
|
4311
|
-
warnings: normalizedChecks.warnings,
|
|
4312
|
-
},
|
|
4313
|
-
task: verifiedTask ? compactTaskView(verifiedTask as unknown as Record<string, unknown>) : undefined,
|
|
4314
|
-
tasks: issueView.tasks.map((entry) => compactTaskView(entry as unknown as Record<string, unknown>)),
|
|
4315
|
-
issue: compactIssueView(issueView as unknown as Record<string, unknown>),
|
|
2095
|
+
return errorResult("TOOL_DISABLED", "configure_github_plugin requires the controller profile");
|
|
2096
|
+
const config = saveGitHubPluginConfig(ctx.repoRoot, {
|
|
2097
|
+
enabled: typeof args.enabled === "boolean" ? args.enabled : undefined,
|
|
2098
|
+
repository: args.clear_repository === true ? "" : typeof args.repository === "string" ? args.repository : undefined,
|
|
2099
|
+
syncMode: args.sync_mode === "checkpoint" ? "checkpoint" : args.sync_mode === "manual" ? "manual" : undefined,
|
|
2100
|
+
includeTasks: typeof args.include_tasks === "boolean" ? args.include_tasks : undefined,
|
|
2101
|
+
projectOwner: args.clear_project === true ? "" : typeof args.project_owner === "string" ? args.project_owner : undefined,
|
|
2102
|
+
projectNumber: args.clear_project === true ? null : typeof args.project_number === "number" ? args.project_number : undefined,
|
|
4316
2103
|
});
|
|
2104
|
+
audit(ctx, name, "ok", args, "repository-registry:github");
|
|
2105
|
+
return textResult(config);
|
|
4317
2106
|
}
|
|
4318
|
-
case "
|
|
4319
|
-
if (ctx.policy.profile !== "controller") return errorResult("TOOL_DISABLED", "accept_task requires the controller profile");
|
|
4320
|
-
const issueId = String(args.issue_id ?? "");
|
|
4321
|
-
const taskId = String(args.task_id ?? "");
|
|
4322
|
-
const current = getIssue(ctx.repoRoot, issueId);
|
|
4323
|
-
const task = current.tasks.find((entry) => entry.id === taskId);
|
|
4324
|
-
if (!task) return errorResult("TASK_NOT_FOUND", `task not found: ${issueId}/${taskId}`);
|
|
4325
|
-
if (task.status === "done") return textResult(projectIssueEffectiveView(ctx.repoRoot, current));
|
|
4326
|
-
if (task.status !== "verified" || !task.verification) return errorResult("TASK_NOT_VERIFIED", `task has not passed its risk-adaptive verification requirements (current: ${task.status})`);
|
|
4327
|
-
const issue = acceptVerifiedTask(
|
|
4328
|
-
ctx.repoRoot,
|
|
4329
|
-
issueId,
|
|
4330
|
-
taskId,
|
|
4331
|
-
typeof args.note === "string" ? args.note : "Accepted after required verification evidence.",
|
|
4332
|
-
);
|
|
4333
|
-
audit(ctx, name, "ok", args, `tasks/issues/${issue.id}`);
|
|
4334
|
-
return textResult(projectIssueEffectiveView(ctx.repoRoot, issue));
|
|
4335
|
-
}
|
|
4336
|
-
case "request_task_changes": {
|
|
2107
|
+
case "github_status": {
|
|
4337
2108
|
if (ctx.policy.profile !== "controller")
|
|
4338
2109
|
return errorResult(
|
|
4339
2110
|
"TOOL_DISABLED",
|
|
4340
|
-
"
|
|
4341
|
-
);
|
|
4342
|
-
const issueId = String(args.issue_id ?? "");
|
|
4343
|
-
const taskId = String(args.task_id ?? "");
|
|
4344
|
-
const current = getIssue(ctx.repoRoot, issueId);
|
|
4345
|
-
const task = current.tasks.find((entry) => entry.id === taskId);
|
|
4346
|
-
if (!task)
|
|
4347
|
-
return errorResult(
|
|
4348
|
-
"TASK_NOT_FOUND",
|
|
4349
|
-
`task not found: ${issueId}/${taskId}`,
|
|
4350
|
-
);
|
|
4351
|
-
if (
|
|
4352
|
-
!["review", "integrated", "verifying", "verified"].includes(
|
|
4353
|
-
task.status,
|
|
4354
|
-
)
|
|
4355
|
-
)
|
|
4356
|
-
return errorResult(
|
|
4357
|
-
"TASK_NOT_REVIEWABLE",
|
|
4358
|
-
`task must be in review, integrated, verifying, or verified before requesting changes (current: ${task.status})`,
|
|
2111
|
+
"github_status requires the controller profile",
|
|
4359
2112
|
);
|
|
4360
|
-
const
|
|
4361
|
-
|
|
4362
|
-
|
|
4363
|
-
|
|
4364
|
-
audit(
|
|
4365
|
-
|
|
2113
|
+
const status = getGitHubStatus(
|
|
2114
|
+
ctx.repoRoot,
|
|
2115
|
+
typeof args.repo === "string" ? args.repo : undefined,
|
|
2116
|
+
);
|
|
2117
|
+
audit(
|
|
2118
|
+
ctx,
|
|
2119
|
+
name,
|
|
2120
|
+
status.available && status.authenticated ? "ok" : "failed",
|
|
2121
|
+
args,
|
|
2122
|
+
status.repository?.nameWithOwner,
|
|
2123
|
+
status.errors.join("; "),
|
|
2124
|
+
);
|
|
2125
|
+
return textResult(status);
|
|
4366
2126
|
}
|
|
4367
2127
|
case "begin_edit_session": {
|
|
4368
2128
|
if (ctx.policy.profile !== "controller")
|
|
@@ -4592,29 +2352,6 @@ export async function callMcpTool(
|
|
|
4592
2352
|
);
|
|
4593
2353
|
return textResult(session);
|
|
4594
2354
|
}
|
|
4595
|
-
case "finish_edit_session": {
|
|
4596
|
-
if (ctx.policy.profile !== "controller")
|
|
4597
|
-
return errorResult(
|
|
4598
|
-
"TOOL_DISABLED",
|
|
4599
|
-
"finish_edit_session requires the controller profile",
|
|
4600
|
-
);
|
|
4601
|
-
const sessionId = String(args.session_id ?? "").trim();
|
|
4602
|
-
if (!sessionId) return errorResult("SESSION_ID_REQUIRED", "finish_edit_session requires session_id");
|
|
4603
|
-
const decision = typeof args.decision === "string" ? args.decision : "auto";
|
|
4604
|
-
if (!["auto", "approve_and_finish", "request_changes", "discard"].includes(decision)) {
|
|
4605
|
-
return errorResult("INVALID_DECISION", "decision must be auto, approve_and_finish, request_changes, or discard");
|
|
4606
|
-
}
|
|
4607
|
-
const finished = finishEditSession(ctx.repoRoot, {
|
|
4608
|
-
sessionId,
|
|
4609
|
-
decision: decision as "auto" | "approve_and_finish" | "request_changes" | "discard",
|
|
4610
|
-
reviewer: typeof args.reviewer === "string" ? args.reviewer : undefined,
|
|
4611
|
-
note: typeof args.note === "string" ? args.note : undefined,
|
|
4612
|
-
checkIds: Array.isArray(args.check_ids) ? args.check_ids.map(String) : undefined,
|
|
4613
|
-
commit: typeof args.commit === "boolean" ? args.commit : undefined,
|
|
4614
|
-
});
|
|
4615
|
-
audit(ctx, name, ["blocked", "needs_decision"].includes(finished.action) ? "failed" : "ok", args, `tasks/issues/${finished.issueId}`, finished.reason);
|
|
4616
|
-
return textResult(finished);
|
|
4617
|
-
}
|
|
4618
2355
|
case "list_workflow_files": {
|
|
4619
2356
|
const files = workflowFileCandidates(ctx.repoRoot)
|
|
4620
2357
|
.filter(
|