@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
|
@@ -26,6 +26,7 @@ export interface GeneratedCacheRetentionOptions {
|
|
|
26
26
|
maxRemovals?: number;
|
|
27
27
|
processCommands?: string[];
|
|
28
28
|
rootStartIndex?: number;
|
|
29
|
+
scanStartIndex?: number;
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
export interface GeneratedCacheRetentionReport {
|
|
@@ -95,6 +96,7 @@ export interface XCTestDeviceCleanupReport {
|
|
|
95
96
|
|
|
96
97
|
interface Candidate {
|
|
97
98
|
path: string;
|
|
99
|
+
cacheRoot?: string;
|
|
98
100
|
kind:
|
|
99
101
|
| 'forge_ios_derived_data'
|
|
100
102
|
| 'harness_derived_data'
|
|
@@ -119,7 +121,7 @@ function pathInside(root: string, path: string): boolean {
|
|
|
119
121
|
return rel !== '' && rel !== '..' && !rel.startsWith(`..${sep}`);
|
|
120
122
|
}
|
|
121
123
|
|
|
122
|
-
function collectProcessCommands(): string[] {
|
|
124
|
+
function collectProcessCommands(): string[] | undefined {
|
|
123
125
|
try {
|
|
124
126
|
return execFileSync('ps', ['-axo', 'command='], {
|
|
125
127
|
encoding: 'utf8',
|
|
@@ -127,7 +129,8 @@ function collectProcessCommands(): string[] {
|
|
|
127
129
|
maxBuffer: 2 * 1024 * 1024,
|
|
128
130
|
}).split('\n').map((line) => line.trim()).filter(Boolean);
|
|
129
131
|
} catch {
|
|
130
|
-
|
|
132
|
+
// Missing process evidence is not proof that a cache is idle.
|
|
133
|
+
return undefined;
|
|
131
134
|
}
|
|
132
135
|
}
|
|
133
136
|
|
|
@@ -142,6 +145,19 @@ function commandReferencesPath(commands: readonly string[], repoRoot: string, pa
|
|
|
142
145
|
|| (canonicalRelative && command.includes(canonicalRelative)));
|
|
143
146
|
}
|
|
144
147
|
|
|
148
|
+
function hasSymlinkAncestor(root: string, path: string): boolean {
|
|
149
|
+
const rel = relative(resolve(root), resolve(path));
|
|
150
|
+
if (!rel || rel === '..' || rel.startsWith(`..${sep}`)) return true;
|
|
151
|
+
let current = resolve(root);
|
|
152
|
+
try {
|
|
153
|
+
for (const part of rel.split(sep)) {
|
|
154
|
+
current = join(current, part);
|
|
155
|
+
if (lstatSync(current).isSymbolicLink()) return true;
|
|
156
|
+
}
|
|
157
|
+
return false;
|
|
158
|
+
} catch { return true; }
|
|
159
|
+
}
|
|
160
|
+
|
|
145
161
|
function containsTrackedFiles(repoRoot: string, path: string): boolean {
|
|
146
162
|
const rel = relative(resolve(repoRoot), resolve(path));
|
|
147
163
|
if (!rel || rel === '..' || rel.startsWith(`..${sep}`)) return true;
|
|
@@ -158,92 +174,106 @@ function containsTrackedFiles(repoRoot: string, path: string): boolean {
|
|
|
158
174
|
}
|
|
159
175
|
}
|
|
160
176
|
|
|
161
|
-
|
|
162
|
-
|
|
177
|
+
const REBUILDABLE_XCODE_CHILDREN = [
|
|
178
|
+
'Build', 'ModuleCache.noindex', 'Index.noindex', 'CompilationCache.noindex',
|
|
179
|
+
'SDKExplicitPrecompiledModules', 'SDKStatCaches.noindex',
|
|
180
|
+
] as const;
|
|
181
|
+
|
|
182
|
+
function rotatedEntries(root: string, round: number) {
|
|
183
|
+
try {
|
|
184
|
+
const entries = readdirSync(root, { withFileTypes: true }).sort((a, b) => String(a.name).localeCompare(String(b.name)));
|
|
185
|
+
if (entries.length > 1) {
|
|
186
|
+
const offset = ((Math.trunc(round) % entries.length) + entries.length) % entries.length;
|
|
187
|
+
entries.push(...entries.splice(0, offset));
|
|
188
|
+
}
|
|
189
|
+
return entries;
|
|
190
|
+
} catch { return []; }
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function directChildDirectories(root: string, predicate: (name: string) => boolean, budget: { remaining: number }, round = 0): string[] {
|
|
163
194
|
const found: string[] = [];
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
for (const entry of entries) {
|
|
195
|
+
if (budget.remaining <= 0) return found;
|
|
196
|
+
for (const entry of rotatedEntries(root, round)) {
|
|
167
197
|
if (budget.remaining <= 0) break;
|
|
168
198
|
budget.remaining -= 1;
|
|
169
|
-
|
|
170
|
-
if (entry.isDirectory() && predicate(name)) found.push(join(root, name));
|
|
199
|
+
if (entry.isDirectory() && predicate(String(entry.name))) found.push(join(root, String(entry.name)));
|
|
171
200
|
}
|
|
172
201
|
return found;
|
|
173
202
|
}
|
|
174
203
|
|
|
175
204
|
function directChildFiles(root: string, budget: { remaining: number }): string[] {
|
|
176
|
-
if (!existsSync(root) || budget.remaining <= 0) return [];
|
|
177
205
|
const found: string[] = [];
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
for (const entry of entries) {
|
|
206
|
+
if (budget.remaining <= 0) return found;
|
|
207
|
+
for (const entry of rotatedEntries(root, 0)) {
|
|
181
208
|
if (budget.remaining <= 0) break;
|
|
182
209
|
budget.remaining -= 1;
|
|
183
|
-
if (entry.isFile()) found.push(join(root, entry.name));
|
|
210
|
+
if (entry.isFile()) found.push(join(root, String(entry.name)));
|
|
184
211
|
}
|
|
185
212
|
return found;
|
|
186
213
|
}
|
|
187
214
|
|
|
188
|
-
function
|
|
189
|
-
|
|
215
|
+
function isXcodeCacheRoot(root: string): boolean {
|
|
216
|
+
try {
|
|
217
|
+
const info = lstatSync(join(root, 'info.plist'));
|
|
218
|
+
return info.isFile() && !info.isSymbolicLink() && REBUILDABLE_XCODE_CHILDREN.some((name) => {
|
|
219
|
+
try { const stat = lstatSync(join(root, name)); return stat.isDirectory() && !stat.isSymbolicLink(); } catch { return false; }
|
|
220
|
+
});
|
|
221
|
+
} catch { return false; }
|
|
190
222
|
}
|
|
191
223
|
|
|
192
|
-
function nestedDerivedDataDirectories(root: string, maxDepth: number, budget: { remaining: number }): string[] {
|
|
193
|
-
if (!existsSync(root) || budget.remaining <= 0) return [];
|
|
224
|
+
function nestedDerivedDataDirectories(root: string, maxDepth: number, budget: { remaining: number }, round: number): string[] {
|
|
194
225
|
const found: string[] = [];
|
|
195
|
-
const visit = (dir: string, depth: number): void => {
|
|
226
|
+
const visit = (dir: string, depth: number, visitRound: number): void => {
|
|
196
227
|
if (depth > maxDepth || budget.remaining <= 0) return;
|
|
197
|
-
|
|
198
|
-
|
|
228
|
+
const entries = rotatedEntries(dir, visitRound);
|
|
229
|
+
// Divide at each level rather than correlating parent/child rotations.
|
|
230
|
+
const childRound = Math.floor(visitRound / Math.max(1, entries.length));
|
|
199
231
|
for (const entry of entries) {
|
|
200
232
|
if (budget.remaining <= 0) return;
|
|
201
233
|
budget.remaining -= 1;
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
}
|
|
208
|
-
visit(path, depth + 1);
|
|
234
|
+
const name = String(entry.name);
|
|
235
|
+
if (!entry.isDirectory() || ['.git', 'node_modules', 'SourcePackages', 'Logs', ...REBUILDABLE_XCODE_CHILDREN].includes(name) || name.endsWith('.xcresult')) continue;
|
|
236
|
+
const path = join(dir, name);
|
|
237
|
+
if (isXcodeCacheRoot(path)) found.push(path);
|
|
238
|
+
else visit(path, depth + 1, childRound);
|
|
209
239
|
}
|
|
210
240
|
};
|
|
211
|
-
visit(root, 0);
|
|
241
|
+
visit(root, 0, round);
|
|
212
242
|
return found;
|
|
213
243
|
}
|
|
214
244
|
|
|
215
|
-
function discoverCandidates(repoRoot: string, maxEntries: number): { candidates: Candidate[]; inspected: number; truncated: boolean } {
|
|
245
|
+
function discoverCandidates(repoRoot: string, maxEntries: number, scanRound: number): { candidates: Candidate[]; inspected: number; truncated: boolean } {
|
|
216
246
|
const root = resolve(repoRoot);
|
|
217
247
|
const budget = { remaining: Math.max(1, maxEntries) };
|
|
218
248
|
const initial = budget.remaining;
|
|
219
249
|
const candidates: Candidate[] = [];
|
|
220
|
-
|
|
221
|
-
//
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
for (const
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
250
|
+
// Preserve browser staging priority. Cache namespace and nested entry rotations
|
|
251
|
+
// use independent visit ordinals, so protected early entries cannot starve peers.
|
|
252
|
+
for (const path of directChildFiles(join(root, '.forge', 'browser', 'uploads'), budget)) candidates.push({ path, kind: 'forge_browser_upload' });
|
|
253
|
+
for (const path of directChildFiles(join(root, '.forge', 'browser', 'screenshots'), budget)) candidates.push({ path, kind: 'forge_browser_screenshot' });
|
|
254
|
+
const namespaces: Array<{ root: string; kind: Candidate['kind']; depth?: number }> = [
|
|
255
|
+
{ root: join(root, '.forge', 'ios', 'DerivedData'), kind: 'forge_ios_derived_data' },
|
|
256
|
+
{ root: join(root, '.ai', 'harness'), kind: 'harness_derived_data' },
|
|
257
|
+
{ root: join(root, '.repo-harness'), kind: 'repo_harness_derived_data', depth: 5 },
|
|
258
|
+
{ root: join(root, '.tmp'), kind: 'tmp_derived_data', depth: 6 },
|
|
259
|
+
];
|
|
260
|
+
const offset = ((Math.trunc(scanRound) % namespaces.length) + namespaces.length) % namespaces.length;
|
|
261
|
+
namespaces.push(...namespaces.splice(0, offset));
|
|
262
|
+
const entryRound = Math.floor(scanRound / namespaces.length);
|
|
263
|
+
for (const namespace of namespaces) {
|
|
264
|
+
const cacheRoots = namespace.depth === undefined
|
|
265
|
+
? directChildDirectories(namespace.root, () => true, budget, entryRound).filter(isXcodeCacheRoot)
|
|
266
|
+
: nestedDerivedDataDirectories(namespace.root, namespace.depth, budget, entryRound);
|
|
267
|
+
for (const cacheRoot of cacheRoots) {
|
|
268
|
+
// Never remove the bucket: Logs, results, SourcePackages and unknown files
|
|
269
|
+
// are evidence/dependencies, not disposable build cache.
|
|
270
|
+
for (const name of REBUILDABLE_XCODE_CHILDREN) {
|
|
271
|
+
const path = join(cacheRoot, name);
|
|
272
|
+
if (existsSync(path)) candidates.push({ path, kind: namespace.kind, cacheRoot });
|
|
273
|
+
}
|
|
274
|
+
}
|
|
241
275
|
}
|
|
242
|
-
return {
|
|
243
|
-
candidates: candidates.filter((candidate, index, values) => values.findIndex((item) => resolve(item.path) === resolve(candidate.path)) === index),
|
|
244
|
-
inspected: initial - budget.remaining,
|
|
245
|
-
truncated: budget.remaining <= 0,
|
|
246
|
-
};
|
|
276
|
+
return { candidates, inspected: initial - budget.remaining, truncated: budget.remaining <= 0 };
|
|
247
277
|
}
|
|
248
278
|
|
|
249
279
|
function retentionGraceMs(candidate: Candidate, explicitGraceMs: number | undefined): number {
|
|
@@ -279,7 +309,7 @@ export function cleanupGeneratedRepositoryCaches(
|
|
|
279
309
|
const maxEntries = Math.max(1, Math.floor(options.maxEntries ?? DEFAULT_SCAN_BUDGET));
|
|
280
310
|
let remainingRemovals = Math.max(1, Math.floor(options.maxRemovals ?? DEFAULT_REMOVAL_BUDGET));
|
|
281
311
|
const commands = options.processCommands ?? collectProcessCommands();
|
|
282
|
-
const discovered = discoverCandidates(repoRoot, maxEntries);
|
|
312
|
+
const discovered = discoverCandidates(repoRoot, maxEntries, options.scanStartIndex ?? 0);
|
|
283
313
|
const report: GeneratedCacheRetentionReport = {
|
|
284
314
|
inspected: discovered.inspected,
|
|
285
315
|
eligible: 0,
|
|
@@ -290,26 +320,34 @@ export function cleanupGeneratedRepositoryCaches(
|
|
|
290
320
|
budgetExhausted: discovered.truncated,
|
|
291
321
|
};
|
|
292
322
|
|
|
323
|
+
if (commands === undefined) {
|
|
324
|
+
report.errors.push('process_inventory_unavailable: generated-cache reclamation skipped');
|
|
325
|
+
report.skippedByReason.process_inventory_unavailable = 1;
|
|
326
|
+
return report;
|
|
327
|
+
}
|
|
293
328
|
for (const candidate of discovered.candidates) {
|
|
294
329
|
const ownedRoot = candidateOwnedRoot(repoRoot, candidate);
|
|
295
330
|
const fileCandidate = candidate.kind === 'forge_browser_upload' || candidate.kind === 'forge_browser_screenshot';
|
|
296
331
|
try {
|
|
297
332
|
const stat = lstatSync(candidate.path);
|
|
298
|
-
if ((fileCandidate ? !stat.isFile() : !stat.isDirectory()) || stat.isSymbolicLink() || !pathInside(ownedRoot, candidate.path)) {
|
|
333
|
+
if ((fileCandidate ? !stat.isFile() : !stat.isDirectory()) || stat.isSymbolicLink() || hasSymlinkAncestor(repoRoot, candidate.path) || !pathInside(ownedRoot, candidate.path)) {
|
|
299
334
|
increment(report.skippedByReason, 'ownership_unproven');
|
|
300
335
|
report.retainedPaths.push(relative(repoRoot, candidate.path));
|
|
301
336
|
continue;
|
|
302
337
|
}
|
|
303
|
-
|
|
338
|
+
const usedAt = candidate.cacheRoot ? Math.max(stat.mtimeMs, lstatSync(candidate.cacheRoot).mtimeMs) : stat.mtimeMs;
|
|
339
|
+
if (nowMs - usedAt < retentionGraceMs(candidate, options.graceMs)) {
|
|
304
340
|
increment(report.skippedByReason, 'retention_grace');
|
|
305
341
|
continue;
|
|
306
342
|
}
|
|
307
|
-
if (commandReferencesPath(commands, repoRoot, candidate.path)
|
|
343
|
+
if (commandReferencesPath(commands, repoRoot, candidate.cacheRoot ?? candidate.path)
|
|
344
|
+
|| (candidate.cacheRoot && commands.some((command) => /(?:^|[/\s])xcodebuild(?:\s|$)/.test(command)
|
|
345
|
+
&& !/(?:^|\s)-derivedDataPath(?:\s|=)/.test(command)))) {
|
|
308
346
|
increment(report.skippedByReason, 'active_process');
|
|
309
347
|
report.retainedPaths.push(relative(repoRoot, candidate.path));
|
|
310
348
|
continue;
|
|
311
349
|
}
|
|
312
|
-
if (containsTrackedFiles(repoRoot, candidate.path)) {
|
|
350
|
+
if (containsTrackedFiles(repoRoot, candidate.cacheRoot ?? candidate.path)) {
|
|
313
351
|
increment(report.skippedByReason, 'tracked_content');
|
|
314
352
|
report.retainedPaths.push(relative(repoRoot, candidate.path));
|
|
315
353
|
continue;
|
|
@@ -320,6 +358,12 @@ export function cleanupGeneratedRepositoryCaches(
|
|
|
320
358
|
increment(report.skippedByReason, 'cleanup_budget_exhausted');
|
|
321
359
|
continue;
|
|
322
360
|
}
|
|
361
|
+
// Recheck last-use immediately before removal: discovery and Git probes
|
|
362
|
+
// may overlap a cache reuse that refreshed the bucket after our first stat.
|
|
363
|
+
if (candidate.cacheRoot && lstatSync(candidate.cacheRoot).mtimeMs !== usedAt && nowMs - lstatSync(candidate.cacheRoot).mtimeMs < retentionGraceMs(candidate, options.graceMs)) {
|
|
364
|
+
increment(report.skippedByReason, 'retention_grace');
|
|
365
|
+
continue;
|
|
366
|
+
}
|
|
323
367
|
remainingRemovals -= 1;
|
|
324
368
|
rmSync(candidate.path, { recursive: true, force: true });
|
|
325
369
|
report.removedPaths.push(relative(repoRoot, candidate.path).replace(/\\/g, '/'));
|
|
@@ -358,10 +402,10 @@ export function cleanupGeneratedRepositoryCheckoutCaches(
|
|
|
358
402
|
}
|
|
359
403
|
const rawRoots = roots.map((root) => resolve(root));
|
|
360
404
|
const canonicalRoots = roots.map((root) => canonical(root));
|
|
361
|
-
const processCommandsForRoot = (root: string): string[] => {
|
|
405
|
+
const processCommandsForRoot = (root: string): string[] | undefined => {
|
|
362
406
|
const rawRoot = resolve(root);
|
|
363
407
|
const canonicalRoot = canonical(root);
|
|
364
|
-
return processCommands
|
|
408
|
+
return processCommands?.filter((command) => {
|
|
365
409
|
const referencedRootIndexes = rawRoots
|
|
366
410
|
.map((candidate, index) => command.includes(candidate) || command.includes(canonicalRoots[index]!) ? index : -1)
|
|
367
411
|
.filter((index) => index >= 0);
|
|
@@ -381,6 +425,11 @@ export function cleanupGeneratedRepositoryCheckoutCaches(
|
|
|
381
425
|
budgetExhausted: false,
|
|
382
426
|
};
|
|
383
427
|
|
|
428
|
+
if (processCommands === undefined) {
|
|
429
|
+
aggregate.errors.push('process_inventory_unavailable: generated-cache reclamation skipped');
|
|
430
|
+
aggregate.skippedByReason.process_inventory_unavailable = 1;
|
|
431
|
+
return aggregate;
|
|
432
|
+
}
|
|
384
433
|
for (const root of roots) {
|
|
385
434
|
if (remainingEntries <= 0 || remainingRemovals <= 0) {
|
|
386
435
|
aggregate.budgetExhausted = true;
|
|
@@ -389,6 +438,7 @@ export function cleanupGeneratedRepositoryCheckoutCaches(
|
|
|
389
438
|
const report = cleanupGeneratedRepositoryCaches(root, {
|
|
390
439
|
...options,
|
|
391
440
|
processCommands: processCommandsForRoot(root),
|
|
441
|
+
scanStartIndex: options.scanStartIndex ?? Math.floor((options.rootStartIndex ?? 0) / Math.max(1, roots.length)),
|
|
392
442
|
maxEntries: remainingEntries,
|
|
393
443
|
maxRemovals: remainingRemovals,
|
|
394
444
|
});
|
|
@@ -565,6 +615,7 @@ export function cleanupIdleXCTestDevices(controllerHome: string): XCTestDeviceCl
|
|
|
565
615
|
if (deviceDirectories <= 4) return { attempted: false, removedDevices: 0, skippedReason: 'below_cleanup_threshold' };
|
|
566
616
|
|
|
567
617
|
const commands = collectProcessCommands();
|
|
618
|
+
if (commands === undefined) return { attempted: false, removedDevices: 0, skippedReason: 'process_inventory_unavailable' };
|
|
568
619
|
if (commands.some((command) => /(?:^|\/)(?:xcodebuild|xctest|swiftc)(?:\s|$)/.test(command))) {
|
|
569
620
|
return { attempted: false, removedDevices: 0, skippedReason: 'active_xcode_process' };
|
|
570
621
|
}
|
|
@@ -80,6 +80,8 @@ export function planSchedulerPeriodicMaintenance(input: {
|
|
|
80
80
|
}): {
|
|
81
81
|
periodicSequence: number;
|
|
82
82
|
deepRetentionSequence: number;
|
|
83
|
+
generatedCacheRepositoryIndex?: number;
|
|
84
|
+
generatedCacheRootStartIndex: number;
|
|
83
85
|
runRetention: boolean;
|
|
84
86
|
runDeepRetention: boolean;
|
|
85
87
|
processGcRepositoryIndex?: number;
|
|
@@ -94,6 +96,9 @@ export function planSchedulerPeriodicMaintenance(input: {
|
|
|
94
96
|
return {
|
|
95
97
|
periodicSequence,
|
|
96
98
|
deepRetentionSequence,
|
|
99
|
+
generatedCacheRepositoryIndex: repositoryCount > 0 && periodicSequence % retentionEvery === 0
|
|
100
|
+
? Math.floor(periodicSequence / retentionEvery) % repositoryCount : undefined,
|
|
101
|
+
generatedCacheRootStartIndex: repositoryCount > 0 ? Math.floor(periodicSequence / retentionEvery / repositoryCount) : 0,
|
|
97
102
|
runRetention: periodicSequence % retentionEvery === 0,
|
|
98
103
|
runDeepRetention: periodicSequence % deepRetentionEvery === 0,
|
|
99
104
|
processGcRepositoryIndex: repositoryCount > 0 ? periodicSequence % repositoryCount : undefined,
|
|
@@ -271,6 +276,21 @@ export async function runSchedulerPeriodicCleanup(input: {
|
|
|
271
276
|
}
|
|
272
277
|
}
|
|
273
278
|
|
|
279
|
+
// Cache production is much faster than evidence retention. Reuse the existing
|
|
280
|
+
// five-minute pass, one repository and one bounded budget per pass. The root
|
|
281
|
+
// ordinal advances per repository visit, not by the correlated global index.
|
|
282
|
+
if (plan.generatedCacheRepositoryIndex !== undefined) {
|
|
283
|
+
if (!runtimeMaintenanceAuthorityCurrent(input.controllerHome)) return;
|
|
284
|
+
const cacheRepo = input.repositories[plan.generatedCacheRepositoryIndex]!;
|
|
285
|
+
try {
|
|
286
|
+
const generated = cleanupGeneratedRepositoryCheckoutCaches(cacheRepo, {
|
|
287
|
+
nowMs: input.nowMs, rootStartIndex: plan.generatedCacheRootStartIndex,
|
|
288
|
+
});
|
|
289
|
+
if (generated.errors.length > 0) console.error(`[forge cleanup] generated-cache retention reported ${generated.errors.length} error(s) for ${cacheRepo.repoId}`);
|
|
290
|
+
} catch (error) {
|
|
291
|
+
console.error(`[forge cleanup] generated-cache retention failed for ${cacheRepo.repoId}:`, error);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
274
294
|
if (!plan.runDeepRetention) return;
|
|
275
295
|
if (!runtimeMaintenanceAuthorityCurrent(input.controllerHome)) return;
|
|
276
296
|
|
|
@@ -285,14 +305,6 @@ export async function runSchedulerPeriodicCleanup(input: {
|
|
|
285
305
|
}
|
|
286
306
|
if (plan.deepRetentionRepositoryIndex === undefined) return;
|
|
287
307
|
const repo = input.repositories[plan.deepRetentionRepositoryIndex]!;
|
|
288
|
-
try {
|
|
289
|
-
const generated = cleanupGeneratedRepositoryCheckoutCaches(repo, { nowMs: input.nowMs, rootStartIndex: plan.deepRetentionSequence });
|
|
290
|
-
if (generated.errors.length > 0) {
|
|
291
|
-
console.error(`[forge cleanup] generated-cache retention reported ${generated.errors.length} error(s) for ${repo.repoId}`);
|
|
292
|
-
}
|
|
293
|
-
} catch (error) {
|
|
294
|
-
console.error(`[forge cleanup] generated-cache retention failed for ${repo.repoId}:`, error);
|
|
295
|
-
}
|
|
296
308
|
try {
|
|
297
309
|
const browserArtifacts = cleanupControllerHomeBrowserArtifacts(input.controllerHome, repo.repoId, { nowMs: input.nowMs });
|
|
298
310
|
if (browserArtifacts.errors.length > 0 || Object.values(browserArtifacts.classes).some((item) => item.overCapacity)) {
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
childReferenceFromJob,
|
|
9
9
|
childReferenceFromReceipt,
|
|
10
10
|
hasDurableChildReference,
|
|
11
|
-
|
|
11
|
+
isAgentDelegationJob,
|
|
12
12
|
mergeChildReferences,
|
|
13
13
|
buildDelegatedExecutionResult,
|
|
14
14
|
} from '../../execution/jobs/child-reference';
|
|
@@ -34,16 +34,6 @@ function canAutomaticallyReplay(job: ExecutionJob): boolean {
|
|
|
34
34
|
return !hasPotentialSideEffects(job);
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
function isAgentDelegationJob(job: ExecutionJob): boolean {
|
|
38
|
-
return isAgentDelegationOperation(job.payload.operation)
|
|
39
|
-
|| job.type === 'agent-run'
|
|
40
|
-
|| job.type === 'dispatch-task'
|
|
41
|
-
|| job.payload.arguments?.agentDelegation === true
|
|
42
|
-
|| (job.payload.operation === 'legacy-local-job'
|
|
43
|
-
&& typeof job.payload.arguments?.localAction === 'string'
|
|
44
|
-
&& ['launch-task', 'quick-agent-session'].includes(String(job.payload.arguments.localAction)));
|
|
45
|
-
}
|
|
46
|
-
|
|
47
37
|
function ambiguousStartedOperation(controllerHome: string, job: ExecutionJob): boolean {
|
|
48
38
|
const receipt = readOperationReceipt(controllerHome, job.repoId, job.jobId);
|
|
49
39
|
if (!receipt || !operationReceiptMatchesJobOwnership(receipt, job)) return false;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createHash, randomUUID } from 'crypto';
|
|
2
|
+
|
|
2
3
|
import { isWslWindowsRuntime } from '../../../cli/chatgpt-browser/bridge-provider';
|
|
3
4
|
import { createChatgptBrowserDeliveryHost } from '../../../../adapters/chatgpt/browser-delivery-host';
|
|
4
5
|
import { createChatgptWslBridgeDeliveryHost } from '../../../../adapters/chatgpt/wsl-bridge-delivery-host';
|
|
@@ -14,7 +14,6 @@ import { cleanupTerminalEditSessionRecords } from '../../cli/editing/edit-sessio
|
|
|
14
14
|
import { listRepositories } from '../../cli/repositories/registry';
|
|
15
15
|
import { managedPathInside, managedWorktreeStorageRoot } from '../../cli/repositories/worktree-storage';
|
|
16
16
|
import { migrateManagedWorkspacePhysicalDependenciesToCanonical } from '../execution/managed-workspace';
|
|
17
|
-
import { cleanupStaleCodegraphLocators } from '../context/codegraph-cache-boundary';
|
|
18
17
|
import { listActiveLeases } from '../resources/leases/store';
|
|
19
18
|
import { cleanupScheduleOccurrenceHistory } from '../../../packages/kernel/scheduler/api/index';
|
|
20
19
|
import { isTerminalSemanticWorkState, readWorkContractStore } from '../../../packages/kernel/work/api/index';
|
|
@@ -22,7 +21,6 @@ import { assertOwnedResourceCleanupTarget, listOwnedResources, markOwnedResource
|
|
|
22
21
|
import { appendJsonLine, readJsonFile, writeJsonAtomic } from '../shared/json-files';
|
|
23
22
|
import { cleanupControllerReleaseHistory } from './release-retention';
|
|
24
23
|
import { cleanupWorkPreservationArtifacts } from './cleanup-artifact-retention';
|
|
25
|
-
import { cleanupCodegraphCaches, CODEGRAPH_CACHE_RETENTION_POLICY_VERSION } from './codegraph-cache-retention';
|
|
26
24
|
import { cleanupRetiredRepositoryNamespaces } from './repository-namespace-retention';
|
|
27
25
|
import { maintainControlPlaneDatabase, type ControlPlaneDatabaseMaintenanceReport } from './persistence/sqlite-store';
|
|
28
26
|
import { cleanupExpiredExperiences } from './persistence/experience-store';
|
|
@@ -168,8 +166,6 @@ export interface RuntimeCleanupOptions {
|
|
|
168
166
|
editSessionMaxRetained?: number;
|
|
169
167
|
/** Grace period before a non-Plan Work with no exact durable owner loses execution authority. */
|
|
170
168
|
ownerlessWorkAuthorityGraceMs?: number;
|
|
171
|
-
/** Minimum age before an unprotected Forge-owned CodeGraph cache can be rebuilt on demand and reclaimed. */
|
|
172
|
-
codegraphCacheRetentionMs?: number;
|
|
173
169
|
/** Grace before a removed repository may shed only explicit disposable Controller Home children. */
|
|
174
170
|
repositoryNamespaceRetentionMs?: number;
|
|
175
171
|
/** Minimum reclaimable SQLite free-page bytes before physical VACUUM is eligible. */
|
|
@@ -243,8 +239,6 @@ export interface RuntimeCleanupReport {
|
|
|
243
239
|
removedScheduleOccurrencePaths: string[];
|
|
244
240
|
removedScheduleDecisionPaths: string[];
|
|
245
241
|
removedEditSessionPaths: string[];
|
|
246
|
-
removedCodegraphLocatorPaths: string[];
|
|
247
|
-
removedCodegraphCachePaths: string[];
|
|
248
242
|
removedRepositoryNamespacePaths: string[];
|
|
249
243
|
/** Physical SQLite checkpoint/compaction receipt; row lifecycle remains domain-owned. */
|
|
250
244
|
sqliteMaintenance?: ControlPlaneDatabaseMaintenanceReport;
|
|
@@ -302,14 +296,14 @@ function addReclaimMeasurement(metrics: MutableReclaimMetrics, path: string): vo
|
|
|
302
296
|
else metrics.unknownReclaimedByteCount += 1;
|
|
303
297
|
}
|
|
304
298
|
|
|
305
|
-
type RemovalPhase = 'worktrees' | 'temporary' | 'artifacts' | 'scheduler' | 'edit_sessions' | '
|
|
306
|
-
const REMOVAL_PHASES: readonly RemovalPhase[] = ['worktrees', 'temporary', 'artifacts', 'scheduler', 'edit_sessions', '
|
|
299
|
+
type RemovalPhase = 'worktrees' | 'temporary' | 'artifacts' | 'scheduler' | 'edit_sessions' | 'repository_namespaces' | 'releases';
|
|
300
|
+
const REMOVAL_PHASES: readonly RemovalPhase[] = ['worktrees', 'temporary', 'artifacts', 'scheduler', 'edit_sessions', 'repository_namespaces', 'releases'];
|
|
307
301
|
|
|
308
302
|
export function cleanupRemovalPhaseOrder(reason: RuntimeCleanupReport['reason'], sequence = 0): RemovalPhase[] {
|
|
309
303
|
if (reason !== 'periodic') return [...REMOVAL_PHASES];
|
|
310
304
|
const offset = ((Math.trunc(sequence) % REMOVAL_PHASES.length) + REMOVAL_PHASES.length) % REMOVAL_PHASES.length;
|
|
311
305
|
// Periodic maintenance is a bounded shard, not a reordered full sweep.
|
|
312
|
-
//
|
|
306
|
+
// Seven one-minute generations cover every removal class once while startup/manual
|
|
313
307
|
// cleanup retains the complete all-phase contract.
|
|
314
308
|
return [REMOVAL_PHASES[offset]!];
|
|
315
309
|
}
|
|
@@ -1058,8 +1052,6 @@ function shouldPersistCleanupAudit(report: RuntimeCleanupReport): boolean {
|
|
|
1058
1052
|
|| report.removedCleanupArtifactPaths.length
|
|
1059
1053
|
|| report.removedReleasePaths.length
|
|
1060
1054
|
|| report.releaseSessionCandidates.removedPaths.length
|
|
1061
|
-
|| report.removedCodegraphLocatorPaths.length
|
|
1062
|
-
|| report.removedCodegraphCachePaths.length
|
|
1063
1055
|
|| report.removedRepositoryNamespacePaths.length
|
|
1064
1056
|
|| report.sqliteMaintenance?.vacuumed
|
|
1065
1057
|
|| report.sqliteMaintenance?.skippedReason === 'database_busy'
|
|
@@ -1124,11 +1116,7 @@ export function cleanupControllerRuntimeState(
|
|
|
1124
1116
|
let artifactRetention: ReturnType<typeof cleanupWorkPreservationArtifacts> | undefined;
|
|
1125
1117
|
let releaseRetention: ReturnType<typeof cleanupControllerReleaseHistory> | undefined;
|
|
1126
1118
|
let releaseSessionCandidates: ReleaseSessionCandidateRetentionReport | undefined;
|
|
1127
|
-
let codegraphRetention: ReturnType<typeof cleanupCodegraphCaches> | undefined;
|
|
1128
1119
|
let repositoryNamespaceRetention: ReturnType<typeof cleanupRetiredRepositoryNamespaces> | undefined;
|
|
1129
|
-
const removedCodegraphLocatorPaths: string[] = [];
|
|
1130
|
-
let codegraphLocatorInspected = 0;
|
|
1131
|
-
let codegraphLocatorRetained = 0;
|
|
1132
1120
|
const schedulerHistory = {
|
|
1133
1121
|
removedOccurrencePaths: [] as string[],
|
|
1134
1122
|
removedDecisionPaths: [] as string[],
|
|
@@ -1263,65 +1251,6 @@ export function cleanupControllerRuntimeState(
|
|
|
1263
1251
|
errors.push(...editSessionHistory.errors.map((error) => `edit-session-history ${error}`));
|
|
1264
1252
|
continue;
|
|
1265
1253
|
}
|
|
1266
|
-
if (phase === 'caches') {
|
|
1267
|
-
const registered = listRepositories(home, { includeRemoved: true });
|
|
1268
|
-
const repositoryRoots = new Set<string>();
|
|
1269
|
-
for (const repository of registered) {
|
|
1270
|
-
repositoryRoots.add(resolve(repository.canonicalRoot));
|
|
1271
|
-
repositoryRoots.add(resolve(repository.localRoot));
|
|
1272
|
-
for (const checkout of repository.checkouts) {
|
|
1273
|
-
repositoryRoots.add(resolve(checkout.canonicalRoot));
|
|
1274
|
-
repositoryRoots.add(resolve(checkout.localRoot));
|
|
1275
|
-
}
|
|
1276
|
-
}
|
|
1277
|
-
const protectedRepositoryRoots = new Set<string>();
|
|
1278
|
-
let locatorScanRemaining = maxEntries;
|
|
1279
|
-
for (const repositoryRoot of [...repositoryRoots].sort()) {
|
|
1280
|
-
if (!existsSync(repositoryRoot)) continue;
|
|
1281
|
-
if (locatorScanRemaining <= 0) {
|
|
1282
|
-
protectedRepositoryRoots.add(repositoryRoot);
|
|
1283
|
-
skippedByReason.codegraph_locator_scan_budget_exhausted = (skippedByReason.codegraph_locator_scan_budget_exhausted ?? 0) + 1;
|
|
1284
|
-
continue;
|
|
1285
|
-
}
|
|
1286
|
-
const locatorReport = cleanupStaleCodegraphLocators(
|
|
1287
|
-
repositoryRoot,
|
|
1288
|
-
home,
|
|
1289
|
-
locatorScanRemaining,
|
|
1290
|
-
removalBudget.remaining,
|
|
1291
|
-
);
|
|
1292
|
-
locatorScanRemaining = Math.max(0, locatorScanRemaining - locatorReport.inspected);
|
|
1293
|
-
removalBudget.remaining = Math.max(0, removalBudget.remaining - locatorReport.attempted);
|
|
1294
|
-
if (locatorReport.budgetExhausted && removalBudget.remaining <= 0) removalBudget.exhausted = true;
|
|
1295
|
-
codegraphLocatorInspected += locatorReport.inspected;
|
|
1296
|
-
codegraphLocatorRetained += Math.max(0, locatorReport.inspected - locatorReport.removed.length);
|
|
1297
|
-
removedCodegraphLocatorPaths.push(...locatorReport.removed.map((name) => join(repositoryRoot, name)));
|
|
1298
|
-
Object.entries(locatorReport.skippedByReason).forEach(([key, value]) => {
|
|
1299
|
-
skippedByReason[`codegraph_locator_${key}`] = (skippedByReason[`codegraph_locator_${key}`] ?? 0) + value;
|
|
1300
|
-
});
|
|
1301
|
-
const protectionUncertain = locatorReport.budgetExhausted
|
|
1302
|
-
|| locatorReport.errors.length > 0
|
|
1303
|
-
|| (locatorReport.skippedByReason.locator_identity_unproven ?? 0) > 0;
|
|
1304
|
-
if (locatorReport.active.length > 0 || protectionUncertain) protectedRepositoryRoots.add(repositoryRoot);
|
|
1305
|
-
if (locatorReport.budgetExhausted) {
|
|
1306
|
-
skippedByReason.codegraph_locator_scan_budget_exhausted = (skippedByReason.codegraph_locator_scan_budget_exhausted ?? 0) + 1;
|
|
1307
|
-
}
|
|
1308
|
-
errors.push(...locatorReport.errors.map((error) => `codegraph-locator ${repositoryRoot}: ${error}`));
|
|
1309
|
-
}
|
|
1310
|
-
codegraphRetention = cleanupCodegraphCaches(home, {
|
|
1311
|
-
nowMs,
|
|
1312
|
-
retentionMs: options.codegraphCacheRetentionMs,
|
|
1313
|
-
maxEntries,
|
|
1314
|
-
maxRemovals: removalBudget.remaining,
|
|
1315
|
-
protectedRepositoryRoots: [...protectedRepositoryRoots],
|
|
1316
|
-
});
|
|
1317
|
-
removalBudget.remaining = Math.max(0, removalBudget.remaining - codegraphRetention.attempted);
|
|
1318
|
-
if (codegraphRetention.budgetExhausted) removalBudget.exhausted = true;
|
|
1319
|
-
Object.entries(codegraphRetention.skippedByReason).forEach(([key, value]) => {
|
|
1320
|
-
skippedByReason[`codegraph_cache_${key}`] = (skippedByReason[`codegraph_cache_${key}`] ?? 0) + value;
|
|
1321
|
-
});
|
|
1322
|
-
errors.push(...codegraphRetention.errors.map((error) => `codegraph-cache ${error}`));
|
|
1323
|
-
continue;
|
|
1324
|
-
}
|
|
1325
1254
|
if (phase === 'repository_namespaces') {
|
|
1326
1255
|
repositoryNamespaceRetention = cleanupRetiredRepositoryNamespaces(home, {
|
|
1327
1256
|
nowMs,
|
|
@@ -1363,12 +1292,11 @@ export function cleanupControllerRuntimeState(
|
|
|
1363
1292
|
dependencyCleanup ??= { migrated: [], skippedByReason: {} } as ReturnType<typeof cleanupManagedWorktreeDependencyCopies>;
|
|
1364
1293
|
temporaryCleanup ??= { removed: [], reclaim: emptyReclaimMetrics() } as ReturnType<typeof cleanupTemporaryStatePaths>;
|
|
1365
1294
|
artifactRetention ??= { removedPaths: [], attempted: 0, budgetExhausted: false, skippedByReason: {}, errors: [], inspected: 0, eligible: 0, retained: 0, skipped: 0, reclaimedBytes: 0, unknownReclaimedByteCount: 0 } as ReturnType<typeof cleanupWorkPreservationArtifacts>;
|
|
1366
|
-
codegraphRetention ??= { policyVersion: CODEGRAPH_CACHE_RETENTION_POLICY_VERSION, removedPaths: [], attempted: 0, budgetExhausted: false, skippedByReason: {}, errors: [], inspected: 0, eligible: 0, retained: 0, reclaimedBytes: 0, unknownReclaimedByteCount: 0, observedBytes: 0, unknownObservedByteCount: 0, protected: 0, protectedOverCapacity: 0, truncated: false } as ReturnType<typeof cleanupCodegraphCaches>;
|
|
1367
1295
|
repositoryNamespaceRetention ??= { policyVersion: 'repository-namespace-retention-v1', removedPaths: [], attempted: 0, budgetExhausted: false, skippedByReason: {}, errors: [], inspected: 0, eligible: 0, retained: 0, reclaimedBytes: 0, unknownReclaimedByteCount: 0 } as ReturnType<typeof cleanupRetiredRepositoryNamespaces>;
|
|
1368
1296
|
releaseRetention ??= { removedPaths: [], removedBackupPaths: [], attempted: 0, budgetExhausted: false, skippedByReason: {}, errors: [], inspected: 0, eligible: 0, retained: 0, skipped: 0, reclaimedBytes: 0, unknownReclaimedByteCount: 0, reclaimedBackupBytes: 0, unknownReclaimedBackupByteCount: 0 } as ReturnType<typeof cleanupControllerReleaseHistory>;
|
|
1369
1297
|
releaseSessionCandidates ??= emptyReleaseSessionCandidateRetentionReport();
|
|
1370
1298
|
}
|
|
1371
|
-
if (!worktrees || !dependencyCleanup || !temporaryCleanup || !artifactRetention || !
|
|
1299
|
+
if (!worktrees || !dependencyCleanup || !temporaryCleanup || !artifactRetention || !repositoryNamespaceRetention || !releaseRetention || !releaseSessionCandidates) {
|
|
1372
1300
|
throw new Error('RUNTIME_CLEANUP_PHASE_INCOMPLETE');
|
|
1373
1301
|
}
|
|
1374
1302
|
const removedTemporaryPaths = temporaryCleanup.removed.sort();
|
|
@@ -1377,8 +1305,6 @@ export function cleanupControllerRuntimeState(
|
|
|
1377
1305
|
const removedScheduleOccurrencePaths = schedulerHistory.removedOccurrencePaths.sort();
|
|
1378
1306
|
const removedScheduleDecisionPaths = schedulerHistory.removedDecisionPaths.sort();
|
|
1379
1307
|
const removedEditSessionPaths = editSessionHistory.removedPaths.sort();
|
|
1380
|
-
removedCodegraphLocatorPaths.sort();
|
|
1381
|
-
const removedCodegraphCachePaths = codegraphRetention.removedPaths.sort();
|
|
1382
1308
|
const removedRepositoryNamespacePaths = repositoryNamespaceRetention.removedPaths.sort();
|
|
1383
1309
|
let sqliteMaintenance: ControlPlaneDatabaseMaintenanceReport | undefined;
|
|
1384
1310
|
if (!periodic || removalPhases.includes('releases')) try {
|
|
@@ -1392,7 +1318,7 @@ export function cleanupControllerRuntimeState(
|
|
|
1392
1318
|
} catch (error) {
|
|
1393
1319
|
errors.push(errorText('SQLite maintenance', error));
|
|
1394
1320
|
}
|
|
1395
|
-
const inspectedPaths = referenceBudget.inspected + worktreeBudget.inspected + dependencyBudget.inspected + tempBudget.inspected + schedulerBudget.inspected + editSessionBudget.inspected + artifactRetention.inspected +
|
|
1321
|
+
const inspectedPaths = referenceBudget.inspected + worktreeBudget.inspected + dependencyBudget.inspected + tempBudget.inspected + schedulerBudget.inspected + editSessionBudget.inspected + artifactRetention.inspected + repositoryNamespaceRetention.inspected + releaseRetention.inspected + releaseSessionCandidates.inspected;
|
|
1396
1322
|
const budgetExhausted = referenceBudget.exhausted || worktreeBudget.exhausted || dependencyBudget.exhausted || tempBudget.exhausted || schedulerBudget.exhausted || editSessionBudget.exhausted || removalBudget.exhausted || releaseSessionCandidates.truncated;
|
|
1397
1323
|
if (pidFiles.skipped.length > 0 && removalBudget.exhausted) skippedByReason.cleanup_budget_exhausted = (skippedByReason.cleanup_budget_exhausted ?? 0) + pidFiles.skipped.length;
|
|
1398
1324
|
if (pidFiles.skipped.length > 0 && !removalBudget.exhausted) skippedByReason.active_owner = (skippedByReason.active_owner ?? 0) + pidFiles.skipped.length;
|
|
@@ -1403,9 +1329,9 @@ export function cleanupControllerRuntimeState(
|
|
|
1403
1329
|
unknownByteCount: worktrees.reclaim.unknownReclaimedByteCount,
|
|
1404
1330
|
},
|
|
1405
1331
|
runtime_temp: {
|
|
1406
|
-
count: pidFiles.removed.length + removedTemporaryPaths.length
|
|
1332
|
+
count: pidFiles.removed.length + removedTemporaryPaths.length,
|
|
1407
1333
|
bytes: pidFiles.reclaim.reclaimedBytes + temporaryCleanup.reclaim.reclaimedBytes,
|
|
1408
|
-
unknownByteCount: pidFiles.reclaim.unknownReclaimedByteCount + temporaryCleanup.reclaim.unknownReclaimedByteCount
|
|
1334
|
+
unknownByteCount: pidFiles.reclaim.unknownReclaimedByteCount + temporaryCleanup.reclaim.unknownReclaimedByteCount,
|
|
1409
1335
|
},
|
|
1410
1336
|
work: {
|
|
1411
1337
|
count: removedCleanupArtifactPaths.length,
|
|
@@ -1437,11 +1363,6 @@ export function cleanupControllerRuntimeState(
|
|
|
1437
1363
|
bytes: editSessionHistory.reclaimedBytes,
|
|
1438
1364
|
unknownByteCount: editSessionHistory.unknownReclaimedByteCount,
|
|
1439
1365
|
},
|
|
1440
|
-
codegraph_cache: {
|
|
1441
|
-
count: removedCodegraphCachePaths.length,
|
|
1442
|
-
bytes: codegraphRetention.reclaimedBytes,
|
|
1443
|
-
unknownByteCount: codegraphRetention.unknownReclaimedByteCount,
|
|
1444
|
-
},
|
|
1445
1366
|
repository_controller_home_namespace: {
|
|
1446
1367
|
count: removedRepositoryNamespacePaths.length,
|
|
1447
1368
|
bytes: repositoryNamespaceRetention.reclaimedBytes,
|
|
@@ -1489,8 +1410,6 @@ export function cleanupControllerRuntimeState(
|
|
|
1489
1410
|
removedScheduleOccurrencePaths,
|
|
1490
1411
|
removedScheduleDecisionPaths,
|
|
1491
1412
|
removedEditSessionPaths,
|
|
1492
|
-
removedCodegraphLocatorPaths,
|
|
1493
|
-
removedCodegraphCachePaths,
|
|
1494
1413
|
removedRepositoryNamespacePaths,
|
|
1495
1414
|
sqliteMaintenance,
|
|
1496
1415
|
retiredOwnerlessWorkAuthorities: retiredOwnerlessWorkAuthorities.sort(),
|
|
@@ -1502,11 +1421,11 @@ export function cleanupControllerRuntimeState(
|
|
|
1502
1421
|
lifecycleMetrics,
|
|
1503
1422
|
cycle: {
|
|
1504
1423
|
scanned: inspectedPaths,
|
|
1505
|
-
eligible: retiredOwnerlessWorkAuthorities.length + pidFiles.removed.length + worktrees.removed.length + dependencyCleanup.migrated.length + removedTemporaryPaths.length + artifactRetention.eligible + schedulerHistory.eligible + editSessionHistory.eligible +
|
|
1506
|
-
attempted: retiredOwnerlessWorkAuthorities.length + pidFiles.removed.length + worktrees.removed.length + dependencyCleanup.migrated.length + removedTemporaryPaths.length + artifactRetention.attempted + schedulerHistory.attempted + editSessionHistory.attempted +
|
|
1507
|
-
removed: retiredOwnerlessWorkAuthorities.length + pidFiles.removed.length + worktrees.removed.length + dependencyCleanup.migrated.length + removedTemporaryPaths.length + removedCleanupArtifactPaths.length + removedScheduleOccurrencePaths.length + removedScheduleDecisionPaths.length + removedEditSessionPaths.length +
|
|
1508
|
-
retained: pidFiles.skipped.length + worktrees.skippedActive.length + artifactRetention.retained + schedulerHistory.retained + editSessionHistory.retained +
|
|
1509
|
-
skipped: Math.max(0, inspectedPaths - pidFiles.removed.length - worktrees.removed.length - dependencyCleanup.migrated.length - removedTemporaryPaths.length - removedCleanupArtifactPaths.length - removedScheduleOccurrencePaths.length - removedScheduleDecisionPaths.length - removedEditSessionPaths.length -
|
|
1424
|
+
eligible: retiredOwnerlessWorkAuthorities.length + pidFiles.removed.length + worktrees.removed.length + dependencyCleanup.migrated.length + removedTemporaryPaths.length + artifactRetention.eligible + schedulerHistory.eligible + editSessionHistory.eligible + repositoryNamespaceRetention.eligible + releaseRetention.eligible + releaseSessionCandidates.eligible,
|
|
1425
|
+
attempted: retiredOwnerlessWorkAuthorities.length + pidFiles.removed.length + worktrees.removed.length + dependencyCleanup.migrated.length + removedTemporaryPaths.length + artifactRetention.attempted + schedulerHistory.attempted + editSessionHistory.attempted + repositoryNamespaceRetention.attempted + releaseRetention.attempted + releaseSessionCandidates.attempted + errors.length,
|
|
1426
|
+
removed: retiredOwnerlessWorkAuthorities.length + pidFiles.removed.length + worktrees.removed.length + dependencyCleanup.migrated.length + removedTemporaryPaths.length + removedCleanupArtifactPaths.length + removedScheduleOccurrencePaths.length + removedScheduleDecisionPaths.length + removedEditSessionPaths.length + removedRepositoryNamespacePaths.length + removedReleasePaths.length + releaseSessionCandidates.removedPaths.length,
|
|
1427
|
+
retained: pidFiles.skipped.length + worktrees.skippedActive.length + artifactRetention.retained + schedulerHistory.retained + editSessionHistory.retained + repositoryNamespaceRetention.retained + releaseRetention.retained + releaseSessionCandidates.retainedCount,
|
|
1428
|
+
skipped: Math.max(0, inspectedPaths - pidFiles.removed.length - worktrees.removed.length - dependencyCleanup.migrated.length - removedTemporaryPaths.length - removedCleanupArtifactPaths.length - removedScheduleOccurrencePaths.length - removedScheduleDecisionPaths.length - removedEditSessionPaths.length - removedRepositoryNamespacePaths.length - removedReleasePaths.length - releaseSessionCandidates.removedPaths.length - errors.length),
|
|
1510
1429
|
failed: errors.length,
|
|
1511
1430
|
truncated: budgetExhausted,
|
|
1512
1431
|
budgetExhausted,
|