@moretea-labs/forge 1.5.0-rc.1 → 1.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +9 -11
- package/CHANGELOG.md +27 -1
- package/CLAUDE.md +8 -10
- package/DESIGN.md +278 -0
- package/README.md +70 -90
- package/README.zh-CN.md +52 -55
- package/SKILL.md +8 -11
- package/THIRD_PARTY_NOTICES.md +9 -3
- package/assets/branding/README.md +4 -2
- package/assets/hooks/lib/workflow-state.sh +19 -18
- package/assets/hooks/post-edit-guard.sh +6 -75
- package/assets/hooks/prompt-guard.sh +24 -12
- package/assets/hooks/session-start-context.sh +17 -17
- package/assets/hooks/stop-orchestrator.sh +2 -2
- package/assets/partials/04-project-structure.partial.md +6 -6
- package/assets/partials/05-workflow.partial.md +6 -6
- package/assets/partials/07-footer.partial.md +1 -1
- package/assets/partials/08-orchestration.partial.md +2 -2
- package/assets/partials-agents/02-operating-mode.partial.md +5 -5
- package/assets/partials-agents/03-orchestration.partial.md +3 -3
- package/assets/partials-agents/04-task-protocol.partial.md +3 -3
- package/assets/partials-agents/08-deep-docs.partial.md +1 -1
- package/assets/reference-configs/agentic-development-flow.md +49 -86
- package/assets/reference-configs/external-tooling.md +21 -61
- package/assets/reference-configs/handoff-protocol.md +25 -20
- package/assets/reference-configs/harness-overview.md +8 -8
- package/assets/skill-version.json +2 -2
- package/assets/templates/helpers/archive-workflow.sh +0 -1
- package/assets/templates/helpers/capture-plan.sh +3 -3
- package/assets/templates/helpers/check-task-workflow.sh +2 -2
- package/assets/templates/helpers/codex-handoff-resume.sh +5 -5
- package/assets/templates/helpers/ensure-task-workflow.sh +37 -35
- package/assets/templates/helpers/new-plan.sh +1 -1
- package/assets/templates/helpers/plan-to-todo.sh +2 -2
- package/assets/templates/helpers/prepare-codex-handoff.sh +2 -2
- package/assets/templates/helpers/prepare-handoff.sh +9 -9
- package/assets/templates/helpers/refresh-current-status.sh +2 -2
- package/assets/templates/helpers/ship-worktrees.sh +1 -1
- package/assets/templates/helpers/sprint-backlog.sh +1 -1
- package/assets/templates/helpers/verify-sprint.sh +7 -7
- package/assets/templates/helpers/workstream-sync.sh +1 -1
- package/assets/templates/plan.template.md +1 -1
- package/assets/workflow-contract.v1.json +6 -3
- package/docs/architecture/current/README.md +10 -9
- package/docs/architecture/current/approved-target-architecture.zh-CN.md +2 -2
- package/docs/architecture/current/architecture-invariants.md +2 -2
- package/docs/architecture/current/automation-and-schedule-engine.md +15 -15
- package/docs/architecture/current/codegraph-context-provider.md +2 -2
- package/docs/architecture/current/control-plane-authority-inventory.md +12 -0
- package/docs/architecture/current/dispatch-and-agent-strategy.md +11 -11
- package/docs/architecture/current/entity-model.md +0 -27
- package/docs/architecture/current/failure-recovery.md +0 -24
- package/docs/architecture/current/governance.md +1 -1
- package/docs/architecture/current/implementation-status.md +1 -1
- package/docs/architecture/current/legacy-governance.md +58 -0
- package/docs/architecture/current/multi-repository-execution.md +1 -1
- package/docs/architecture/current/peer-supercontroller-runtime.md +1 -1
- package/docs/architecture/current/personal-assistant-plugin-baseline.md +4 -5
- package/docs/architecture/current/scheduler-and-resource-claims.md +0 -10
- package/docs/architecture/current/target-requirements-traceability.md +2 -2
- package/docs/architecture/current/thin-harness-governance-policy.md +1 -1
- package/docs/architecture/current/thin-harness-v1.md +9 -11
- package/docs/architecture/domains/runtime-harness.md +1 -1
- package/docs/forge-autonomous-goal-loop.md +3 -1
- package/docs/forge-chatgpt-mcp-setup.md +67 -176
- package/docs/images/forge-banner-cn.svg +26 -1
- package/docs/images/forge-banner.svg +26 -1
- package/docs/operations/features.md +1 -1
- package/docs/operations/homebrew.md +3 -3
- package/docs/operations/mcp-tool-exposure.md +5 -5
- package/docs/operations/platform-support.md +45 -37
- package/docs/operations/platform-support.zh-CN.md +40 -38
- package/docs/operations/releasing.md +3 -3
- package/docs/operations/releasing.zh-CN.md +3 -3
- package/docs/operations/troubleshooting.md +11 -6
- package/docs/operations/troubleshooting.zh-CN.md +11 -6
- package/docs/public-usage-guide.md +28 -21
- package/docs/public-usage-guide.zh-CN.md +28 -21
- package/docs/tutorials/01-install-and-start.md +58 -35
- package/docs/tutorials/01-install-and-start.zh-CN.md +58 -35
- package/docs/tutorials/02-connect-chatgpt.md +118 -46
- package/docs/tutorials/02-connect-chatgpt.zh-CN.md +119 -45
- package/docs/wiki/Architecture.md +1 -1
- package/docs/wiki/Installation.md +2 -2
- package/docs/wiki/Quick-Start.md +1 -1
- package/docs/wiki/Work-Lifecycle.md +1 -1
- package/install.ps1 +4 -5
- package/install.sh +4 -6
- package/package.json +15 -5
- package/scripts/archive-workflow.sh +0 -1
- package/scripts/benchmark-thin-harness-mcp-e2e.ts +27 -14
- package/scripts/build-controller-ui.ts +28 -0
- package/scripts/capture-plan.sh +4 -4
- package/scripts/check-agent-tooling.sh +12 -1
- package/scripts/check-platform-support.mjs +2 -2
- package/scripts/check-public-docs.sh +16 -5
- package/scripts/check-release-readiness.sh +5 -11
- package/scripts/check-runtime-architecture.mjs +14 -22
- package/scripts/check-tarball-install-smoke.sh +3 -2
- package/scripts/check-task-workflow.sh +2 -2
- package/scripts/codex-handoff-resume.sh +5 -5
- package/scripts/create-project-dirs.sh +3 -3
- package/scripts/ensure-task-workflow.sh +45 -35
- package/scripts/init-project.sh +4 -4
- package/scripts/install-desktop-operator-registration.ts +2 -2
- package/scripts/lib/project-init-lib.sh +46 -44
- package/scripts/migrate-project-template.sh +7 -4
- package/scripts/new-plan.sh +1 -1
- package/scripts/plan-to-todo.sh +2 -2
- package/scripts/prepare-codex-handoff.sh +2 -2
- package/scripts/prepare-handoff.sh +9 -9
- package/scripts/refresh-current-status.sh +2 -2
- package/scripts/setup-plugins.sh +3 -3
- package/scripts/ship-worktrees.sh +1 -1
- package/scripts/sprint-backlog.sh +5 -5
- package/scripts/sync-codex-installed-copies.sh +2 -3
- package/scripts/verify-sprint.sh +7 -7
- package/scripts/workstream-sync.sh +1 -1
- package/src/cli/chatgpt-browser/bridge-extension.ts +1 -1
- package/src/cli/chatgpt-browser/bridge-provider.ts +29 -0
- package/src/cli/chatgpt-browser/file-policy.ts +3 -1
- package/src/cli/commands/chatgpt.ts +51 -0
- package/src/cli/commands/doctor.ts +1 -1
- package/src/cli/commands/init-hook.ts +213 -70
- package/src/cli/commands/init.ts +5 -6
- package/src/cli/commands/mcp.ts +31 -5
- package/src/cli/commands/runtime.ts +61 -8
- package/src/cli/commands/setup-profile.ts +472 -0
- package/src/cli/controller/check-runner.ts +20 -3
- package/src/cli/controller/codex-continuation.ts +4 -7
- package/src/cli/controller/context-pack.ts +118 -14
- package/src/cli/controller/operational-plan.ts +4 -4
- package/src/cli/controller/task-ledger.ts +10 -10
- package/src/cli/controller/work-mode.ts +6 -13
- package/src/cli/index.ts +14 -35
- package/src/cli/local-bridge/connector-freshness.ts +1 -1
- package/src/cli/local-bridge/console-assets.ts +17 -0
- package/src/cli/local-bridge/console-automations.ts +160 -0
- package/src/cli/local-bridge/dashboard.ts +8 -1303
- package/src/cli/local-bridge/facade-api.ts +16 -7
- package/src/cli/local-bridge/frontend/src/App.tsx +10 -0
- package/src/cli/local-bridge/frontend/src/components/Button.tsx +1 -0
- package/src/cli/local-bridge/frontend/src/components/DetailPane.tsx +3 -0
- package/src/cli/local-bridge/frontend/src/components/SearchField.tsx +1 -0
- package/src/cli/local-bridge/frontend/src/components/SectionHeader.tsx +1 -0
- package/src/cli/local-bridge/frontend/src/components/Segmented.tsx +1 -0
- package/src/cli/local-bridge/frontend/src/components/StatusText.tsx +3 -0
- package/src/cli/local-bridge/frontend/src/design/icons.tsx +11 -0
- package/src/cli/local-bridge/frontend/src/design/tokens.css +8 -0
- package/src/cli/local-bridge/frontend/src/features/automations/AutomationsView.tsx +3 -0
- package/src/cli/local-bridge/frontend/src/features/capabilities/CapabilitiesView.tsx +4 -0
- package/src/cli/local-bridge/frontend/src/features/overview/OverviewView.tsx +3 -0
- package/src/cli/local-bridge/frontend/src/features/repositories/RepositoriesView.tsx +2 -0
- package/src/cli/local-bridge/frontend/src/features/settings/SettingsView.tsx +2 -0
- package/src/cli/local-bridge/frontend/src/features/system/SystemView.tsx +2 -0
- package/src/cli/local-bridge/frontend/src/features/work/WorkView.tsx +3 -0
- package/src/cli/local-bridge/frontend/src/lib/api.ts +23 -0
- package/src/cli/local-bridge/frontend/src/lib/format.ts +4 -0
- package/src/cli/local-bridge/frontend/src/lib/routes.ts +7 -0
- package/src/cli/local-bridge/frontend/src/main.tsx +1 -0
- package/src/cli/local-bridge/frontend/src/shell/AppShell.tsx +1 -0
- package/src/cli/local-bridge/frontend/src/shell/CommandBar.tsx +2 -0
- package/src/cli/local-bridge/frontend/src/shell/Sidebar.tsx +3 -0
- package/src/cli/local-bridge/frontend/src/styles.css +12 -0
- package/src/cli/local-bridge/frontend/src/types.ts +14 -0
- package/src/cli/local-bridge/frontend/src/vite-env.d.ts +1 -0
- package/src/cli/local-bridge/frontend/vite.config.ts +27 -0
- package/src/cli/local-bridge/server.ts +45 -0
- package/src/cli/local-bridge/ui-dist/app.css +1 -0
- package/src/cli/local-bridge/ui-dist/app.js +11094 -0
- package/src/cli/mcp/auth.ts +2 -0
- package/src/cli/mcp/instructions.ts +3 -3
- package/src/cli/mcp/legacy-tool-service.ts +27 -23
- package/src/cli/mcp/multi-repository.ts +2 -0
- package/src/cli/mcp/policy.ts +7 -5
- package/src/cli/mcp/repository-tools.ts +53 -14
- package/src/cli/mcp/server.ts +5 -10
- package/src/cli/mcp/setup.ts +220 -276
- package/src/cli/mcp/tool-capability-cache.ts +1 -1
- package/src/cli/mcp/toolset-names.ts +1 -1
- package/src/cli/mcp/toolset.ts +0 -1
- package/src/cli/mcp/transports/http.ts +51 -4
- package/src/cli/repositories/branch-name-policy.ts +0 -7
- package/src/cli/repositories/controller-home.ts +110 -3
- package/src/cli/repositories/safe-patch.ts +1 -1
- package/src/cli/repositories/selected-path-actions.ts +26 -24
- package/src/core/adoption/gitignore-plan.ts +2 -3
- package/src/core/adoption/plan.ts +4 -1
- package/src/runtime/control-plane/execution/edit-validation-coordinator.ts +556 -0
- package/src/runtime/control-plane/execution/work-handle-store.ts +128 -1
- package/src/runtime/control-plane/execution/work-validation-reconciler.ts +277 -0
- package/src/runtime/control-plane/execution/work-verification-snapshot.ts +169 -0
- package/src/runtime/control-plane/facade/capability-registry.ts +0 -10
- package/src/runtime/control-plane/facade/handoff-inbox-store.ts +44 -25
- package/src/runtime/control-plane/facade/suggested-actions.ts +7 -1
- package/src/runtime/control-plane/facade/types.ts +0 -1
- package/src/runtime/control-plane/global-scheduler/scheduler.ts +30 -8
- package/src/runtime/control-plane/launcher/chatgpt-work-binding-store.ts +114 -0
- package/src/runtime/control-plane/launcher/chatgpt-work-continuation.ts +175 -0
- package/src/runtime/control-plane/launcher/launch-reservation-store.ts +161 -0
- package/src/runtime/control-plane/launcher/provider-mcp-bootstrap.ts +64 -0
- package/src/runtime/control-plane/launcher/thin-launcher.ts +117 -24
- package/src/runtime/control-plane/routing/route-policy.ts +14 -14
- package/src/runtime/control-plane/runtime-cleanup.ts +0 -1
- package/src/runtime/evidence/event-ledger.ts +1 -1
- package/src/runtime/{workflow/campaigns/workspace.ts → execution/managed-workspace.ts} +41 -43
- package/src/runtime/execution/process-runtime/check-facade.ts +6 -0
- package/src/runtime/execution/process-runtime/check-result.ts +45 -0
- package/src/runtime/execution/process-runtime/check-runner-sidecar.ts +42 -16
- package/src/runtime/execution/process-runtime/command-facade.ts +4 -2
- package/src/runtime/execution/process-runtime/gc.ts +1 -1
- package/src/runtime/execution/process-runtime/index.ts +3 -0
- package/src/runtime/execution/process-runtime/interactive-admission.ts +14 -5
- package/src/runtime/execution/process-runtime/persisted-check.ts +285 -0
- package/src/runtime/execution/process-runtime/resource-claims.ts +30 -0
- package/src/runtime/execution/process-runtime/runtime.ts +18 -1
- package/src/runtime/execution/process-runtime/store.ts +1 -19
- package/src/runtime/execution/process-runtime/types.ts +4 -0
- package/src/runtime/execution/thin-harness/execution-router.ts +0 -1
- package/src/runtime/execution/thin-harness/fast-executor.ts +1 -1
- package/src/runtime/execution/thin-harness/lightweight-lanes.ts +1 -2
- package/src/runtime/execution/thin-harness/types.ts +0 -1
- package/src/runtime/execution/workers/executor.ts +1 -1
- package/src/runtime/gateway/mcp/execution-tools.ts +1 -1
- package/src/runtime/gateway/mcp/persisted-check-process.ts +3 -255
- package/src/runtime/gateway/mcp/process-tools.ts +16 -3
- package/src/runtime/gateway/mcp/resource-policy.ts +0 -9
- package/src/runtime/gateway/mcp/router.ts +117 -182
- package/src/runtime/gateway/mcp/runtime-tools.ts +331 -409
- package/src/runtime/gateway/mcp/work-validation-reconciler.ts +1 -219
- package/src/runtime/plugins/browser-adapter.ts +5 -0
- package/src/runtime/plugins/desktop-operator-registration.ts +1 -1
- package/src/runtime/plugins/store.ts +14 -6
- package/src/runtime/plugins/types.ts +1 -1
- package/src/runtime/projections/materialized-view.ts +1 -23
- package/src/runtime/recovery/maintenance-executor.ts +9 -0
- package/src/runtime/resources/ownership.ts +0 -1
- package/src/runtime/root/gateway-adapter.ts +2 -0
- package/src/runtime/root/mcp-transport.ts +10 -2
- package/src/runtime/root/package-connector-service.ts +136 -0
- package/src/runtime/root/package-runtime-release.ts +161 -0
- package/src/runtime/root/package-runtime-service.ts +185 -0
- package/src/runtime/root/release-materialize.ts +19 -0
- package/src/runtime/root/runtime.ts +2 -1
- package/src/runtime/root/scheduler.ts +5 -0
- package/src/runtime/root/service.ts +18 -5
- package/src/runtime/standalone-recovery/core.ts +65 -16
- package/src/runtime/workflow/findings/store.ts +145 -106
- package/src/runtime/workflow/findings/types.ts +22 -0
- package/src/runtime/workflow/schedules/engine.ts +132 -2
- package/src/runtime/workflow/schedules/pending-activation.ts +139 -0
- package/src/runtime/workflow/schedules/work-continuation.ts +218 -0
- package/docs/CHANGELOG.md +0 -978
- package/docs/README.zh-CN.md +0 -58
- package/scripts/smoke-chatgpt-supervised-automation.ts +0 -208
- package/src/cli/commands/controller.ts +0 -680
- package/src/runtime/workflow/campaigns/cleanup.ts +0 -263
- package/src/runtime/workflow/campaigns/engine.ts +0 -112
- package/src/runtime/workflow/campaigns/normalize.ts +0 -100
- package/src/runtime/workflow/campaigns/review.ts +0 -315
- package/src/runtime/workflow/campaigns/store.ts +0 -526
- package/src/runtime/workflow/campaigns/supervisor.ts +0 -112
- package/src/runtime/workflow/campaigns/types.ts +0 -290
- /package/src/runtime/{workflow/campaigns → execution}/workspace-agent.ts +0 -0
|
@@ -1,263 +0,0 @@
|
|
|
1
|
-
import { existsSync, realpathSync } from 'fs';
|
|
2
|
-
import { resolve } from 'path';
|
|
3
|
-
import { branchRef, validateBranchName } from '../../../cli/repositories/branch-name-policy';
|
|
4
|
-
import { getRepository, setRepositoryCheckoutLifecycle } from '../../../cli/repositories/registry';
|
|
5
|
-
import { runProcess } from '../../../effects/process-runner';
|
|
6
|
-
import { cancelExecutionJob, findExecutionJob } from '../../execution/jobs/store';
|
|
7
|
-
import type { Campaign, CampaignCleanupReport, CampaignCleanupResource } from './types';
|
|
8
|
-
import { getCampaign, updateCampaign } from './store';
|
|
9
|
-
|
|
10
|
-
type CleanupOptions = { targetBranch?: string };
|
|
11
|
-
|
|
12
|
-
function now(): string { return new Date().toISOString(); }
|
|
13
|
-
|
|
14
|
-
function git(root: string, args: string[]): { ok: boolean; stdout: string; stderr: string } {
|
|
15
|
-
const result = runProcess('git', ['-C', root, ...args], {
|
|
16
|
-
timeoutMs: 120_000,
|
|
17
|
-
maxOutputBytes: 256 * 1024,
|
|
18
|
-
});
|
|
19
|
-
return { ok: result.ok, stdout: result.stdout.trim(), stderr: result.stderr || result.error || '' };
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function samePath(left: string, right: string): boolean {
|
|
23
|
-
try { return realpathSync(left) === realpathSync(right); }
|
|
24
|
-
catch { return resolve(left) === resolve(right); }
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function cleanupManagedWorkspace(
|
|
28
|
-
controllerHome: string,
|
|
29
|
-
campaign: Campaign,
|
|
30
|
-
options?: CleanupOptions,
|
|
31
|
-
): { resources: CampaignCleanupResource[]; leaks: string[] } {
|
|
32
|
-
const resources: CampaignCleanupResource[] = [];
|
|
33
|
-
const leaks: string[] = [];
|
|
34
|
-
const workspace = campaign.workspace;
|
|
35
|
-
if (!workspace.managed || workspace.mode !== 'isolated' || !workspace.root) return { resources, leaks };
|
|
36
|
-
|
|
37
|
-
const repository = getRepository(campaign.repoId, controllerHome);
|
|
38
|
-
const sourceRoot = repository.canonicalRoot;
|
|
39
|
-
const worktree = workspace.root;
|
|
40
|
-
const branch = workspace.branch ?? undefined;
|
|
41
|
-
let safeBranch: string | undefined;
|
|
42
|
-
if (branch) {
|
|
43
|
-
try {
|
|
44
|
-
safeBranch = validateBranchName(branch, { purpose: 'CAMPAIGN_WORKSPACE_BRANCH' });
|
|
45
|
-
} catch (error) {
|
|
46
|
-
const message = `Managed Campaign workspace has invalid branch metadata; preserved ${branch}.`;
|
|
47
|
-
resources.push({ kind: 'branch', id: branch, status: 'preserved', message });
|
|
48
|
-
leaks.push(message);
|
|
49
|
-
return { resources, leaks };
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
const baseRevision = workspace.baseRevision ?? undefined;
|
|
53
|
-
|
|
54
|
-
if (samePath(sourceRoot, worktree)) {
|
|
55
|
-
const message = 'Managed Campaign workspace resolved to the repository root; refusing cleanup.';
|
|
56
|
-
resources.push({ kind: 'worktree', id: worktree, status: 'preserved', message });
|
|
57
|
-
leaks.push(message);
|
|
58
|
-
return { resources, leaks };
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
if (existsSync(worktree)) {
|
|
62
|
-
const top = git(worktree, ['rev-parse', '--show-toplevel']);
|
|
63
|
-
const currentBranch = git(worktree, ['branch', '--show-current']);
|
|
64
|
-
const status = git(worktree, ['status', '--porcelain=v1', '--untracked-files=all']);
|
|
65
|
-
if (!top.ok || !samePath(top.stdout, worktree) || (safeBranch && currentBranch.stdout !== safeBranch)) {
|
|
66
|
-
const message = `Workspace ownership mismatch; preserved ${worktree}.`;
|
|
67
|
-
resources.push({ kind: 'worktree', id: worktree, status: 'preserved', message });
|
|
68
|
-
leaks.push(message);
|
|
69
|
-
return { resources, leaks };
|
|
70
|
-
}
|
|
71
|
-
if (!status.ok || status.stdout.trim()) {
|
|
72
|
-
const message = `Workspace contains unintegrated or unreadable changes; preserved ${worktree}.`;
|
|
73
|
-
resources.push({ kind: 'worktree', id: worktree, status: 'preserved', message });
|
|
74
|
-
leaks.push(message);
|
|
75
|
-
return { resources, leaks };
|
|
76
|
-
}
|
|
77
|
-
const removed = git(sourceRoot, ['worktree', 'remove', '--force', worktree]);
|
|
78
|
-
if (!removed.ok) {
|
|
79
|
-
const message = `Failed to remove managed worktree: ${removed.stderr}`;
|
|
80
|
-
resources.push({ kind: 'worktree', id: worktree, status: 'failed', message });
|
|
81
|
-
leaks.push(message);
|
|
82
|
-
return { resources, leaks };
|
|
83
|
-
}
|
|
84
|
-
resources.push({ kind: 'worktree', id: worktree, status: 'cleaned' });
|
|
85
|
-
} else {
|
|
86
|
-
resources.push({ kind: 'worktree', id: worktree, status: 'missing' });
|
|
87
|
-
git(sourceRoot, ['worktree', 'prune', '--expire', 'now']);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
const checkoutId = workspace.checkoutId;
|
|
91
|
-
if (!checkoutId) {
|
|
92
|
-
const message = `Managed Campaign ${campaign.campaignId} workspace has no checkout identity.`;
|
|
93
|
-
resources.push({ kind: 'checkout', id: worktree, status: 'failed', message });
|
|
94
|
-
leaks.push(message);
|
|
95
|
-
} else {
|
|
96
|
-
try {
|
|
97
|
-
setRepositoryCheckoutLifecycle({
|
|
98
|
-
repoId: campaign.repoId,
|
|
99
|
-
checkoutId,
|
|
100
|
-
lifecycle: 'removed',
|
|
101
|
-
reason: `Campaign ${campaign.campaignId} workspace cleanup completed.`,
|
|
102
|
-
controllerHome,
|
|
103
|
-
});
|
|
104
|
-
resources.push({ kind: 'checkout', id: checkoutId, status: 'cleaned' });
|
|
105
|
-
} catch (error) {
|
|
106
|
-
const message = `Failed to update checkout lifecycle: ${error instanceof Error ? error.message : String(error)}`;
|
|
107
|
-
resources.push({ kind: 'checkout', id: checkoutId, status: 'failed', message });
|
|
108
|
-
leaks.push(message);
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
if (safeBranch) {
|
|
113
|
-
const branchRevision = git(sourceRoot, ['rev-parse', '--verify', branchRef(safeBranch, { purpose: 'CAMPAIGN_WORKSPACE_BRANCH' })]);
|
|
114
|
-
if (!branchRevision.ok) {
|
|
115
|
-
resources.push({ kind: 'branch', id: safeBranch, status: 'missing' });
|
|
116
|
-
} else {
|
|
117
|
-
const safe = options?.targetBranch
|
|
118
|
-
? git(sourceRoot, ['branch', '--merged', options.targetBranch, '--list', safeBranch]).stdout.includes(safeBranch)
|
|
119
|
-
: !baseRevision || branchRevision.stdout === baseRevision;
|
|
120
|
-
if (!safe) {
|
|
121
|
-
const message = options?.targetBranch
|
|
122
|
-
? `Campaign branch has unmerged commits into ${options.targetBranch}; preserved ${safeBranch}.`
|
|
123
|
-
: `Campaign branch has commits beyond its base revision; preserved ${safeBranch}.`;
|
|
124
|
-
resources.push({ kind: 'branch', id: safeBranch, status: 'preserved', message });
|
|
125
|
-
leaks.push(message);
|
|
126
|
-
} else {
|
|
127
|
-
const deleted = git(sourceRoot, ['branch', '-D', safeBranch]);
|
|
128
|
-
if (deleted.ok) resources.push({ kind: 'branch', id: safeBranch, status: 'cleaned' });
|
|
129
|
-
else {
|
|
130
|
-
const message = `Failed to delete managed branch: ${deleted.stderr}`;
|
|
131
|
-
resources.push({ kind: 'branch', id: safeBranch, status: 'failed', message });
|
|
132
|
-
leaks.push(message);
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
return { resources, leaks };
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* Two-phase, idempotent Campaign cancellation. A Campaign is not terminal
|
|
142
|
-
* until child Jobs and managed workspace resources have been reconciled.
|
|
143
|
-
*/
|
|
144
|
-
export async function cancelCampaign(
|
|
145
|
-
controllerHome: string,
|
|
146
|
-
repoId: string,
|
|
147
|
-
campaignId: string,
|
|
148
|
-
requestId: string,
|
|
149
|
-
reason = 'Cancelled by user',
|
|
150
|
-
expectedRevision?: number,
|
|
151
|
-
): Promise<Campaign> {
|
|
152
|
-
const initial = getCampaign(controllerHome, repoId, campaignId);
|
|
153
|
-
if (initial.status === 'completed') throw new Error('CAMPAIGN_ALREADY_COMPLETED');
|
|
154
|
-
if (initial.status === 'cancelled') return initial;
|
|
155
|
-
|
|
156
|
-
const startedAt = now();
|
|
157
|
-
let campaign = updateCampaign(controllerHome, repoId, campaignId, `${requestId}:begin`, (current) => {
|
|
158
|
-
current.status = 'cancelling';
|
|
159
|
-
current.pauseReason = reason;
|
|
160
|
-
current.nextReconcileAt = undefined;
|
|
161
|
-
current.cleanup = { schemaVersion: 1, startedAt, resources: [], leaks: [] };
|
|
162
|
-
for (const checkpoint of current.checkpoints) {
|
|
163
|
-
if (checkpoint.status === 'open') checkpoint.status = 'superseded';
|
|
164
|
-
}
|
|
165
|
-
for (const task of current.tasks) {
|
|
166
|
-
if (!['succeeded', 'succeeded_no_change', 'skipped', 'failed', 'failed_no_effect', 'blocked', 'cancelled'].includes(task.status)) {
|
|
167
|
-
task.status = 'cancelled';
|
|
168
|
-
task.completedAt = startedAt;
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
return current;
|
|
172
|
-
}, {
|
|
173
|
-
expectedRevision,
|
|
174
|
-
eventType: 'campaign_cancelling',
|
|
175
|
-
eventData: { reason },
|
|
176
|
-
requestFingerprint: reason,
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
const report: CampaignCleanupReport = { schemaVersion: 1, startedAt, resources: [], leaks: [] };
|
|
180
|
-
const childJobIds = new Set([
|
|
181
|
-
...campaign.tasks.map((task) => task.jobId),
|
|
182
|
-
...campaign.checkpoints.map((checkpoint) => checkpoint.triggerJobId),
|
|
183
|
-
].filter((jobId): jobId is string => Boolean(jobId)));
|
|
184
|
-
for (const jobId of childJobIds) {
|
|
185
|
-
const job = findExecutionJob(controllerHome, jobId);
|
|
186
|
-
if (!job) {
|
|
187
|
-
report.resources.push({ kind: 'job', id: jobId, status: 'missing' });
|
|
188
|
-
continue;
|
|
189
|
-
}
|
|
190
|
-
try {
|
|
191
|
-
const cancelled = await cancelExecutionJob(controllerHome, repoId, jobId, `Campaign ${campaignId}: ${reason}`);
|
|
192
|
-
report.resources.push({
|
|
193
|
-
kind: 'job',
|
|
194
|
-
id: jobId,
|
|
195
|
-
status: ['cancelled', 'succeeded', 'failed', 'timed_out', 'orphaned', 'stale', 'human_attention_required'].includes(cancelled.status)
|
|
196
|
-
? 'cleaned'
|
|
197
|
-
: 'failed',
|
|
198
|
-
message: `Execution Job ended as ${cancelled.status}.`,
|
|
199
|
-
});
|
|
200
|
-
} catch (error) {
|
|
201
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
202
|
-
report.resources.push({ kind: 'job', id: jobId, status: 'failed', message });
|
|
203
|
-
report.leaks.push(`job ${jobId}: ${message}`);
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
for (const checkpoint of campaign.checkpoints) {
|
|
208
|
-
report.resources.push({
|
|
209
|
-
kind: 'checkpoint', id: checkpoint.checkpointId,
|
|
210
|
-
status: checkpoint.status === 'superseded' || checkpoint.status === 'submitted' ? 'cleaned' : 'preserved',
|
|
211
|
-
});
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
const workspace = cleanupManagedWorkspace(controllerHome, campaign);
|
|
215
|
-
report.resources.push(...workspace.resources);
|
|
216
|
-
report.leaks.push(...workspace.leaks);
|
|
217
|
-
report.finishedAt = now();
|
|
218
|
-
|
|
219
|
-
campaign = updateCampaign(controllerHome, repoId, campaignId, `${requestId}:finalize`, (current) => {
|
|
220
|
-
current.cleanup = report;
|
|
221
|
-
current.status = report.leaks.length > 0 ? 'cancelled_with_leaks' : 'cancelled';
|
|
222
|
-
current.completedAt = report.finishedAt;
|
|
223
|
-
current.nextReconcileAt = undefined;
|
|
224
|
-
return current;
|
|
225
|
-
}, {
|
|
226
|
-
eventType: report.leaks.length > 0 ? 'campaign_cancelled_with_leaks' : 'campaign_cancelled',
|
|
227
|
-
eventData: { reason, cleanup: report },
|
|
228
|
-
requestFingerprint: JSON.stringify({ reason, leaks: report.leaks }),
|
|
229
|
-
});
|
|
230
|
-
return campaign;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
/**
|
|
234
|
-
* After a campaign is accepted/completed, clean up its managed workspace using
|
|
235
|
-
* `git branch --merged <targetBranch>` as the safety gate rather than the
|
|
236
|
-
* cancel-path's base-revision check. Merged branches and their worktrees are
|
|
237
|
-
* removed; unmerged ones are preserved and recorded as leaks.
|
|
238
|
-
*/
|
|
239
|
-
export function completeCampaignWorkspace(
|
|
240
|
-
controllerHome: string,
|
|
241
|
-
repoId: string,
|
|
242
|
-
campaignId: string,
|
|
243
|
-
requestId: string,
|
|
244
|
-
): Campaign {
|
|
245
|
-
const campaign = getCampaign(controllerHome, repoId, campaignId);
|
|
246
|
-
const repository = getRepository(repoId, controllerHome);
|
|
247
|
-
const targetBranch = repository.defaultBranch ?? 'main';
|
|
248
|
-
|
|
249
|
-
const startedAt = now();
|
|
250
|
-
const report: CampaignCleanupReport = { schemaVersion: 1, startedAt, resources: [], leaks: [] };
|
|
251
|
-
const workspace = cleanupManagedWorkspace(controllerHome, campaign, { targetBranch });
|
|
252
|
-
report.resources.push(...workspace.resources);
|
|
253
|
-
report.leaks.push(...workspace.leaks);
|
|
254
|
-
report.finishedAt = now();
|
|
255
|
-
|
|
256
|
-
return updateCampaign(controllerHome, repoId, campaignId, `${requestId}:workspace-cleanup`, (current) => {
|
|
257
|
-
current.cleanup = report;
|
|
258
|
-
return current;
|
|
259
|
-
}, {
|
|
260
|
-
eventType: report.leaks.length > 0 ? 'campaign_workspace_cleanup_leaks' : 'campaign_workspace_cleaned',
|
|
261
|
-
eventData: { cleanup: report },
|
|
262
|
-
});
|
|
263
|
-
}
|
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
import type { CampaignReconcileResult, CampaignTask } from './types';
|
|
2
|
-
import { getCampaign, updateCampaign } from './store';
|
|
3
|
-
import { createHandoffItem, createWorkContract, getWorkContract } from '../../control-plane/facade';
|
|
4
|
-
|
|
5
|
-
const SUCCESS_TASK_STATUSES = new Set<CampaignTask['status']>(['succeeded', 'succeeded_no_change', 'skipped']);
|
|
6
|
-
|
|
7
|
-
function now(): string { return new Date().toISOString(); }
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Campaign is a historical planning and migration view. Explicit reconciliation
|
|
11
|
-
* migrates unfinished tasks into WorkContracts and HandoffItems without dispatching
|
|
12
|
-
* ExecutionJobs, Agent Runs, retries, or supervisor triggers.
|
|
13
|
-
*/
|
|
14
|
-
function migrateFrozenCampaign(controllerHome: string, repoId: string, campaignId: string): CampaignReconcileResult {
|
|
15
|
-
const before = getCampaign(controllerHome, repoId, campaignId);
|
|
16
|
-
if (before.pauseReason?.startsWith('Campaign automation frozen;')) {
|
|
17
|
-
return { campaignId, changed: false, dispatched: 0, checkpointsOpened: 0, status: before.status };
|
|
18
|
-
}
|
|
19
|
-
const migratable = before.tasks.filter((task) => !SUCCESS_TASK_STATUSES.has(task.status));
|
|
20
|
-
let migrated = 0;
|
|
21
|
-
for (const task of migratable) {
|
|
22
|
-
const store = { controllerHome, repoId };
|
|
23
|
-
const workId = `campaign-${before.campaignId}-${task.taskId}`;
|
|
24
|
-
const terminalFailure = ['failed', 'failed_no_effect', 'cancelled'].includes(task.status);
|
|
25
|
-
const work = getWorkContract(store, workId) ?? createWorkContract(store, {
|
|
26
|
-
workId,
|
|
27
|
-
repoId,
|
|
28
|
-
mode: 'direct_control',
|
|
29
|
-
objective: task.objective || task.title,
|
|
30
|
-
acceptanceCriteria: before.goals.at(-1)?.acceptanceCriteria ?? [],
|
|
31
|
-
constraints: {
|
|
32
|
-
requireHandoffOnAmbiguity: true,
|
|
33
|
-
workspaceMode: before.workspace.mode === 'isolated' ? 'isolated' : 'current',
|
|
34
|
-
},
|
|
35
|
-
allowedPaths: [],
|
|
36
|
-
forbiddenPaths: [],
|
|
37
|
-
checks: [],
|
|
38
|
-
status: terminalFailure ? 'failed' : task.status === 'waiting_review' ? 'ready' : 'blocked',
|
|
39
|
-
requestedBy: 'system',
|
|
40
|
-
scopeSummary: `Migrated from frozen Campaign ${before.campaignId} task ${task.taskId}.`,
|
|
41
|
-
});
|
|
42
|
-
const handoffId = `campaign-migration-${before.campaignId}-${task.taskId}`;
|
|
43
|
-
try {
|
|
44
|
-
createHandoffItem(store, {
|
|
45
|
-
id: handoffId,
|
|
46
|
-
repoId,
|
|
47
|
-
workId: work.workId,
|
|
48
|
-
title: `Campaign task migrated: ${task.title}`,
|
|
49
|
-
severity: terminalFailure ? 'failed' : task.status === 'waiting_review' ? 'ready_to_continue' : 'blocked',
|
|
50
|
-
creationReason: 'ambiguous_outcome',
|
|
51
|
-
reason: 'Campaign automation is frozen; an external SuperController must inspect and continue this WorkContract.',
|
|
52
|
-
summary: `Campaign task ${task.taskId} was migrated without dispatching a legacy Job.`,
|
|
53
|
-
currentState: {
|
|
54
|
-
repoId,
|
|
55
|
-
workId: work.workId,
|
|
56
|
-
statusSummary: task.status,
|
|
57
|
-
blockedBy: task.dependsOn,
|
|
58
|
-
checks: task.jobId ? [{ checkId: task.jobId, ok: false, summary: 'Legacy Campaign Job reference retained for inspection.' }] : undefined,
|
|
59
|
-
},
|
|
60
|
-
attemptedActions: [
|
|
61
|
-
...(task.jobId ? [`legacy ExecutionJob: ${task.jobId}`] : []),
|
|
62
|
-
...(task.runId ? [`legacy Agent Run: ${task.runId}`] : []),
|
|
63
|
-
],
|
|
64
|
-
evidenceRefs: task.evidenceIds.map((evidenceId) => ({ evidenceId, title: 'Campaign evidence' })),
|
|
65
|
-
recommendedDecision: 'Claim the WorkContract and inspect retained evidence before continuation.',
|
|
66
|
-
recommendedPrompt: `Claim Work ${work.workId}, inspect the Campaign handoff, and continue through the repository MCP facade.`,
|
|
67
|
-
suggestedNextActions: [],
|
|
68
|
-
});
|
|
69
|
-
} catch (error) {
|
|
70
|
-
if (!(error instanceof Error) || !error.message.includes('already exists')) throw error;
|
|
71
|
-
}
|
|
72
|
-
migrated += 1;
|
|
73
|
-
}
|
|
74
|
-
const updated = updateCampaign(controllerHome, repoId, campaignId, `migration:${campaignId}:${before.revision}`, (campaign) => {
|
|
75
|
-
for (const task of campaign.tasks) {
|
|
76
|
-
if (SUCCESS_TASK_STATUSES.has(task.status)) continue;
|
|
77
|
-
task.status = 'blocked';
|
|
78
|
-
task.nextAttemptAt = undefined;
|
|
79
|
-
task.executionFinishedAt ??= now();
|
|
80
|
-
task.error = {
|
|
81
|
-
code: 'CAMPAIGN_MIGRATED_TO_WORK',
|
|
82
|
-
message: 'Campaign automation is frozen; continue through the linked WorkContract and HandoffItem.',
|
|
83
|
-
retryable: false,
|
|
84
|
-
};
|
|
85
|
-
}
|
|
86
|
-
campaign.status = 'paused';
|
|
87
|
-
campaign.pauseReason = `Campaign automation frozen; ${migrated} task(s) migrated to WorkContracts.`;
|
|
88
|
-
campaign.nextReconcileAt = undefined;
|
|
89
|
-
return campaign;
|
|
90
|
-
}, {
|
|
91
|
-
eventType: 'campaign_migrated_to_work',
|
|
92
|
-
eventData: { migrated },
|
|
93
|
-
wakeScheduler: false,
|
|
94
|
-
});
|
|
95
|
-
return { campaignId, changed: updated.revision !== before.revision, dispatched: 0, checkpointsOpened: 0, status: updated.status };
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
export function reconcileCampaign(controllerHome: string, repoId: string, campaignId: string): CampaignReconcileResult {
|
|
99
|
-
return migrateFrozenCampaign(controllerHome, repoId, campaignId);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
export interface TickCampaignsOptions {
|
|
103
|
-
maxCampaigns?: number;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
export function tickCampaigns(controllerHome: string, repoIds: string[], options: TickCampaignsOptions = {}): CampaignReconcileResult[] {
|
|
107
|
-
// Automatic reconcile, retry, dispatch, and supervisor triggering are retired.
|
|
108
|
-
void controllerHome;
|
|
109
|
-
void repoIds;
|
|
110
|
-
void options;
|
|
111
|
-
return [];
|
|
112
|
-
}
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
const OPERATION_ALIASES = new Map<string, string>([
|
|
2
|
-
['launch_task', 'dispatch_task'],
|
|
3
|
-
['launch_ready_tasks', 'dispatch_ready_tasks'],
|
|
4
|
-
['retry_run', 'retry_task_run'],
|
|
5
|
-
['integrate_run', 'integrate_task_run'],
|
|
6
|
-
['verify_edit', 'verify_edit_session'],
|
|
7
|
-
['finish_edit', 'finish_edit_session'],
|
|
8
|
-
['repository_command', 'repository_command_execute'],
|
|
9
|
-
['command_execute', 'repository_command_execute'],
|
|
10
|
-
['quick_agent_run', 'quick_agent_session'],
|
|
11
|
-
]);
|
|
12
|
-
|
|
13
|
-
const OBSOLETE_OPERATION_MESSAGES = new Map<string, string>([
|
|
14
|
-
['prepare_issue_launch', 'prepare_issue_launch is obsolete for Campaigns; use launch_issue after readiness review or dispatch_task for one scoped Task.'],
|
|
15
|
-
]);
|
|
16
|
-
|
|
17
|
-
function extractDependencyReference(value: unknown): string | undefined {
|
|
18
|
-
if (typeof value === 'string') return value;
|
|
19
|
-
if (!value || typeof value !== 'object' || Array.isArray(value)) return undefined;
|
|
20
|
-
const record = value as Record<string, unknown>;
|
|
21
|
-
for (const key of ['task_id', 'taskId', 'id', 'reference', 'ref', 'dependency']) {
|
|
22
|
-
if (typeof record[key] === 'string') return record[key] as string;
|
|
23
|
-
}
|
|
24
|
-
return undefined;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function operationToken(value: string): string {
|
|
28
|
-
return value
|
|
29
|
-
.trim()
|
|
30
|
-
.replace(/([a-z0-9])([A-Z])/g, '$1_$2')
|
|
31
|
-
.replace(/[^A-Za-z0-9]+/g, '_')
|
|
32
|
-
.replace(/^_+|_+$/g, '')
|
|
33
|
-
.toLowerCase();
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export function normalizeCampaignOperationName(value: string): string {
|
|
37
|
-
const normalized = operationToken(value);
|
|
38
|
-
return OPERATION_ALIASES.get(normalized) ?? normalized;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export function assertCampaignOperationSupported(value: string): string {
|
|
42
|
-
const operation = normalizeCampaignOperationName(value);
|
|
43
|
-
if (!operation) throw new Error('CAMPAIGN_TASK_OPERATION_REQUIRED');
|
|
44
|
-
if (operation.startsWith('work_')) {
|
|
45
|
-
throw new Error(`CAMPAIGN_OPERATION_INVALID: ${value} is a Work wrapper, not a Campaign task operation.`);
|
|
46
|
-
}
|
|
47
|
-
const obsolete = OBSOLETE_OPERATION_MESSAGES.get(operation);
|
|
48
|
-
if (obsolete) throw new Error(`CAMPAIGN_OPERATION_OBSOLETE: ${obsolete}`);
|
|
49
|
-
return operation;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export function normalizeCampaignDependencyReference(value: unknown): string | undefined {
|
|
53
|
-
let text = extractDependencyReference(value)?.trim();
|
|
54
|
-
if (!text) return undefined;
|
|
55
|
-
text = text.replace(/^`+|`+$/g, '').trim();
|
|
56
|
-
while (text.startsWith('#')) text = text.slice(1).trim();
|
|
57
|
-
const prefixed = /^(?:task|task_id|task-id|dependency|depends_on|depends-on|campaign[_ -]?task|campaign\.task|campaign\/task)[:/#-]+(.+)$/i.exec(text);
|
|
58
|
-
if (prefixed) text = prefixed[1]?.trim() ?? '';
|
|
59
|
-
return text || undefined;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function dependencyLookupKeys(value: string): string[] {
|
|
63
|
-
const normalized = normalizeCampaignDependencyReference(value) ?? value.trim();
|
|
64
|
-
const keys = new Set<string>();
|
|
65
|
-
for (const candidate of [value.trim(), normalized]) {
|
|
66
|
-
if (!candidate) continue;
|
|
67
|
-
keys.add(candidate);
|
|
68
|
-
keys.add(candidate.toLowerCase());
|
|
69
|
-
const token = operationToken(candidate);
|
|
70
|
-
if (token) keys.add(token);
|
|
71
|
-
}
|
|
72
|
-
return [...keys];
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
export function normalizeCampaignDependencyReferences(
|
|
76
|
-
values: readonly unknown[],
|
|
77
|
-
knownTaskIds: readonly string[] = [],
|
|
78
|
-
): string[] {
|
|
79
|
-
const actualTaskIds = knownTaskIds.map((value) => value.trim()).filter(Boolean);
|
|
80
|
-
const lookup = new Map<string, string>();
|
|
81
|
-
for (const taskId of actualTaskIds) {
|
|
82
|
-
for (const key of dependencyLookupKeys(taskId)) if (!lookup.has(key)) lookup.set(key, taskId);
|
|
83
|
-
}
|
|
84
|
-
const normalized: string[] = [];
|
|
85
|
-
for (const value of values) {
|
|
86
|
-
const reference = normalizeCampaignDependencyReference(value);
|
|
87
|
-
if (!reference) continue;
|
|
88
|
-
const resolved = dependencyLookupKeys(reference).map((key) => lookup.get(key)).find(Boolean) ?? reference;
|
|
89
|
-
if (!normalized.includes(resolved)) normalized.push(resolved);
|
|
90
|
-
}
|
|
91
|
-
return normalized;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
export function normalizeCampaignTaskReferences<T extends { taskId: string; dependsOn: string[] }>(tasks: T[]): T[] {
|
|
95
|
-
const taskIds = tasks.map((task) => task.taskId);
|
|
96
|
-
for (const task of tasks) {
|
|
97
|
-
task.dependsOn = normalizeCampaignDependencyReferences(task.dependsOn, taskIds);
|
|
98
|
-
}
|
|
99
|
-
return tasks;
|
|
100
|
-
}
|