@straiffi/archon 1.11.8 → 1.11.10
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/dist/client/assets/{ActivityView-BKLYfPXE.js → ActivityView-RnE0983a.js} +1 -1
- package/dist/client/assets/{GitHubActionsDialog-yqqwcP8z.js → GitHubActionsDialog-PaNJhVzv.js} +1 -1
- package/dist/client/assets/{VerificationDialog-DhyIDs1V.js → VerificationDialog-PGrCJ1DJ.js} +1 -1
- package/dist/client/assets/{architectureDiagram-3BPJPVTR-5krjaOU4.js → architectureDiagram-3BPJPVTR-CKd1Dmpz.js} +1 -1
- package/dist/client/assets/{blockDiagram-GPEHLZMM-Duo2q2gn.js → blockDiagram-GPEHLZMM-M3OjRhA0.js} +1 -1
- package/dist/client/assets/{c4Diagram-AAUBKEIU-D2lxLmwB.js → c4Diagram-AAUBKEIU-VWBcGTXO.js} +1 -1
- package/dist/client/assets/channel-CBQk1IQf.js +1 -0
- package/dist/client/assets/{chunk-2J33WTMH-Dbo5lbSs.js → chunk-2J33WTMH-C4WpCUzU.js} +1 -1
- package/dist/client/assets/{chunk-3OPIFGDE-mjZrNxCg.js → chunk-3OPIFGDE-BQ-j87T6.js} +1 -1
- package/dist/client/assets/{chunk-5ZQYHXKU-C2nUnqLh.js → chunk-5ZQYHXKU-BCZvyT9u.js} +1 -1
- package/dist/client/assets/{chunk-727SXJPM-7eUgm0cn.js → chunk-727SXJPM-DZP_PMNz.js} +1 -1
- package/dist/client/assets/{chunk-AQP2D5EJ-DouWpaE0.js → chunk-AQP2D5EJ-Cejit8wh.js} +1 -1
- package/dist/client/assets/{chunk-CSCIHK7Q-DesC0Fd7.js → chunk-CSCIHK7Q-CvXC4rkf.js} +1 -1
- package/dist/client/assets/{chunk-KSCS5N6A-DAsFowjn.js → chunk-KSCS5N6A-hTgJWrr3.js} +1 -1
- package/dist/client/assets/{chunk-L5ZTLDWV-w7V9r0Cw.js → chunk-L5ZTLDWV-bSK1mut3.js} +1 -1
- package/dist/client/assets/{chunk-LZXEDZCA-BCnxdvaJ.js → chunk-LZXEDZCA-Y2hnSpRr.js} +2 -2
- package/dist/client/assets/{chunk-ND2GUHAM-DO8HdZCX.js → chunk-ND2GUHAM-BiHH5lS5.js} +1 -1
- package/dist/client/assets/{chunk-NZK2D7GU-37Rbzvm9.js → chunk-NZK2D7GU-DdxcwTTc.js} +1 -1
- package/dist/client/assets/{chunk-O5CBEL6O-DTjB0GGI.js → chunk-O5CBEL6O-BZIbgKJr.js} +1 -1
- package/dist/client/assets/{chunk-WU5MYG2G-CAonpz-x.js → chunk-WU5MYG2G-D3yvqt9P.js} +1 -1
- package/dist/client/assets/classDiagram-4FO5ZUOK-BjizLQoS.js +1 -0
- package/dist/client/assets/classDiagram-v2-Q7XG4LA2-B6qpU-kO.js +1 -0
- package/dist/client/assets/{dagre-BM42HDAG-DDET9pbe.js → dagre-BM42HDAG-CZQZPoaH.js} +1 -1
- package/dist/client/assets/{diagram-2AECGRRQ-r_bu5Cgo.js → diagram-2AECGRRQ-DU7zsX_b.js} +1 -1
- package/dist/client/assets/{diagram-5GNKFQAL-QlPCzkUJ.js → diagram-5GNKFQAL-BCCoHKlp.js} +1 -1
- package/dist/client/assets/{diagram-KO2AKTUF-D5SzFOzP.js → diagram-KO2AKTUF-BR_GNJLN.js} +1 -1
- package/dist/client/assets/{diagram-LMA3HP47-BJcWw3_s.js → diagram-LMA3HP47-B6LZOUTc.js} +1 -1
- package/dist/client/assets/{diagram-OG6HWLK6-CpsR9p94.js → diagram-OG6HWLK6-w89pjkBe.js} +1 -1
- package/dist/client/assets/{erDiagram-TEJ5UH35-Dq-K6YrY.js → erDiagram-TEJ5UH35-BReR8ngL.js} +1 -1
- package/dist/client/assets/{flowDiagram-I6XJVG4X-39hKkH4n.js → flowDiagram-I6XJVG4X-CGwpJ9iQ.js} +1 -1
- package/dist/client/assets/{ganttDiagram-6RSMTGT7-DaeBYOLz.js → ganttDiagram-6RSMTGT7-BcOPz4K9.js} +1 -1
- package/dist/client/assets/{gitGraphDiagram-PVQCEYII-CCeo9Czy.js → gitGraphDiagram-PVQCEYII-m6-x38H5.js} +1 -1
- package/dist/client/assets/index-D_brbG-0.css +2 -0
- package/dist/client/assets/index-yUckGfMu.js +293 -0
- package/dist/client/assets/{infoDiagram-5YYISTIA-jgHH1Ta7.js → infoDiagram-5YYISTIA-11iWCl_6.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-YF4QCWOH-gYPsFHRd.js → ishikawaDiagram-YF4QCWOH-DbeJ14eh.js} +1 -1
- package/dist/client/assets/{journeyDiagram-JHISSGLW-C5m8h46L.js → journeyDiagram-JHISSGLW-BYFdU9SL.js} +1 -1
- package/dist/client/assets/{kanban-definition-UN3LZRKU-rag4UxAB.js → kanban-definition-UN3LZRKU-Dyc52bol.js} +1 -1
- package/dist/client/assets/{line-CAWxQrG3.js → line-CBauYkto.js} +1 -1
- package/dist/client/assets/{mermaid-parser.core-C5Hj2V3U.js → mermaid-parser.core-BBbL8arn.js} +1 -1
- package/dist/client/assets/{mermaid.core-BxRoDLxv.js → mermaid.core-CB2mIiiP.js} +3 -3
- package/dist/client/assets/{mindmap-definition-RKZ34NQL-BUV37aab.js → mindmap-definition-RKZ34NQL-CvWYGfWn.js} +1 -1
- package/dist/client/assets/{pieDiagram-4H26LBE5-CMwVVhpZ.js → pieDiagram-4H26LBE5-BsbIF0rU.js} +1 -1
- package/dist/client/assets/{quadrantDiagram-W4KKPZXB-DYHRB097.js → quadrantDiagram-W4KKPZXB-Bvs09UnJ.js} +1 -1
- package/dist/client/assets/{requirementDiagram-4Y6WPE33-BLtNTWtR.js → requirementDiagram-4Y6WPE33-B3U5xSYZ.js} +1 -1
- package/dist/client/assets/{sankeyDiagram-5OEKKPKP-Bcd-UVHZ.js → sankeyDiagram-5OEKKPKP-Dds8cy2g.js} +1 -1
- package/dist/client/assets/{sequenceDiagram-3UESZ5HK-Die4-puP.js → sequenceDiagram-3UESZ5HK-BoMb08Ay.js} +1 -1
- package/dist/client/assets/{stateDiagram-AJRCARHV-TBbOyQST.js → stateDiagram-AJRCARHV-atm7kYDg.js} +1 -1
- package/dist/client/assets/stateDiagram-v2-BHNVJYJU-EYJsus1Q.js +1 -0
- package/dist/client/assets/{timeline-definition-PNZ67QCA-CZ3E6e1X.js → timeline-definition-PNZ67QCA-C7U--kCB.js} +1 -1
- package/dist/client/assets/{vennDiagram-CIIHVFJN-CY_kofMf.js → vennDiagram-CIIHVFJN-BoPWwPSI.js} +1 -1
- package/dist/client/assets/{wardleyDiagram-YWT4CUSO-TzBIV1Er.js → wardleyDiagram-YWT4CUSO-CJ6Y7g3b.js} +1 -1
- package/dist/client/assets/{xychartDiagram-2RQKCTM6-BTvKU2GO.js → xychartDiagram-2RQKCTM6-rN5shXue.js} +1 -1
- package/dist/client/index.html +2 -2
- package/dist/server/db.js +2 -0
- package/dist/server/db.js.map +1 -1
- package/dist/server/index.js +212 -238
- package/dist/server/index.js.map +1 -1
- package/dist/server/lib/agent.js +23 -7
- package/dist/server/lib/agent.js.map +1 -1
- package/dist/server/lib/agentProvider.js +62 -15
- package/dist/server/lib/agentProvider.js.map +1 -1
- package/dist/server/lib/bundleCleanupManagement.js +181 -0
- package/dist/server/lib/bundleCleanupManagement.js.map +1 -0
- package/dist/server/lib/bundleCleanupRoutes.js +36 -0
- package/dist/server/lib/bundleCleanupRoutes.js.map +1 -0
- package/dist/server/lib/bundleDeletion.js +257 -0
- package/dist/server/lib/bundleDeletion.js.map +1 -0
- package/dist/server/lib/bundleDeletionRoutes.js +73 -0
- package/dist/server/lib/bundleDeletionRoutes.js.map +1 -0
- package/dist/server/lib/bundleDeletionSchema.js +134 -0
- package/dist/server/lib/bundleDeletionSchema.js.map +1 -0
- package/dist/server/lib/bundleDeletionStore.js +18 -0
- package/dist/server/lib/bundleDeletionStore.js.map +1 -0
- package/dist/server/lib/bundleWorktreeCleanup.js +160 -0
- package/dist/server/lib/bundleWorktreeCleanup.js.map +1 -0
- package/dist/server/lib/codexAgentProvider.js +186 -123
- package/dist/server/lib/codexAgentProvider.js.map +1 -1
- package/dist/server/lib/diffExplanation.js +485 -0
- package/dist/server/lib/diffExplanation.js.map +1 -0
- package/dist/server/lib/diffExplanationJobs.js +605 -0
- package/dist/server/lib/diffExplanationJobs.js.map +1 -0
- package/dist/server/lib/diffExplanationRoutes.js +236 -0
- package/dist/server/lib/diffExplanationRoutes.js.map +1 -0
- package/dist/server/lib/dockerSandboxCleanup.js +68 -0
- package/dist/server/lib/dockerSandboxCleanup.js.map +1 -0
- package/dist/server/lib/dockerSandboxEnvironmentOperations.js +4 -3
- package/dist/server/lib/dockerSandboxEnvironmentOperations.js.map +1 -1
- package/dist/server/lib/dockerSandboxExecutionEnvironment.js +35 -59
- package/dist/server/lib/dockerSandboxExecutionEnvironment.js.map +1 -1
- package/dist/server/lib/dockerSandboxReleaseProfiles.js +1 -1
- package/dist/server/lib/dockerSandboxReleaseProfiles.js.map +1 -1
- package/dist/server/lib/executionEnvironmentOperations.js +7 -2
- package/dist/server/lib/executionEnvironmentOperations.js.map +1 -1
- package/dist/server/lib/executionEnvironmentReconciliation.js +34 -169
- package/dist/server/lib/executionEnvironmentReconciliation.js.map +1 -1
- package/dist/server/lib/executionEnvironmentTargetCleanup.js +232 -0
- package/dist/server/lib/executionEnvironmentTargetCleanup.js.map +1 -0
- package/dist/server/lib/executionEnvironments.js +1 -0
- package/dist/server/lib/executionEnvironments.js.map +1 -1
- package/dist/server/lib/executionTargets.js +1 -1
- package/dist/server/lib/executionTargets.js.map +1 -1
- package/dist/server/lib/git.js +205 -35
- package/dist/server/lib/git.js.map +1 -1
- package/dist/server/lib/i18n/resources.js +10 -0
- package/dist/server/lib/i18n/resources.js.map +1 -1
- package/dist/server/lib/lightweightPrompt.js +284 -52
- package/dist/server/lib/lightweightPrompt.js.map +1 -1
- package/dist/server/lib/oneShotCancellation.js +54 -0
- package/dist/server/lib/oneShotCancellation.js.map +1 -0
- package/dist/server/lib/opencodeExecutionReliability.js +46 -2
- package/dist/server/lib/opencodeExecutionReliability.js.map +1 -1
- package/dist/server/lib/projectMemoryScan.js +3 -46
- package/dist/server/lib/projectMemoryScan.js.map +1 -1
- package/dist/server/lib/promptDiffSafety.js +53 -0
- package/dist/server/lib/promptDiffSafety.js.map +1 -0
- package/dist/server/lib/repositorySecrets.js +69 -0
- package/dist/server/lib/repositorySecrets.js.map +1 -0
- package/dist/server/lib/tickets.js +3 -2
- package/dist/server/lib/tickets.js.map +1 -1
- package/dist/server/lib/types.js.map +1 -1
- package/dist/server/lib/unifiedDiff.js +459 -0
- package/dist/server/lib/unifiedDiff.js.map +1 -0
- package/dist/server/lib/workspaceDiff.js +156 -0
- package/dist/server/lib/workspaceDiff.js.map +1 -0
- package/dist/server/lib/workspaceDiffRoutes.js +63 -0
- package/dist/server/lib/workspaceDiffRoutes.js.map +1 -0
- package/dist/server/lib/workspaceTargets.js +88 -0
- package/dist/server/lib/workspaceTargets.js.map +1 -0
- package/dist/server/workers/chat.js +71 -1
- package/dist/server/workers/chat.js.map +1 -1
- package/dist/server/workers/plan.js +24 -2
- package/dist/server/workers/plan.js.map +1 -1
- package/dist/server/workers/review.js +19 -13
- package/dist/server/workers/review.js.map +1 -1
- package/package.json +1 -1
- package/dist/client/assets/channel-Cf2U7nU4.js +0 -1
- package/dist/client/assets/classDiagram-4FO5ZUOK-CK8VsU-L.js +0 -1
- package/dist/client/assets/classDiagram-v2-Q7XG4LA2-RvySOEwt.js +0 -1
- package/dist/client/assets/index-B9-M1vEw.js +0 -287
- package/dist/client/assets/index-CbYT3N3U.css +0 -2
- package/dist/client/assets/stateDiagram-v2-BHNVJYJU-BRotXz7h.js +0 -1
package/dist/server/index.js
CHANGED
|
@@ -8,8 +8,16 @@ import { createHash, randomUUID } from 'crypto';
|
|
|
8
8
|
import cors from 'cors';
|
|
9
9
|
import { adjectives, animals, uniqueNamesGenerator } from 'unique-names-generator';
|
|
10
10
|
import db from './db.js';
|
|
11
|
+
import { registerBundleDeletionRoutes } from './lib/bundleDeletionRoutes.js';
|
|
12
|
+
import { registerBundleCleanupRoutes } from './lib/bundleCleanupRoutes.js';
|
|
13
|
+
import { attemptBundleDeletion, beginBundleDeletion, BundleDeletionConflict, resumeBundleDeletions } from './lib/bundleDeletion.js';
|
|
14
|
+
import { isBundleDeletionBranchReserved } from './lib/bundleDeletionStore.js';
|
|
11
15
|
import { pruneAllRepositoryGraphScans } from './lib/repositoryGraphRetention.js';
|
|
12
16
|
import { registerRepositoryGraphRoutes } from './lib/repositoryGraphRoutes.js';
|
|
17
|
+
import { registerWorkspaceDiffRoutes } from './lib/workspaceDiffRoutes.js';
|
|
18
|
+
import { getWorkspaceDiffAsync } from './lib/workspaceDiff.js';
|
|
19
|
+
import { registerDiffExplanationRoutes, registerDiffExplanationSocketHandlers, } from './lib/diffExplanationRoutes.js';
|
|
20
|
+
import { openDiffExplanationJobAdmissions, shutdownDiffExplanationJobs, } from './lib/diffExplanationJobs.js';
|
|
13
21
|
import { registerExecutionEnvironmentRoutes } from './lib/executionEnvironmentRoutes.js';
|
|
14
22
|
import { registerExecutionProfileRoutes } from './lib/executionProfileRoutes.js';
|
|
15
23
|
import { executionProfileSupportsChecks, hasConfiguredCheckPolicy } from './lib/executionProfileCheckPolicy.js';
|
|
@@ -34,7 +42,7 @@ import { resolveSessionTargetForExecution } from './lib/sessionTargets.js';
|
|
|
34
42
|
import { createChatMessage, getChatMessage, updateChatMessageContent } from './lib/chatMessages.js';
|
|
35
43
|
import { attachChatTicketProposalsToMessageContent } from './lib/chatTicketProposals.js';
|
|
36
44
|
import { createCorsOriginResolver, parseCorsOrigins } from './lib/cors.js';
|
|
37
|
-
import { abortGitOperation, branchExistsLocally, commitGitChanges, continueGitRebase, createWorktreeAsync, deleteLocalBranch, deleteWorktree, getActiveRebaseBranch,
|
|
45
|
+
import { abortGitOperation, branchExistsLocally, commitGitChanges, continueGitRebase, createWorktreeAsync, deleteLocalBranch, deleteWorktree, getActiveRebaseBranch, getGitDiffFiles, getGitDiffFilesAsync, getGitStatus, getGitStatusAsync, getGitUnifiedDiff, getLocalProjectBranch, getProjectBranch, getProjectBranchAsync, getProjectBranchSuggestions, getRebaseStagedDiff, getSelectedGitUnifiedDiff, getUnpushedCommitDiffForCwd, getUnpushedCommitSummaryForCwd, getUnpushedCommitSummaryForCwdAsync, hasKnownBranchRef, listUnpushedCommitsForCwd, pullGitBranch, pushCurrentBranch, rebaseGitBranchOntoBase, resolveExistingWorktreePath, resolveWorktreePath, skipGitRebaseStep, squashUnpushedCommitsForCwd, summarizeTicketDiffFiles, switchProjectBranch, syncWorktreeFilesAsync, undoLatestUnpushedCommitForCwd, UnpushedCommitDiffSelectionError } from './lib/git.js';
|
|
38
46
|
import config from './lib/config.js';
|
|
39
47
|
import { getActiveTool, initializeActiveTool, setActiveTool } from './lib/activeTool.js';
|
|
40
48
|
import { autoConfigProject, ProjectAutoConfigError } from './lib/projectAutoConfig.js';
|
|
@@ -44,7 +52,7 @@ import { resolveDesktopServerHost } from './lib/desktopServerHost.js';
|
|
|
44
52
|
import { emitMobileRealtime, getMobileAccessStatus, regenerateMobilePairingChallenge, revokeMobileSession, setMobileAccessStatusChangeListener, startMobileAccess, stopMobileAccess, } from './lib/mobileAccess.js';
|
|
45
53
|
import { validateDesktopMobileAccessRequest } from './lib/mobileAccessSecurity.js';
|
|
46
54
|
import { refreshChatSessionUsageSnapshot, refreshSpecSessionUsageSnapshot, stopAllBuildAgents, stopAllReviewAgents, stopBuildAgent, stopReviewAgent } from './lib/agent.js';
|
|
47
|
-
import { runTrackedLightweightPrompt } from './lib/lightweightPrompt.js';
|
|
55
|
+
import { openLightweightPromptAdmissions, runTrackedLightweightPrompt, shutdownLightweightPrompts, } from './lib/lightweightPrompt.js';
|
|
48
56
|
import { advanceBuildChain, startBuildChain, stopBuildChain } from './lib/buildChains.js';
|
|
49
57
|
import { activateQueuedBuildSequenceTicket, bindBuildSequenceTicketExecutionRun, compensateBuildSequenceAcceptance, enqueueBundledBuild, getDependencyOrderedBundleTicketIds, getBuildSequenceIdForTicket, getBuildSequenceTicketExecutionProfileSelection, hasActiveSequenceForBundle, releaseQueuedBuildSequenceTicketExecutionRunForRetry, removeTicketFromBuildSequence, stopBuildSequence, } from './lib/buildSequences.js';
|
|
50
58
|
import { resumeRemainingBundleBuilds } from './lib/bundleBuildRecovery.js';
|
|
@@ -101,13 +109,14 @@ import { buildImportedIssueSessionTitle, importProviderHostedIssueImages } from
|
|
|
101
109
|
import { dispatchGitHubWorkflow, getGitHubWorkflowDispatchConfig, getGitHubWorkflowRun, getGitHubWorkflowRunJobs, listGitHubWorkflows, listGitHubWorkflowRuns, } from './lib/integrations/githubActions.js';
|
|
102
110
|
import { clearAllGitHubActionsSessions, getActiveTrackedRunCount, getTrackedGitHubActionsRuns, startGitHubActionsPolling, trackGitHubActionsRun, } from './lib/integrations/githubActionsSessions.js';
|
|
103
111
|
import { reconcileStartupExecutions } from './lib/executionReconciliation.js';
|
|
112
|
+
import { deleteArchivedAbandonedProjectEnvironments } from './lib/executionEnvironmentTargetCleanup.js';
|
|
104
113
|
import { cleanupProjectExecutionEnvironments, cleanupExecutionEnvironmentsForTarget, ExecutionEnvironmentCleanupError, reconcileOrphanedExecutionEnvironments, reconcileExecutionEnvironments, reconcileExecutionEnvironmentLifecycleOperations, reconcileProductionExecutionEnvironmentBindings, } from './lib/executionEnvironmentReconciliation.js';
|
|
105
114
|
import { createExecutionTargetKey, resolveExecutionTarget, resolveProjectRootExecutionTarget } from './lib/executionTargets.js';
|
|
106
115
|
import { getExecutionEnvironmentContextForRun } from './lib/executionEnvironmentContexts.js';
|
|
107
116
|
import { PlanningReferenceError, resolveExternalPlanningDescription } from './lib/integrations/planning.js';
|
|
108
117
|
import { refreshIfNeeded as refreshAssignedIssues, clearCachedAssignedIssues } from './lib/integrations/assignedIssuesCache.js';
|
|
109
118
|
import { buildCommitMessagePrompt, normalizeGeneratedCommitMessage } from './lib/commitMessage.js';
|
|
110
|
-
import { BUNDLE_PULL_REQUEST_SYNC_TTL_MS, getBundlePullRequestRecord, hasOpenBundlePullRequest, serializeBundlePullRequest, shouldSyncBundlePullRequest, syncTrackedBundlePullRequestIfNeeded, upsertBundlePullRequest, upsertBundlePullRequestFromGitHub, } from './lib/bundlePullRequests.js';
|
|
119
|
+
import { BUNDLE_PULL_REQUEST_SYNC_TTL_MS, getBundlePullRequestRecord, hasOpenBundlePullRequest, hasTerminalBundlePullRequest, serializeBundlePullRequest, shouldSyncBundlePullRequest, syncTrackedBundlePullRequestIfNeeded, upsertBundlePullRequest, upsertBundlePullRequestFromGitHub, } from './lib/bundlePullRequests.js';
|
|
111
120
|
import { countInboundProjectLinks, getEffectiveProject, getProjectCheckDefinitions, getProjectById, getProjectTestCommands, getProjectTicketStats, hasProjectRunIde, listInterruptedProjectDeletions, listProjects, releaseInterruptedProjectDeletionClaims, replaceProjectLinks, serializeProject, updateProjectActiveTarget, validateProjectRequiredCheckPolicyReferences, validateProjectPayload, } from './lib/projects.js';
|
|
112
121
|
import { forceReleaseProjectRunCleanup, getActiveRunStatus, getActiveRunStatusForProject, getLastRunStatusForProject, getPreviewStatusForProject, hasRunConfig, isRunning, openIde, openTicketIdeInWorkspace, resolveProjectRunContextKey, runTicketInWorkspace, stopAllTickets, stopTicket } from './lib/run.js';
|
|
113
122
|
import { deleteProjectServiceRunLogs, getServiceRunLogs } from './lib/serviceRunLogs.js';
|
|
@@ -653,52 +662,28 @@ const deleteTicketRecord = async (ticket) => {
|
|
|
653
662
|
}
|
|
654
663
|
const bundledProject = isBundledTicket(ticket) ? getEffectiveProject(ticket) : null;
|
|
655
664
|
const deletedBundleId = ticket.worktree_bundle_id;
|
|
656
|
-
|
|
657
|
-
const bundleResourcesWereCleaned = Boolean(bundledProject
|
|
658
|
-
&& deletedBundle
|
|
659
|
-
&& !isProjectRootBundle(deletedBundle)
|
|
660
|
-
&& countBundleTickets(deletedBundle.id, bundledProject.id) === 1);
|
|
661
|
-
if (bundleResourcesWereCleaned && bundledProject && deletedBundle) {
|
|
662
|
-
try {
|
|
663
|
-
await cleanupWorktreeExecutionTarget({
|
|
664
|
-
project: bundledProject,
|
|
665
|
-
targetKind: 'bundle-worktree',
|
|
666
|
-
targetKey: createExecutionTargetKey(bundledProject.id, {
|
|
667
|
-
kind: 'bundle-worktree',
|
|
668
|
-
bundleId: deletedBundle.id,
|
|
669
|
-
branch: deletedBundle.branch,
|
|
670
|
-
}),
|
|
671
|
-
targetLabel: 'bundle worktree',
|
|
672
|
-
worktreeTarget: 'bundle',
|
|
673
|
-
branch: deletedBundle.branch,
|
|
674
|
-
});
|
|
675
|
-
}
|
|
676
|
-
catch (error) {
|
|
677
|
-
return {
|
|
678
|
-
ok: false,
|
|
679
|
-
status: error instanceof HttpError ? error.status : 500,
|
|
680
|
-
error: error instanceof Error ? error.message : 'Unable to delete the bundle worktree',
|
|
681
|
-
};
|
|
682
|
-
}
|
|
683
|
-
}
|
|
665
|
+
let bundleCleanup;
|
|
684
666
|
let nextBuildTicketId = null;
|
|
685
667
|
let nextBuildSequenceId = null;
|
|
686
668
|
let affectedTicketIds = [];
|
|
687
|
-
if (ticket.state === 'build') {
|
|
688
|
-
const sequenceRemoval = removeTicketFromBuildSequence(ticket.id);
|
|
689
|
-
if (sequenceRemoval.ok) {
|
|
690
|
-
nextBuildTicketId = sequenceRemoval.nextTicketId;
|
|
691
|
-
nextBuildSequenceId = sequenceRemoval.sequenceId;
|
|
692
|
-
affectedTicketIds = sequenceRemoval.affectedTicketIds;
|
|
693
|
-
}
|
|
694
|
-
}
|
|
695
669
|
try {
|
|
696
670
|
withExecutionRunTransaction(transaction => {
|
|
697
671
|
assertTicketServiceTargetMutable(ticket.id);
|
|
672
|
+
if (ticket.state === 'build') {
|
|
673
|
+
const sequenceRemoval = removeTicketFromBuildSequence(ticket.id);
|
|
674
|
+
if (sequenceRemoval.ok) {
|
|
675
|
+
nextBuildTicketId = sequenceRemoval.nextTicketId;
|
|
676
|
+
nextBuildSequenceId = sequenceRemoval.sequenceId;
|
|
677
|
+
affectedTicketIds = sequenceRemoval.affectedTicketIds;
|
|
678
|
+
}
|
|
679
|
+
}
|
|
698
680
|
if (ticket.project_id) {
|
|
699
681
|
deleteTerminalReviewRepairIterationsForTicketInTransaction(transaction, ticket.project_id, ticket.id);
|
|
700
682
|
}
|
|
701
683
|
db.prepare('DELETE FROM tickets WHERE id = ?').run(ticket.id);
|
|
684
|
+
if (bundledProject && deletedBundleId) {
|
|
685
|
+
bundleCleanup = cleanupBundleIfEmpty(deletedBundleId, bundledProject);
|
|
686
|
+
}
|
|
702
687
|
}, io, 'immediate');
|
|
703
688
|
}
|
|
704
689
|
catch (error) {
|
|
@@ -714,18 +699,7 @@ const deleteTicketRecord = async (ticket) => {
|
|
|
714
699
|
catch (error) {
|
|
715
700
|
console.warn(`[ticket-delete] Unable to remove stage snapshots for ticket ${ticket.id}`, error);
|
|
716
701
|
}
|
|
717
|
-
|
|
718
|
-
try {
|
|
719
|
-
await cleanupBundleIfEmpty(deletedBundleId, bundledProject, { resourcesAlreadyCleaned: bundleResourcesWereCleaned });
|
|
720
|
-
}
|
|
721
|
-
catch (error) {
|
|
722
|
-
return {
|
|
723
|
-
ok: false,
|
|
724
|
-
status: error instanceof HttpError ? error.status : 500,
|
|
725
|
-
error: error instanceof Error ? error.message : 'Unable to clean up the empty bundle',
|
|
726
|
-
};
|
|
727
|
-
}
|
|
728
|
-
}
|
|
702
|
+
await finishAutomaticBundleDeletion(bundleCleanup);
|
|
729
703
|
io.emit('ticket:deleted', { id: ticket.id });
|
|
730
704
|
return { ok: true, nextBuildTicketId, nextBuildSequenceId, affectedTicketIds };
|
|
731
705
|
};
|
|
@@ -976,6 +950,9 @@ const uniqueBundleBranchName = (name, projectId, options = {}) => {
|
|
|
976
950
|
let branch = base;
|
|
977
951
|
let suffix = 2;
|
|
978
952
|
const conflictsWithBranch = (candidate) => {
|
|
953
|
+
if (isBundleDeletionBranchReserved(projectId, candidate)) {
|
|
954
|
+
return true;
|
|
955
|
+
}
|
|
979
956
|
const bundleConflict = getBundleByBranch(candidate, projectId);
|
|
980
957
|
if (bundleConflict && bundleConflict.id !== options.currentBundle?.id) {
|
|
981
958
|
return true;
|
|
@@ -1030,7 +1007,7 @@ const createAutoBundleForName = (name, projectId, baseBranch = null) => {
|
|
|
1030
1007
|
}
|
|
1031
1008
|
return bundle;
|
|
1032
1009
|
};
|
|
1033
|
-
const cleanupBundleIfEmpty =
|
|
1010
|
+
const cleanupBundleIfEmpty = (bundleId, project) => {
|
|
1034
1011
|
if (!bundleId) {
|
|
1035
1012
|
return;
|
|
1036
1013
|
}
|
|
@@ -1041,24 +1018,41 @@ const cleanupBundleIfEmpty = async (bundleId, project, options = {}) => {
|
|
|
1041
1018
|
if (isProjectRootBundle(bundle)) {
|
|
1042
1019
|
return;
|
|
1043
1020
|
}
|
|
1044
|
-
|
|
1045
|
-
|
|
1021
|
+
const assertEligible = () => {
|
|
1022
|
+
if (hasActiveBundleCheckWork(project.id, bundle.id)) {
|
|
1023
|
+
throw new BundleDeletionConflict('Cannot delete a bundle while check work is active');
|
|
1024
|
+
}
|
|
1025
|
+
if (hasActiveBundleSpecWork(project.id, bundle.id)) {
|
|
1026
|
+
throw new BundleDeletionConflict('Cannot delete a bundle while Spec work is active');
|
|
1027
|
+
}
|
|
1028
|
+
if (hasActiveBundleRepositoryGraphScan(project.id, bundle.id)) {
|
|
1029
|
+
throw new BundleDeletionConflict('Cannot delete a bundle while a repository graph scan is active');
|
|
1030
|
+
}
|
|
1031
|
+
};
|
|
1032
|
+
const chatSessionIds = listChatSessions(project.id)
|
|
1033
|
+
.filter(session => session.target_bundle_id === bundle.id).map(session => session.id);
|
|
1034
|
+
const specSessionIds = listSpecSessions(project.id)
|
|
1035
|
+
.filter(session => session.target_bundle_id === bundle.id).map(session => session.id);
|
|
1036
|
+
beginBundleDeletion({ project, bundleId: bundle.id, assertEligible });
|
|
1037
|
+
return { projectId: project.id, bundleId: bundle.id, chatSessionIds, specSessionIds };
|
|
1038
|
+
};
|
|
1039
|
+
const finishAutomaticBundleDeletion = async (cleanup) => {
|
|
1040
|
+
if (!cleanup) {
|
|
1041
|
+
return;
|
|
1046
1042
|
}
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
kind: 'bundle-worktree',
|
|
1053
|
-
bundleId: bundle.id,
|
|
1054
|
-
branch: bundle.branch,
|
|
1055
|
-
}),
|
|
1056
|
-
targetLabel: 'bundle worktree',
|
|
1057
|
-
worktreeTarget: 'bundle',
|
|
1058
|
-
branch: bundle.branch,
|
|
1059
|
-
});
|
|
1043
|
+
for (const sessionId of cleanup.chatSessionIds) {
|
|
1044
|
+
const session = getProjectChatSession(cleanup.projectId, sessionId);
|
|
1045
|
+
if (session) {
|
|
1046
|
+
io.emit('chat-session:updated', session);
|
|
1047
|
+
}
|
|
1060
1048
|
}
|
|
1061
|
-
|
|
1049
|
+
for (const sessionId of cleanup.specSessionIds) {
|
|
1050
|
+
const session = getProjectSpecSession(cleanup.projectId, sessionId);
|
|
1051
|
+
if (session) {
|
|
1052
|
+
io.emit('spec-session:updated', session);
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
await attemptBundleDeletion(cleanup.projectId, cleanup.bundleId);
|
|
1062
1056
|
};
|
|
1063
1057
|
const asProjectPayload = (body) => {
|
|
1064
1058
|
if (!body || typeof body !== 'object' || Array.isArray(body)) {
|
|
@@ -1130,6 +1124,22 @@ const matchesTicketProjectContext = (ticket, req) => {
|
|
|
1130
1124
|
}
|
|
1131
1125
|
return ticket.project_id === requestedProjectId;
|
|
1132
1126
|
};
|
|
1127
|
+
const isTerminalBundleTarget = (projectId, bundleId) => {
|
|
1128
|
+
return Boolean(bundleId
|
|
1129
|
+
&& hasTerminalBundlePullRequest(getBundlePullRequestRecord(bundleId, projectId)));
|
|
1130
|
+
};
|
|
1131
|
+
const normalizeTerminalBundleGitStatus = (status, projectId, bundleId) => {
|
|
1132
|
+
if (!isTerminalBundleTarget(projectId, bundleId)) {
|
|
1133
|
+
return status;
|
|
1134
|
+
}
|
|
1135
|
+
return {
|
|
1136
|
+
...status,
|
|
1137
|
+
ahead: 0,
|
|
1138
|
+
can_push: false,
|
|
1139
|
+
can_force_push_with_lease: false,
|
|
1140
|
+
};
|
|
1141
|
+
};
|
|
1142
|
+
const TERMINAL_BUNDLE_BRANCH_ERROR = 'This bundle pull request is already merged or closed. Start follow-up work on a new branch instead.';
|
|
1133
1143
|
const getTicketRouteContext = (ticketId) => {
|
|
1134
1144
|
return getTicketRouteContextStatement.get(ticketId);
|
|
1135
1145
|
};
|
|
@@ -2282,29 +2292,27 @@ const getCurrentReviewWorkspaceSignature = async (ticket) => {
|
|
|
2282
2292
|
return (await getGitStatusAsync(workspace.cwd, workspace.branch, workspace.project)).workspace_signature ?? null;
|
|
2283
2293
|
};
|
|
2284
2294
|
const getCurrentReviewDiffState = async (ticket) => {
|
|
2285
|
-
if (!ticket) {
|
|
2295
|
+
if (!ticket?.project_id) {
|
|
2286
2296
|
return null;
|
|
2287
2297
|
}
|
|
2288
|
-
const
|
|
2289
|
-
|
|
2298
|
+
const target = { kind: 'ticket', ticket_id: ticket.id };
|
|
2299
|
+
const reviewDiff = await getWorkspaceDiffAsync({
|
|
2300
|
+
projectId: ticket.project_id,
|
|
2301
|
+
target,
|
|
2302
|
+
scope: 'review',
|
|
2303
|
+
});
|
|
2304
|
+
const uncommittedDiff = await getWorkspaceDiffAsync({
|
|
2305
|
+
projectId: ticket.project_id,
|
|
2306
|
+
target,
|
|
2307
|
+
scope: 'uncommitted',
|
|
2308
|
+
});
|
|
2309
|
+
if (!reviewDiff.available || !uncommittedDiff.available) {
|
|
2290
2310
|
return null;
|
|
2291
2311
|
}
|
|
2292
|
-
const trackedPullRequest = ticket.project_id && ticket.worktree_bundle_id
|
|
2293
|
-
? getBundlePullRequestRecord(ticket.worktree_bundle_id, ticket.project_id)
|
|
2294
|
-
: null;
|
|
2295
|
-
const [reviewDiff, uncommittedDiff] = await Promise.all([
|
|
2296
|
-
getReviewDiffDataAsync({
|
|
2297
|
-
cwd: workspace.cwd,
|
|
2298
|
-
branch: workspace.branch,
|
|
2299
|
-
project: workspace.project,
|
|
2300
|
-
pullRequestBaseBranch: trackedPullRequest?.baseBranch ?? null,
|
|
2301
|
-
}),
|
|
2302
|
-
getGitUnifiedDiffAsync(workspace.cwd),
|
|
2303
|
-
]);
|
|
2304
2312
|
return {
|
|
2305
2313
|
diffText: reviewDiff.diff,
|
|
2306
|
-
uncommittedDiffSignature: computeDiffSignature(uncommittedDiff),
|
|
2307
|
-
changeScope: (reviewDiff.diff === uncommittedDiff ? 'uncommitted' : 'branch_diff'),
|
|
2314
|
+
uncommittedDiffSignature: computeDiffSignature(uncommittedDiff.diff),
|
|
2315
|
+
changeScope: (reviewDiff.diff === uncommittedDiff.diff ? 'uncommitted' : 'branch_diff'),
|
|
2308
2316
|
};
|
|
2309
2317
|
};
|
|
2310
2318
|
const getBundleReviewFindings = async (projectId, bundleId) => {
|
|
@@ -2740,6 +2748,8 @@ const emitRunStateForContext = (ticket, isRunningValue) => {
|
|
|
2740
2748
|
};
|
|
2741
2749
|
app.use(express.json({ limit: '5mb' }));
|
|
2742
2750
|
registerRepositoryGraphRoutes(app, { emitter: io });
|
|
2751
|
+
registerWorkspaceDiffRoutes(app);
|
|
2752
|
+
registerDiffExplanationRoutes(app);
|
|
2743
2753
|
registerExecutionEnvironmentRoutes(app, {
|
|
2744
2754
|
emitter: io,
|
|
2745
2755
|
runner: dockerSandboxRunner,
|
|
@@ -3054,16 +3064,15 @@ app.get('/projects/:id/chat-sessions/:sessionId/diff', async (req, res) => {
|
|
|
3054
3064
|
if (!chatSession) {
|
|
3055
3065
|
return res.status(404).json({ error: 'Not found' });
|
|
3056
3066
|
}
|
|
3057
|
-
const
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
const diffData = await getGitDiffDataAsync(workspace.cwd);
|
|
3062
|
-
return res.json({
|
|
3063
|
-
stats: summarizeTicketDiffFiles(diffData.files),
|
|
3064
|
-
files: diffData.files,
|
|
3065
|
-
diff: diffData.diff,
|
|
3067
|
+
const diff = await getWorkspaceDiffAsync({
|
|
3068
|
+
projectId: project.id,
|
|
3069
|
+
target: { kind: 'chat_session', chat_session_id: chatSession.id },
|
|
3070
|
+
scope: 'uncommitted',
|
|
3066
3071
|
});
|
|
3072
|
+
if (!diff.available) {
|
|
3073
|
+
return res.status(diff.reason === 'target_not_found' ? 404 : 409).json({ ...diff, error: diff.message });
|
|
3074
|
+
}
|
|
3075
|
+
return res.json({ stats: diff.stats, files: diff.files, diff: diff.diff });
|
|
3067
3076
|
});
|
|
3068
3077
|
app.get('/projects/:id/chat-sessions/:sessionId/git-status', async (req, res) => {
|
|
3069
3078
|
const project = getProjectById(req.params.id);
|
|
@@ -3694,15 +3703,17 @@ app.get('/tickets/:id/diff', async (req, res) => {
|
|
|
3694
3703
|
}
|
|
3695
3704
|
const workspace = resolveTicketDiffWorkspaceFromContext(ticket);
|
|
3696
3705
|
if ('error' in workspace) {
|
|
3697
|
-
|
|
3698
|
-
return res.status(status).json({ error });
|
|
3706
|
+
return res.status(workspace.status).json({ error: workspace.error });
|
|
3699
3707
|
}
|
|
3700
|
-
const
|
|
3701
|
-
|
|
3702
|
-
|
|
3703
|
-
|
|
3704
|
-
diff: diffData.diff,
|
|
3708
|
+
const diff = await getWorkspaceDiffAsync({
|
|
3709
|
+
projectId: ticket.project_id,
|
|
3710
|
+
target: { kind: 'ticket', ticket_id: ticket.id },
|
|
3711
|
+
scope: 'uncommitted',
|
|
3705
3712
|
});
|
|
3713
|
+
if (!diff.available) {
|
|
3714
|
+
return res.status(diff.reason === 'target_not_found' ? 404 : 409).json({ ...diff, error: diff.message });
|
|
3715
|
+
}
|
|
3716
|
+
return res.json({ stats: diff.stats, files: diff.files, diff: diff.diff });
|
|
3706
3717
|
});
|
|
3707
3718
|
app.get('/tickets/:id/committed-diff', (req, res) => {
|
|
3708
3719
|
const ticket = getTicketRouteContext(req.params.id);
|
|
@@ -3731,7 +3742,8 @@ app.get('/tickets/:id/git-status', async (req, res) => {
|
|
|
3731
3742
|
return res.status(workspace.status).json({ error: workspace.error });
|
|
3732
3743
|
}
|
|
3733
3744
|
const refreshRemote = req.query.refresh_remote === 'true';
|
|
3734
|
-
|
|
3745
|
+
const status = await getGitStatusAsync(workspace.cwd, workspace.branch, workspace.project, { refreshRemote });
|
|
3746
|
+
return res.json(normalizeTerminalBundleGitStatus(status, workspace.project.id, ticket.worktree_bundle_id));
|
|
3735
3747
|
});
|
|
3736
3748
|
app.get('/tickets/:id/rebase-staged-diff', (req, res) => {
|
|
3737
3749
|
const ticket = getTicketRouteContext(req.params.id);
|
|
@@ -3833,6 +3845,9 @@ app.get('/tickets/:id/git-commits', async (req, res) => {
|
|
|
3833
3845
|
if ('error' in workspace) {
|
|
3834
3846
|
return res.status(workspace.status).json({ error: workspace.error });
|
|
3835
3847
|
}
|
|
3848
|
+
if (isTerminalBundleTarget(workspace.project.id, ticket.worktree_bundle_id)) {
|
|
3849
|
+
return res.json({ commits: [], total_count: 0 });
|
|
3850
|
+
}
|
|
3836
3851
|
const summary = await getUnpushedCommitSummaryForCwdAsync(workspace.cwd, workspace.branch, workspace.project);
|
|
3837
3852
|
return res.json({ commits: summary.commits, total_count: summary.totalCount });
|
|
3838
3853
|
});
|
|
@@ -3896,7 +3911,7 @@ app.get('/bundles/:id/review-findings', async (req, res) => {
|
|
|
3896
3911
|
}
|
|
3897
3912
|
return res.json(response);
|
|
3898
3913
|
});
|
|
3899
|
-
app.get('/bundles/:id/review-diff', (req, res) => {
|
|
3914
|
+
app.get('/bundles/:id/review-diff', async (req, res) => {
|
|
3900
3915
|
const result = getRequiredProject(req);
|
|
3901
3916
|
if ('error' in result) {
|
|
3902
3917
|
return res.status(400).json({ error: result.error });
|
|
@@ -3908,21 +3923,24 @@ app.get('/bundles/:id/review-diff', (req, res) => {
|
|
|
3908
3923
|
if (isProjectRootBundle(bundle)) {
|
|
3909
3924
|
return res.status(409).json({ error: 'Bundle diff is not available for the project root bundle.' });
|
|
3910
3925
|
}
|
|
3911
|
-
const
|
|
3912
|
-
|
|
3913
|
-
|
|
3914
|
-
|
|
3915
|
-
const trackedPullRequest = getBundlePullRequestRecord(bundle.id, result.project.id);
|
|
3916
|
-
const reviewDiff = getBundleReviewDiffForCwd({
|
|
3917
|
-
cwd: worktreePath,
|
|
3918
|
-
branch: bundle.branch,
|
|
3919
|
-
project: result.project,
|
|
3920
|
-
pullRequestBaseBranch: trackedPullRequest?.baseBranch ?? null,
|
|
3926
|
+
const reviewDiff = await getWorkspaceDiffAsync({
|
|
3927
|
+
projectId: result.project.id,
|
|
3928
|
+
target: { kind: 'bundle', bundle_id: bundle.id },
|
|
3929
|
+
scope: 'review',
|
|
3921
3930
|
});
|
|
3922
|
-
if (!reviewDiff) {
|
|
3923
|
-
return res.status(409).json({ error:
|
|
3931
|
+
if (!reviewDiff.available) {
|
|
3932
|
+
return res.status(409).json({ ...reviewDiff, error: reviewDiff.message });
|
|
3924
3933
|
}
|
|
3925
|
-
return res.json(
|
|
3934
|
+
return res.json({
|
|
3935
|
+
stats: reviewDiff.stats,
|
|
3936
|
+
files: reviewDiff.files,
|
|
3937
|
+
diff: reviewDiff.diff,
|
|
3938
|
+
diff_signature: reviewDiff.diff_signature,
|
|
3939
|
+
head_branch: reviewDiff.workspace.branch,
|
|
3940
|
+
base_branch: reviewDiff.base.branch,
|
|
3941
|
+
base_commit: reviewDiff.base.commit,
|
|
3942
|
+
source: 'worktree_base',
|
|
3943
|
+
});
|
|
3926
3944
|
});
|
|
3927
3945
|
app.get('/bundles/:id/pull-request/comments', async (req, res) => {
|
|
3928
3946
|
const result = getRequiredProject(req);
|
|
@@ -4195,6 +4213,9 @@ app.post('/bundles', async (req, res) => {
|
|
|
4195
4213
|
});
|
|
4196
4214
|
}
|
|
4197
4215
|
if (requestedBranch) {
|
|
4216
|
+
if (isBundleDeletionBranchReserved(result.project.id, branch)) {
|
|
4217
|
+
return res.status(409).json({ error: 'This branch is reserved while a deleted bundle is being cleaned up. Please try again after cleanup finishes.' });
|
|
4218
|
+
}
|
|
4198
4219
|
if (getBundleByBranch(branch, result.project.id) || hasBundleBranchCaseConflict(branch, result.project.id)) {
|
|
4199
4220
|
return res.status(409).json({ error: 'Bundle branch already exists in this project' });
|
|
4200
4221
|
}
|
|
@@ -4229,76 +4250,24 @@ app.post('/bundles', async (req, res) => {
|
|
|
4229
4250
|
}
|
|
4230
4251
|
res.status(201).json(serializeBundleRow(result.project.id, bundle, config.tool ?? 'opencode', [], result.project));
|
|
4231
4252
|
});
|
|
4232
|
-
app
|
|
4233
|
-
|
|
4234
|
-
|
|
4235
|
-
|
|
4236
|
-
|
|
4237
|
-
|
|
4238
|
-
|
|
4239
|
-
|
|
4240
|
-
|
|
4241
|
-
|
|
4242
|
-
return res.status(409).json({ error: 'Project root cannot be deleted' });
|
|
4243
|
-
}
|
|
4244
|
-
if (hasActiveBundleCheckWork(result.project.id, bundle.id)) {
|
|
4245
|
-
return res.status(409).json({ error: 'Cannot delete a bundle while check work is active' });
|
|
4246
|
-
}
|
|
4247
|
-
if (hasActiveBundleSpecWork(result.project.id, bundle.id)) {
|
|
4248
|
-
return res.status(409).json({ error: 'Cannot delete a bundle while Spec work is active' });
|
|
4249
|
-
}
|
|
4250
|
-
if (hasActiveBundleRepositoryGraphScan(result.project.id, bundle.id)) {
|
|
4251
|
-
return res.status(409).json({ error: 'Cannot delete a bundle while a repository graph scan is active' });
|
|
4252
|
-
}
|
|
4253
|
-
if (countBundleTickets(bundle.id, result.project.id) > 0) {
|
|
4254
|
-
return res.status(409).json({ error: 'Cannot delete a bundle that is still used by tickets' });
|
|
4255
|
-
}
|
|
4256
|
-
if (result.project.active_target_kind === 'bundle' && result.project.active_target_bundle_id === bundle.id) {
|
|
4257
|
-
return res.status(409).json({ error: 'Cannot delete the currently selected target bundle' });
|
|
4258
|
-
}
|
|
4259
|
-
const affectedChatSessionIds = listChatSessions(result.project.id)
|
|
4260
|
-
.filter(chatSession => chatSession.target_bundle_id === bundle.id)
|
|
4261
|
-
.map(chatSession => chatSession.id);
|
|
4262
|
-
const affectedSpecSessionIds = listSpecSessions(result.project.id)
|
|
4263
|
-
.filter(specSession => specSession.target_bundle_id === bundle.id)
|
|
4264
|
-
.map(specSession => specSession.id);
|
|
4265
|
-
let cleanupResult;
|
|
4266
|
-
try {
|
|
4267
|
-
cleanupResult = await cleanupWorktreeExecutionTarget({
|
|
4268
|
-
project: result.project,
|
|
4269
|
-
targetKind: 'bundle-worktree',
|
|
4270
|
-
targetKey: createExecutionTargetKey(result.project.id, {
|
|
4271
|
-
kind: 'bundle-worktree',
|
|
4272
|
-
bundleId: bundle.id,
|
|
4273
|
-
branch: bundle.branch,
|
|
4274
|
-
}),
|
|
4275
|
-
targetLabel: 'bundle worktree',
|
|
4276
|
-
worktreeTarget: 'bundle',
|
|
4277
|
-
branch: bundle.branch,
|
|
4278
|
-
});
|
|
4279
|
-
}
|
|
4280
|
-
catch (error) {
|
|
4281
|
-
return res.status(error instanceof HttpError ? error.status : 500).json({
|
|
4282
|
-
error: error instanceof Error ? error.message : 'Unable to delete the bundle worktree',
|
|
4283
|
-
});
|
|
4284
|
-
}
|
|
4285
|
-
deleteBundle(bundle.id, result.project.id);
|
|
4286
|
-
affectedChatSessionIds.forEach(chatSessionId => {
|
|
4287
|
-
const updatedChatSession = getProjectChatSession(result.project.id, chatSessionId);
|
|
4288
|
-
if (updatedChatSession) {
|
|
4289
|
-
io.emit('chat-session:updated', updatedChatSession);
|
|
4253
|
+
registerBundleCleanupRoutes(app);
|
|
4254
|
+
registerBundleDeletionRoutes(app, {
|
|
4255
|
+
getRequiredProject,
|
|
4256
|
+
hasActiveCheckWork: hasActiveBundleCheckWork,
|
|
4257
|
+
hasActiveSpecWork: hasActiveBundleSpecWork,
|
|
4258
|
+
hasActiveGraphScan: hasActiveBundleRepositoryGraphScan,
|
|
4259
|
+
emitChatSessionUpdate: (projectId, sessionId) => {
|
|
4260
|
+
const session = getProjectChatSession(projectId, sessionId);
|
|
4261
|
+
if (session) {
|
|
4262
|
+
io.emit('chat-session:updated', session);
|
|
4290
4263
|
}
|
|
4291
|
-
}
|
|
4292
|
-
|
|
4293
|
-
const
|
|
4294
|
-
if (
|
|
4295
|
-
io.emit('spec-session:updated',
|
|
4264
|
+
},
|
|
4265
|
+
emitSpecSessionUpdate: (projectId, sessionId) => {
|
|
4266
|
+
const session = getProjectSpecSession(projectId, sessionId);
|
|
4267
|
+
if (session) {
|
|
4268
|
+
io.emit('spec-session:updated', session);
|
|
4296
4269
|
}
|
|
4297
|
-
}
|
|
4298
|
-
if (cleanupResult.branchWarning) {
|
|
4299
|
-
return res.status(200).json({ warning: cleanupResult.branchWarning });
|
|
4300
|
-
}
|
|
4301
|
-
res.status(204).end();
|
|
4270
|
+
},
|
|
4302
4271
|
});
|
|
4303
4272
|
app.patch('/bundles/:id', async (req, res) => {
|
|
4304
4273
|
const result = getRequiredProject(req);
|
|
@@ -7964,16 +7933,18 @@ app.get('/projects/:id/diff', async (req, res) => {
|
|
|
7964
7933
|
if (!project) {
|
|
7965
7934
|
return res.status(404).json({ error: 'Not found' });
|
|
7966
7935
|
}
|
|
7967
|
-
const
|
|
7968
|
-
|
|
7969
|
-
|
|
7970
|
-
|
|
7971
|
-
|
|
7972
|
-
|
|
7973
|
-
|
|
7974
|
-
files: diffData.files,
|
|
7975
|
-
diff: diffData.diff,
|
|
7936
|
+
const target = project.active_target_kind === 'bundle' && project.active_target_bundle_id
|
|
7937
|
+
? { kind: 'bundle', bundle_id: project.active_target_bundle_id }
|
|
7938
|
+
: { kind: 'repo_root' };
|
|
7939
|
+
const diff = await getWorkspaceDiffAsync({
|
|
7940
|
+
projectId: project.id,
|
|
7941
|
+
target,
|
|
7942
|
+
scope: 'uncommitted',
|
|
7976
7943
|
});
|
|
7944
|
+
if (!diff.available) {
|
|
7945
|
+
return res.status(diff.reason === 'target_not_found' ? 404 : 409).json({ ...diff, error: diff.message });
|
|
7946
|
+
}
|
|
7947
|
+
return res.json({ stats: diff.stats, files: diff.files, diff: diff.diff });
|
|
7977
7948
|
});
|
|
7978
7949
|
app.get('/projects/:id/committed-diff', (req, res) => {
|
|
7979
7950
|
const project = getProjectById(req.params.id);
|
|
@@ -7996,7 +7967,8 @@ app.get('/projects/:id/git-status', async (req, res) => {
|
|
|
7996
7967
|
return res.status(workspace.status).json({ error: workspace.error });
|
|
7997
7968
|
}
|
|
7998
7969
|
const refreshRemote = req.query.refresh_remote === 'true';
|
|
7999
|
-
|
|
7970
|
+
const status = await getGitStatusAsync(workspace.cwd, workspace.branch, workspace.project, { refreshRemote });
|
|
7971
|
+
return res.json(normalizeTerminalBundleGitStatus(status, workspace.project.id, workspace.target_bundle_id));
|
|
8000
7972
|
});
|
|
8001
7973
|
app.get('/projects/:id/git-commits', async (req, res) => {
|
|
8002
7974
|
const project = getProjectById(req.params.id);
|
|
@@ -8007,6 +7979,9 @@ app.get('/projects/:id/git-commits', async (req, res) => {
|
|
|
8007
7979
|
if ('error' in workspace) {
|
|
8008
7980
|
return res.status(workspace.status).json({ error: workspace.error });
|
|
8009
7981
|
}
|
|
7982
|
+
if (isTerminalBundleTarget(workspace.project.id, workspace.target_bundle_id)) {
|
|
7983
|
+
return res.json({ commits: [], total_count: 0 });
|
|
7984
|
+
}
|
|
8010
7985
|
const summary = await getUnpushedCommitSummaryForCwdAsync(workspace.cwd, workspace.branch, workspace.project);
|
|
8011
7986
|
return res.json({ commits: summary.commits, total_count: summary.totalCount });
|
|
8012
7987
|
});
|
|
@@ -8037,7 +8012,7 @@ app.post('/projects/:id/git-commit', async (req, res) => {
|
|
|
8037
8012
|
}
|
|
8038
8013
|
return res.json({
|
|
8039
8014
|
ticket: null,
|
|
8040
|
-
git_status: getGitStatus(workspace.cwd, workspace.branch, workspace.project),
|
|
8015
|
+
git_status: normalizeTerminalBundleGitStatus(getGitStatus(workspace.cwd, workspace.branch, workspace.project), workspace.project.id, workspace.target_bundle_id),
|
|
8041
8016
|
});
|
|
8042
8017
|
});
|
|
8043
8018
|
app.post('/projects/:id/git-commit-message', async (req, res) => {
|
|
@@ -8207,6 +8182,9 @@ app.post('/projects/:id/git-push', async (req, res) => {
|
|
|
8207
8182
|
if ('error' in workspace) {
|
|
8208
8183
|
return res.status(workspace.status).json({ error: workspace.error });
|
|
8209
8184
|
}
|
|
8185
|
+
if (isTerminalBundleTarget(workspace.project.id, workspace.target_bundle_id)) {
|
|
8186
|
+
return res.status(409).json({ error: TERMINAL_BUNDLE_BRANCH_ERROR });
|
|
8187
|
+
}
|
|
8210
8188
|
const forceWithLease = req.body?.force_with_lease === true;
|
|
8211
8189
|
if (forceWithLease) {
|
|
8212
8190
|
try {
|
|
@@ -8248,6 +8226,9 @@ app.post('/projects/:id/git-undo-latest', async (req, res) => {
|
|
|
8248
8226
|
if ('error' in workspace) {
|
|
8249
8227
|
return res.status(workspace.status).json({ error: workspace.error });
|
|
8250
8228
|
}
|
|
8229
|
+
if (isTerminalBundleTarget(workspace.project.id, workspace.target_bundle_id)) {
|
|
8230
|
+
return res.status(409).json({ error: TERMINAL_BUNDLE_BRANCH_ERROR });
|
|
8231
|
+
}
|
|
8251
8232
|
try {
|
|
8252
8233
|
await undoLatestUnpushedCommitForCwd(workspace.cwd, workspace.branch, workspace.project);
|
|
8253
8234
|
}
|
|
@@ -8271,6 +8252,9 @@ app.post('/projects/:id/git-squash', async (req, res) => {
|
|
|
8271
8252
|
if ('error' in workspace) {
|
|
8272
8253
|
return res.status(workspace.status).json({ error: workspace.error });
|
|
8273
8254
|
}
|
|
8255
|
+
if (isTerminalBundleTarget(workspace.project.id, workspace.target_bundle_id)) {
|
|
8256
|
+
return res.status(409).json({ error: TERMINAL_BUNDLE_BRANCH_ERROR });
|
|
8257
|
+
}
|
|
8274
8258
|
const message = typeof req.body?.message === 'string' ? req.body.message.trim() : '';
|
|
8275
8259
|
if (!message) {
|
|
8276
8260
|
return res.status(400).json({ error: 'message is required' });
|
|
@@ -8489,6 +8473,9 @@ const assertProjectDeletionEligible = (projectId, claimId) => {
|
|
|
8489
8473
|
if (projectCount <= 1) {
|
|
8490
8474
|
throw new ProjectDeletionError(409, 'Cannot delete the last remaining project');
|
|
8491
8475
|
}
|
|
8476
|
+
if (db.prepare("SELECT 1 FROM bundle_deletion_jobs WHERE project_id = ? AND status IN ('pending', 'blocked') LIMIT 1").get(projectId)) {
|
|
8477
|
+
throw new ProjectDeletionError(409, 'Cannot delete a project while bundle workspace cleanup is pending');
|
|
8478
|
+
}
|
|
8492
8479
|
if (countInboundProjectLinks(projectId) > 0) {
|
|
8493
8480
|
throw new ProjectDeletionError(409, 'Cannot delete a project that is linked from another project');
|
|
8494
8481
|
}
|
|
@@ -8591,7 +8578,9 @@ const completeClaimedProjectDeletion = async (projectId, claimId) => {
|
|
|
8591
8578
|
SET parent_run_id = NULL, retry_of_run_id = NULL
|
|
8592
8579
|
WHERE project_id = ?
|
|
8593
8580
|
`).run(projectId);
|
|
8581
|
+
deleteArchivedAbandonedProjectEnvironments(projectId);
|
|
8594
8582
|
db.prepare("DELETE FROM execution_environments WHERE project_id = ? AND lifecycle_state = 'retired'").run(projectId);
|
|
8583
|
+
db.prepare("DELETE FROM bundle_deletion_jobs WHERE project_id = ? AND status = 'completed'").run(projectId);
|
|
8595
8584
|
const deleted = db.prepare('DELETE FROM projects WHERE id = ? AND deletion_claim_id = ?').run(projectId, claimId);
|
|
8596
8585
|
if (deleted.changes !== 1) {
|
|
8597
8586
|
throw new ProjectDeletionError(409, 'Project deletion ownership was lost');
|
|
@@ -8857,34 +8846,7 @@ app.patch('/tickets/:id', async (req, res) => {
|
|
|
8857
8846
|
}
|
|
8858
8847
|
}
|
|
8859
8848
|
const bundleChanged = previousBundleId !== resolvedBundleId || ticket.project_id !== nextProjectId;
|
|
8860
|
-
let
|
|
8861
|
-
if (bundleChanged && previousBundleId && previousProject) {
|
|
8862
|
-
const previousBundle = getBundle(previousBundleId, previousProject.id);
|
|
8863
|
-
if (previousBundle
|
|
8864
|
-
&& !isProjectRootBundle(previousBundle)
|
|
8865
|
-
&& countBundleTickets(previousBundle.id, previousProject.id) === 1) {
|
|
8866
|
-
try {
|
|
8867
|
-
await cleanupWorktreeExecutionTarget({
|
|
8868
|
-
project: previousProject,
|
|
8869
|
-
targetKind: 'bundle-worktree',
|
|
8870
|
-
targetKey: createExecutionTargetKey(previousProject.id, {
|
|
8871
|
-
kind: 'bundle-worktree',
|
|
8872
|
-
bundleId: previousBundle.id,
|
|
8873
|
-
branch: previousBundle.branch,
|
|
8874
|
-
}),
|
|
8875
|
-
targetLabel: 'bundle worktree',
|
|
8876
|
-
worktreeTarget: 'bundle',
|
|
8877
|
-
branch: previousBundle.branch,
|
|
8878
|
-
});
|
|
8879
|
-
previousBundleResourcesCleaned = true;
|
|
8880
|
-
}
|
|
8881
|
-
catch (error) {
|
|
8882
|
-
return res.status(error instanceof HttpError ? error.status : 500).json({
|
|
8883
|
-
error: error instanceof Error ? error.message : 'Unable to clean up the empty bundle',
|
|
8884
|
-
});
|
|
8885
|
-
}
|
|
8886
|
-
}
|
|
8887
|
-
}
|
|
8849
|
+
let bundleCleanup;
|
|
8888
8850
|
try {
|
|
8889
8851
|
db.transaction(() => {
|
|
8890
8852
|
if (bundleChanged) {
|
|
@@ -8894,28 +8856,20 @@ app.patch('/tickets/:id', async (req, res) => {
|
|
|
8894
8856
|
deleteTicketDependenciesForTicket(ticket.id);
|
|
8895
8857
|
}
|
|
8896
8858
|
db.prepare('UPDATE tickets SET title = ?, description = ?, model = ?, variant = ?, skills_json = ?, worktree_bundle_id = ?, project_id = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?').run(title?.trim() ?? ticket.title, description ?? ticket.description, resolvedModel, resolvedVariant, JSON.stringify(resolvedSkills), resolvedBundleId, nextProjectId, req.params.id);
|
|
8859
|
+
if (bundleChanged && previousBundleId && previousProject) {
|
|
8860
|
+
bundleCleanup = cleanupBundleIfEmpty(previousBundleId, previousProject);
|
|
8861
|
+
}
|
|
8897
8862
|
}).immediate();
|
|
8898
8863
|
}
|
|
8899
8864
|
catch (error) {
|
|
8900
|
-
if (error instanceof HttpError) {
|
|
8901
|
-
return res.status(error.status).json({ error: error.message });
|
|
8865
|
+
if (error instanceof HttpError || error instanceof BundleDeletionConflict) {
|
|
8866
|
+
return res.status(error instanceof HttpError ? error.status : 409).json({ error: error.message });
|
|
8902
8867
|
}
|
|
8903
8868
|
throw error;
|
|
8904
8869
|
}
|
|
8905
8870
|
upsertTicketDescriptionMessage(req.params.id, description ?? ticket.description);
|
|
8906
8871
|
const updated = getTicket(req.params.id);
|
|
8907
|
-
|
|
8908
|
-
try {
|
|
8909
|
-
await cleanupBundleIfEmpty(previousBundleId, previousProject, {
|
|
8910
|
-
resourcesAlreadyCleaned: previousBundleResourcesCleaned,
|
|
8911
|
-
});
|
|
8912
|
-
}
|
|
8913
|
-
catch (error) {
|
|
8914
|
-
return res.status(error instanceof HttpError ? error.status : 500).json({
|
|
8915
|
-
error: error instanceof Error ? error.message : 'Unable to clean up the empty bundle',
|
|
8916
|
-
});
|
|
8917
|
-
}
|
|
8918
|
-
}
|
|
8872
|
+
await finishAutomaticBundleDeletion(bundleCleanup);
|
|
8919
8873
|
io.emit('ticket:updated', updated);
|
|
8920
8874
|
res.json(updated);
|
|
8921
8875
|
});
|
|
@@ -9399,7 +9353,7 @@ app.post('/tickets/:id/git-commit', async (req, res) => {
|
|
|
9399
9353
|
const updated = reconcileRunContextTickets(previousTickets, ticket.id);
|
|
9400
9354
|
return res.json({
|
|
9401
9355
|
ticket: updated,
|
|
9402
|
-
git_status: getGitStatus(workspace.cwd, workspace.branch, workspace.project),
|
|
9356
|
+
git_status: normalizeTerminalBundleGitStatus(getGitStatus(workspace.cwd, workspace.branch, workspace.project), workspace.project.id, ticket.worktree_bundle_id),
|
|
9403
9357
|
});
|
|
9404
9358
|
});
|
|
9405
9359
|
app.post('/tickets/:id/git-commit-message', async (req, res) => {
|
|
@@ -9475,6 +9429,9 @@ app.post('/tickets/:id/git-push', async (req, res) => {
|
|
|
9475
9429
|
if ('error' in workspace) {
|
|
9476
9430
|
return res.status(workspace.status).json({ error: workspace.error });
|
|
9477
9431
|
}
|
|
9432
|
+
if (isTerminalBundleTarget(workspace.project.id, ticket.worktree_bundle_id)) {
|
|
9433
|
+
return res.status(409).json({ error: TERMINAL_BUNDLE_BRANCH_ERROR });
|
|
9434
|
+
}
|
|
9478
9435
|
const previousTickets = listTicketsInRunContext(ticket);
|
|
9479
9436
|
const forceWithLease = req.body?.force_with_lease === true;
|
|
9480
9437
|
if (forceWithLease) {
|
|
@@ -9525,6 +9482,9 @@ app.post('/tickets/:id/git-undo-latest', async (req, res) => {
|
|
|
9525
9482
|
if ('error' in workspace) {
|
|
9526
9483
|
return res.status(workspace.status).json({ error: workspace.error });
|
|
9527
9484
|
}
|
|
9485
|
+
if (isTerminalBundleTarget(workspace.project.id, ticket.worktree_bundle_id)) {
|
|
9486
|
+
return res.status(409).json({ error: TERMINAL_BUNDLE_BRANCH_ERROR });
|
|
9487
|
+
}
|
|
9528
9488
|
const previousTickets = listTicketsInRunContext(ticket);
|
|
9529
9489
|
try {
|
|
9530
9490
|
await undoLatestUnpushedCommitForCwd(workspace.cwd, workspace.branch, workspace.project);
|
|
@@ -9553,6 +9513,9 @@ app.post('/tickets/:id/git-squash', async (req, res) => {
|
|
|
9553
9513
|
if ('error' in workspace) {
|
|
9554
9514
|
return res.status(workspace.status).json({ error: workspace.error });
|
|
9555
9515
|
}
|
|
9516
|
+
if (isTerminalBundleTarget(workspace.project.id, ticket.worktree_bundle_id)) {
|
|
9517
|
+
return res.status(409).json({ error: TERMINAL_BUNDLE_BRANCH_ERROR });
|
|
9518
|
+
}
|
|
9556
9519
|
const message = typeof req.body?.message === 'string' ? req.body.message.trim() : '';
|
|
9557
9520
|
if (!message) {
|
|
9558
9521
|
return res.status(400).json({ error: 'message is required' });
|
|
@@ -10575,6 +10538,7 @@ app.use((error, _req, res, next) => {
|
|
|
10575
10538
|
io.on('connection', (socket) => {
|
|
10576
10539
|
console.log('client connected:', socket.id);
|
|
10577
10540
|
registerTerminalSocketHandlers(io, socket);
|
|
10541
|
+
registerDiffExplanationSocketHandlers(socket);
|
|
10578
10542
|
});
|
|
10579
10543
|
const DEFAULT_PORT = 3001;
|
|
10580
10544
|
const APPROVAL_RECONCILIATION_INTERVAL_MS = 30_000;
|
|
@@ -10629,6 +10593,7 @@ export const runExecutionArtifactCleanup = (mode) => {
|
|
|
10629
10593
|
if (mode === 'periodic') {
|
|
10630
10594
|
await resumeInterruptedProjectDeletions();
|
|
10631
10595
|
}
|
|
10596
|
+
await resumeBundleDeletions();
|
|
10632
10597
|
const orphanedEnvironments = await reconcileOrphanedExecutionEnvironments();
|
|
10633
10598
|
if (orphanedEnvironments.scanned > 0) {
|
|
10634
10599
|
console.log(`Reconciled ${orphanedEnvironments.cleaned} orphaned execution environment(s); ${orphanedEnvironments.failed} cleanup(s) retained for retry`);
|
|
@@ -10707,6 +10672,7 @@ const runExecutionEnvironmentLifecycleReconciliation = () => {
|
|
|
10707
10672
|
isProjectOwnedInMemory: projectId => getActiveRunStatusForProject(projectId) !== null,
|
|
10708
10673
|
})
|
|
10709
10674
|
.then(() => reconcileExecutionEnvironmentLifecycleOperations())
|
|
10675
|
+
.then(() => resumeBundleDeletions())
|
|
10710
10676
|
.then(() => undefined)
|
|
10711
10677
|
.catch(error => {
|
|
10712
10678
|
console.error('Failed to reconcile execution environment lifecycle operations', error);
|
|
@@ -10746,6 +10712,8 @@ export const startServer = ({ port = DEFAULT_PORT, host } = {}) => {
|
|
|
10746
10712
|
return Promise.reject(new Error('Server startup was cancelled by shutdown'));
|
|
10747
10713
|
}
|
|
10748
10714
|
if (httpServer.listening) {
|
|
10715
|
+
openLightweightPromptAdmissions();
|
|
10716
|
+
openDiffExplanationJobAdmissions();
|
|
10749
10717
|
return Promise.resolve(resolveStartResult(host));
|
|
10750
10718
|
}
|
|
10751
10719
|
if (startServerPromise) {
|
|
@@ -10781,11 +10749,15 @@ export const startServer = ({ port = DEFAULT_PORT, host } = {}) => {
|
|
|
10781
10749
|
};
|
|
10782
10750
|
httpServer.once('error', handleError);
|
|
10783
10751
|
const { bindHost } = resolveDesktopServerHost(host);
|
|
10752
|
+
openLightweightPromptAdmissions();
|
|
10753
|
+
openDiffExplanationJobAdmissions();
|
|
10784
10754
|
openCheckRunnerAdmissions();
|
|
10785
10755
|
httpServer.listen(port, bindHost, handleListening);
|
|
10786
10756
|
});
|
|
10787
10757
|
}
|
|
10788
10758
|
catch (error) {
|
|
10759
|
+
await shutdownDiffExplanationJobs();
|
|
10760
|
+
await shutdownLightweightPrompts();
|
|
10789
10761
|
await clearAllGitHubActionsSessions();
|
|
10790
10762
|
throw error;
|
|
10791
10763
|
}
|
|
@@ -10812,9 +10784,11 @@ export const shutdownServer = async (signal) => {
|
|
|
10812
10784
|
void httpServerClosePromise?.catch(() => undefined);
|
|
10813
10785
|
const pendingStartup = startServerPromise;
|
|
10814
10786
|
shutdownServerPromise = (async () => {
|
|
10787
|
+
await shutdownDiffExplanationJobs();
|
|
10815
10788
|
await Promise.all([
|
|
10816
10789
|
pendingStartup?.catch(() => undefined),
|
|
10817
10790
|
stopAllPlanAgents(),
|
|
10791
|
+
shutdownLightweightPrompts(),
|
|
10818
10792
|
]);
|
|
10819
10793
|
if (executionEvidenceCleanupTimer) {
|
|
10820
10794
|
clearInterval(executionEvidenceCleanupTimer);
|