@straiffi/archon 1.11.9 → 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-CgC2rnNq.js → ActivityView-RnE0983a.js} +1 -1
- package/dist/client/assets/{GitHubActionsDialog-XXGwm-bw.js → GitHubActionsDialog-PaNJhVzv.js} +1 -1
- package/dist/client/assets/{VerificationDialog-C8cDKxSG.js → VerificationDialog-PGrCJ1DJ.js} +1 -1
- package/dist/client/assets/{architectureDiagram-3BPJPVTR-BhdIU_o9.js → architectureDiagram-3BPJPVTR-CKd1Dmpz.js} +1 -1
- package/dist/client/assets/{blockDiagram-GPEHLZMM-CfKNdNso.js → blockDiagram-GPEHLZMM-M3OjRhA0.js} +1 -1
- package/dist/client/assets/{c4Diagram-AAUBKEIU-BYOg4iNZ.js → c4Diagram-AAUBKEIU-VWBcGTXO.js} +1 -1
- package/dist/client/assets/channel-CBQk1IQf.js +1 -0
- package/dist/client/assets/{chunk-2J33WTMH-JmBb9p2l.js → chunk-2J33WTMH-C4WpCUzU.js} +1 -1
- package/dist/client/assets/{chunk-3OPIFGDE-CuCn8uDI.js → chunk-3OPIFGDE-BQ-j87T6.js} +1 -1
- package/dist/client/assets/{chunk-5ZQYHXKU-DaGbZz7d.js → chunk-5ZQYHXKU-BCZvyT9u.js} +1 -1
- package/dist/client/assets/{chunk-727SXJPM-D7UwchWL.js → chunk-727SXJPM-DZP_PMNz.js} +1 -1
- package/dist/client/assets/{chunk-AQP2D5EJ-CxYpis5S.js → chunk-AQP2D5EJ-Cejit8wh.js} +1 -1
- package/dist/client/assets/{chunk-CSCIHK7Q-DF4Jo5lo.js → chunk-CSCIHK7Q-CvXC4rkf.js} +1 -1
- package/dist/client/assets/{chunk-KSCS5N6A-Bul_8QaO.js → chunk-KSCS5N6A-hTgJWrr3.js} +1 -1
- package/dist/client/assets/{chunk-L5ZTLDWV-BQvBZt_1.js → chunk-L5ZTLDWV-bSK1mut3.js} +1 -1
- package/dist/client/assets/{chunk-LZXEDZCA-BPsejGww.js → chunk-LZXEDZCA-Y2hnSpRr.js} +2 -2
- package/dist/client/assets/{chunk-ND2GUHAM-D8AX2HAv.js → chunk-ND2GUHAM-BiHH5lS5.js} +1 -1
- package/dist/client/assets/{chunk-NZK2D7GU-CE21a8CK.js → chunk-NZK2D7GU-DdxcwTTc.js} +1 -1
- package/dist/client/assets/{chunk-O5CBEL6O-vZQ8YCvm.js → chunk-O5CBEL6O-BZIbgKJr.js} +1 -1
- package/dist/client/assets/{chunk-WU5MYG2G-BpBNiw0j.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-BqEPuJwq.js → dagre-BM42HDAG-CZQZPoaH.js} +1 -1
- package/dist/client/assets/{diagram-2AECGRRQ-BWLSNrm2.js → diagram-2AECGRRQ-DU7zsX_b.js} +1 -1
- package/dist/client/assets/{diagram-5GNKFQAL-CUNUlEy4.js → diagram-5GNKFQAL-BCCoHKlp.js} +1 -1
- package/dist/client/assets/{diagram-KO2AKTUF-CgZcDZ0J.js → diagram-KO2AKTUF-BR_GNJLN.js} +1 -1
- package/dist/client/assets/{diagram-LMA3HP47-DTfMha7_.js → diagram-LMA3HP47-B6LZOUTc.js} +1 -1
- package/dist/client/assets/{diagram-OG6HWLK6-ClO5evmB.js → diagram-OG6HWLK6-w89pjkBe.js} +1 -1
- package/dist/client/assets/{erDiagram-TEJ5UH35-BRW1HE33.js → erDiagram-TEJ5UH35-BReR8ngL.js} +1 -1
- package/dist/client/assets/{flowDiagram-I6XJVG4X-tC2xALm9.js → flowDiagram-I6XJVG4X-CGwpJ9iQ.js} +1 -1
- package/dist/client/assets/{ganttDiagram-6RSMTGT7-D7Bu6hss.js → ganttDiagram-6RSMTGT7-BcOPz4K9.js} +1 -1
- package/dist/client/assets/{gitGraphDiagram-PVQCEYII-Cx1uYcGX.js → gitGraphDiagram-PVQCEYII-m6-x38H5.js} +1 -1
- package/dist/client/assets/{index-CArcyEjQ.js → index-yUckGfMu.js} +119 -113
- package/dist/client/assets/{infoDiagram-5YYISTIA-bKT-ZR4K.js → infoDiagram-5YYISTIA-11iWCl_6.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-YF4QCWOH-CrUBKkGR.js → ishikawaDiagram-YF4QCWOH-DbeJ14eh.js} +1 -1
- package/dist/client/assets/{journeyDiagram-JHISSGLW-CCmXkk8M.js → journeyDiagram-JHISSGLW-BYFdU9SL.js} +1 -1
- package/dist/client/assets/{kanban-definition-UN3LZRKU-BmcV7QM-.js → kanban-definition-UN3LZRKU-Dyc52bol.js} +1 -1
- package/dist/client/assets/{line-C5oH-IfR.js → line-CBauYkto.js} +1 -1
- package/dist/client/assets/{mermaid-parser.core-BFyFUctK.js → mermaid-parser.core-BBbL8arn.js} +1 -1
- package/dist/client/assets/{mermaid.core-DXkHU1mX.js → mermaid.core-CB2mIiiP.js} +3 -3
- package/dist/client/assets/{mindmap-definition-RKZ34NQL-DhErnbAy.js → mindmap-definition-RKZ34NQL-CvWYGfWn.js} +1 -1
- package/dist/client/assets/{pieDiagram-4H26LBE5-B8pkGeer.js → pieDiagram-4H26LBE5-BsbIF0rU.js} +1 -1
- package/dist/client/assets/{quadrantDiagram-W4KKPZXB-BkYWz3t_.js → quadrantDiagram-W4KKPZXB-Bvs09UnJ.js} +1 -1
- package/dist/client/assets/{requirementDiagram-4Y6WPE33-DB6jC89I.js → requirementDiagram-4Y6WPE33-B3U5xSYZ.js} +1 -1
- package/dist/client/assets/{sankeyDiagram-5OEKKPKP-BeAslLcb.js → sankeyDiagram-5OEKKPKP-Dds8cy2g.js} +1 -1
- package/dist/client/assets/{sequenceDiagram-3UESZ5HK-BgXC1aKi.js → sequenceDiagram-3UESZ5HK-BoMb08Ay.js} +1 -1
- package/dist/client/assets/{stateDiagram-AJRCARHV-Dj3Qlk0A.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-C8ncLV_K.js → timeline-definition-PNZ67QCA-C7U--kCB.js} +1 -1
- package/dist/client/assets/{vennDiagram-CIIHVFJN-jgBSpqC1.js → vennDiagram-CIIHVFJN-BoPWwPSI.js} +1 -1
- package/dist/client/assets/{wardleyDiagram-YWT4CUSO-DfGD5Kys.js → wardleyDiagram-YWT4CUSO-CJ6Y7g3b.js} +1 -1
- package/dist/client/assets/{xychartDiagram-2RQKCTM6-Dyk41E2n.js → xychartDiagram-2RQKCTM6-rN5shXue.js} +1 -1
- package/dist/client/index.html +1 -1
- package/dist/server/db.js +2 -0
- package/dist/server/db.js.map +1 -1
- package/dist/server/index.js +88 -175
- package/dist/server/index.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/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 +8 -55
- package/dist/server/lib/dockerSandboxExecutionEnvironment.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/i18n/resources.js +10 -0
- package/dist/server/lib/i18n/resources.js.map +1 -1
- package/package.json +1 -1
- package/dist/client/assets/channel-4wFtrIng.js +0 -1
- package/dist/client/assets/classDiagram-4FO5ZUOK-DNAwGSF5.js +0 -1
- package/dist/client/assets/classDiagram-v2-Q7XG4LA2-cskOKMdS.js +0 -1
- package/dist/client/assets/stateDiagram-v2-BHNVJYJU-I1Ry2lsa.js +0 -1
package/dist/server/index.js
CHANGED
|
@@ -8,6 +8,10 @@ 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';
|
|
13
17
|
import { registerWorkspaceDiffRoutes } from './lib/workspaceDiffRoutes.js';
|
|
@@ -105,6 +109,7 @@ import { buildImportedIssueSessionTitle, importProviderHostedIssueImages } from
|
|
|
105
109
|
import { dispatchGitHubWorkflow, getGitHubWorkflowDispatchConfig, getGitHubWorkflowRun, getGitHubWorkflowRunJobs, listGitHubWorkflows, listGitHubWorkflowRuns, } from './lib/integrations/githubActions.js';
|
|
106
110
|
import { clearAllGitHubActionsSessions, getActiveTrackedRunCount, getTrackedGitHubActionsRuns, startGitHubActionsPolling, trackGitHubActionsRun, } from './lib/integrations/githubActionsSessions.js';
|
|
107
111
|
import { reconcileStartupExecutions } from './lib/executionReconciliation.js';
|
|
112
|
+
import { deleteArchivedAbandonedProjectEnvironments } from './lib/executionEnvironmentTargetCleanup.js';
|
|
108
113
|
import { cleanupProjectExecutionEnvironments, cleanupExecutionEnvironmentsForTarget, ExecutionEnvironmentCleanupError, reconcileOrphanedExecutionEnvironments, reconcileExecutionEnvironments, reconcileExecutionEnvironmentLifecycleOperations, reconcileProductionExecutionEnvironmentBindings, } from './lib/executionEnvironmentReconciliation.js';
|
|
109
114
|
import { createExecutionTargetKey, resolveExecutionTarget, resolveProjectRootExecutionTarget } from './lib/executionTargets.js';
|
|
110
115
|
import { getExecutionEnvironmentContextForRun } from './lib/executionEnvironmentContexts.js';
|
|
@@ -657,52 +662,28 @@ const deleteTicketRecord = async (ticket) => {
|
|
|
657
662
|
}
|
|
658
663
|
const bundledProject = isBundledTicket(ticket) ? getEffectiveProject(ticket) : null;
|
|
659
664
|
const deletedBundleId = ticket.worktree_bundle_id;
|
|
660
|
-
|
|
661
|
-
const bundleResourcesWereCleaned = Boolean(bundledProject
|
|
662
|
-
&& deletedBundle
|
|
663
|
-
&& !isProjectRootBundle(deletedBundle)
|
|
664
|
-
&& countBundleTickets(deletedBundle.id, bundledProject.id) === 1);
|
|
665
|
-
if (bundleResourcesWereCleaned && bundledProject && deletedBundle) {
|
|
666
|
-
try {
|
|
667
|
-
await cleanupWorktreeExecutionTarget({
|
|
668
|
-
project: bundledProject,
|
|
669
|
-
targetKind: 'bundle-worktree',
|
|
670
|
-
targetKey: createExecutionTargetKey(bundledProject.id, {
|
|
671
|
-
kind: 'bundle-worktree',
|
|
672
|
-
bundleId: deletedBundle.id,
|
|
673
|
-
branch: deletedBundle.branch,
|
|
674
|
-
}),
|
|
675
|
-
targetLabel: 'bundle worktree',
|
|
676
|
-
worktreeTarget: 'bundle',
|
|
677
|
-
branch: deletedBundle.branch,
|
|
678
|
-
});
|
|
679
|
-
}
|
|
680
|
-
catch (error) {
|
|
681
|
-
return {
|
|
682
|
-
ok: false,
|
|
683
|
-
status: error instanceof HttpError ? error.status : 500,
|
|
684
|
-
error: error instanceof Error ? error.message : 'Unable to delete the bundle worktree',
|
|
685
|
-
};
|
|
686
|
-
}
|
|
687
|
-
}
|
|
665
|
+
let bundleCleanup;
|
|
688
666
|
let nextBuildTicketId = null;
|
|
689
667
|
let nextBuildSequenceId = null;
|
|
690
668
|
let affectedTicketIds = [];
|
|
691
|
-
if (ticket.state === 'build') {
|
|
692
|
-
const sequenceRemoval = removeTicketFromBuildSequence(ticket.id);
|
|
693
|
-
if (sequenceRemoval.ok) {
|
|
694
|
-
nextBuildTicketId = sequenceRemoval.nextTicketId;
|
|
695
|
-
nextBuildSequenceId = sequenceRemoval.sequenceId;
|
|
696
|
-
affectedTicketIds = sequenceRemoval.affectedTicketIds;
|
|
697
|
-
}
|
|
698
|
-
}
|
|
699
669
|
try {
|
|
700
670
|
withExecutionRunTransaction(transaction => {
|
|
701
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
|
+
}
|
|
702
680
|
if (ticket.project_id) {
|
|
703
681
|
deleteTerminalReviewRepairIterationsForTicketInTransaction(transaction, ticket.project_id, ticket.id);
|
|
704
682
|
}
|
|
705
683
|
db.prepare('DELETE FROM tickets WHERE id = ?').run(ticket.id);
|
|
684
|
+
if (bundledProject && deletedBundleId) {
|
|
685
|
+
bundleCleanup = cleanupBundleIfEmpty(deletedBundleId, bundledProject);
|
|
686
|
+
}
|
|
706
687
|
}, io, 'immediate');
|
|
707
688
|
}
|
|
708
689
|
catch (error) {
|
|
@@ -718,18 +699,7 @@ const deleteTicketRecord = async (ticket) => {
|
|
|
718
699
|
catch (error) {
|
|
719
700
|
console.warn(`[ticket-delete] Unable to remove stage snapshots for ticket ${ticket.id}`, error);
|
|
720
701
|
}
|
|
721
|
-
|
|
722
|
-
try {
|
|
723
|
-
await cleanupBundleIfEmpty(deletedBundleId, bundledProject, { resourcesAlreadyCleaned: bundleResourcesWereCleaned });
|
|
724
|
-
}
|
|
725
|
-
catch (error) {
|
|
726
|
-
return {
|
|
727
|
-
ok: false,
|
|
728
|
-
status: error instanceof HttpError ? error.status : 500,
|
|
729
|
-
error: error instanceof Error ? error.message : 'Unable to clean up the empty bundle',
|
|
730
|
-
};
|
|
731
|
-
}
|
|
732
|
-
}
|
|
702
|
+
await finishAutomaticBundleDeletion(bundleCleanup);
|
|
733
703
|
io.emit('ticket:deleted', { id: ticket.id });
|
|
734
704
|
return { ok: true, nextBuildTicketId, nextBuildSequenceId, affectedTicketIds };
|
|
735
705
|
};
|
|
@@ -980,6 +950,9 @@ const uniqueBundleBranchName = (name, projectId, options = {}) => {
|
|
|
980
950
|
let branch = base;
|
|
981
951
|
let suffix = 2;
|
|
982
952
|
const conflictsWithBranch = (candidate) => {
|
|
953
|
+
if (isBundleDeletionBranchReserved(projectId, candidate)) {
|
|
954
|
+
return true;
|
|
955
|
+
}
|
|
983
956
|
const bundleConflict = getBundleByBranch(candidate, projectId);
|
|
984
957
|
if (bundleConflict && bundleConflict.id !== options.currentBundle?.id) {
|
|
985
958
|
return true;
|
|
@@ -1034,7 +1007,7 @@ const createAutoBundleForName = (name, projectId, baseBranch = null) => {
|
|
|
1034
1007
|
}
|
|
1035
1008
|
return bundle;
|
|
1036
1009
|
};
|
|
1037
|
-
const cleanupBundleIfEmpty =
|
|
1010
|
+
const cleanupBundleIfEmpty = (bundleId, project) => {
|
|
1038
1011
|
if (!bundleId) {
|
|
1039
1012
|
return;
|
|
1040
1013
|
}
|
|
@@ -1045,24 +1018,41 @@ const cleanupBundleIfEmpty = async (bundleId, project, options = {}) => {
|
|
|
1045
1018
|
if (isProjectRootBundle(bundle)) {
|
|
1046
1019
|
return;
|
|
1047
1020
|
}
|
|
1048
|
-
|
|
1049
|
-
|
|
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;
|
|
1050
1042
|
}
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
branch: bundle.branch,
|
|
1063
|
-
});
|
|
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
|
+
}
|
|
1048
|
+
}
|
|
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
|
+
}
|
|
1064
1054
|
}
|
|
1065
|
-
|
|
1055
|
+
await attemptBundleDeletion(cleanup.projectId, cleanup.bundleId);
|
|
1066
1056
|
};
|
|
1067
1057
|
const asProjectPayload = (body) => {
|
|
1068
1058
|
if (!body || typeof body !== 'object' || Array.isArray(body)) {
|
|
@@ -4223,6 +4213,9 @@ app.post('/bundles', async (req, res) => {
|
|
|
4223
4213
|
});
|
|
4224
4214
|
}
|
|
4225
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
|
+
}
|
|
4226
4219
|
if (getBundleByBranch(branch, result.project.id) || hasBundleBranchCaseConflict(branch, result.project.id)) {
|
|
4227
4220
|
return res.status(409).json({ error: 'Bundle branch already exists in this project' });
|
|
4228
4221
|
}
|
|
@@ -4257,76 +4250,24 @@ app.post('/bundles', async (req, res) => {
|
|
|
4257
4250
|
}
|
|
4258
4251
|
res.status(201).json(serializeBundleRow(result.project.id, bundle, config.tool ?? 'opencode', [], result.project));
|
|
4259
4252
|
});
|
|
4260
|
-
app
|
|
4261
|
-
|
|
4262
|
-
|
|
4263
|
-
|
|
4264
|
-
|
|
4265
|
-
|
|
4266
|
-
|
|
4267
|
-
|
|
4268
|
-
|
|
4269
|
-
|
|
4270
|
-
return res.status(409).json({ error: 'Project root cannot be deleted' });
|
|
4271
|
-
}
|
|
4272
|
-
if (hasActiveBundleCheckWork(result.project.id, bundle.id)) {
|
|
4273
|
-
return res.status(409).json({ error: 'Cannot delete a bundle while check work is active' });
|
|
4274
|
-
}
|
|
4275
|
-
if (hasActiveBundleSpecWork(result.project.id, bundle.id)) {
|
|
4276
|
-
return res.status(409).json({ error: 'Cannot delete a bundle while Spec work is active' });
|
|
4277
|
-
}
|
|
4278
|
-
if (hasActiveBundleRepositoryGraphScan(result.project.id, bundle.id)) {
|
|
4279
|
-
return res.status(409).json({ error: 'Cannot delete a bundle while a repository graph scan is active' });
|
|
4280
|
-
}
|
|
4281
|
-
if (countBundleTickets(bundle.id, result.project.id) > 0) {
|
|
4282
|
-
return res.status(409).json({ error: 'Cannot delete a bundle that is still used by tickets' });
|
|
4283
|
-
}
|
|
4284
|
-
if (result.project.active_target_kind === 'bundle' && result.project.active_target_bundle_id === bundle.id) {
|
|
4285
|
-
return res.status(409).json({ error: 'Cannot delete the currently selected target bundle' });
|
|
4286
|
-
}
|
|
4287
|
-
const affectedChatSessionIds = listChatSessions(result.project.id)
|
|
4288
|
-
.filter(chatSession => chatSession.target_bundle_id === bundle.id)
|
|
4289
|
-
.map(chatSession => chatSession.id);
|
|
4290
|
-
const affectedSpecSessionIds = listSpecSessions(result.project.id)
|
|
4291
|
-
.filter(specSession => specSession.target_bundle_id === bundle.id)
|
|
4292
|
-
.map(specSession => specSession.id);
|
|
4293
|
-
let cleanupResult;
|
|
4294
|
-
try {
|
|
4295
|
-
cleanupResult = await cleanupWorktreeExecutionTarget({
|
|
4296
|
-
project: result.project,
|
|
4297
|
-
targetKind: 'bundle-worktree',
|
|
4298
|
-
targetKey: createExecutionTargetKey(result.project.id, {
|
|
4299
|
-
kind: 'bundle-worktree',
|
|
4300
|
-
bundleId: bundle.id,
|
|
4301
|
-
branch: bundle.branch,
|
|
4302
|
-
}),
|
|
4303
|
-
targetLabel: 'bundle worktree',
|
|
4304
|
-
worktreeTarget: 'bundle',
|
|
4305
|
-
branch: bundle.branch,
|
|
4306
|
-
});
|
|
4307
|
-
}
|
|
4308
|
-
catch (error) {
|
|
4309
|
-
return res.status(error instanceof HttpError ? error.status : 500).json({
|
|
4310
|
-
error: error instanceof Error ? error.message : 'Unable to delete the bundle worktree',
|
|
4311
|
-
});
|
|
4312
|
-
}
|
|
4313
|
-
deleteBundle(bundle.id, result.project.id);
|
|
4314
|
-
affectedChatSessionIds.forEach(chatSessionId => {
|
|
4315
|
-
const updatedChatSession = getProjectChatSession(result.project.id, chatSessionId);
|
|
4316
|
-
if (updatedChatSession) {
|
|
4317
|
-
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);
|
|
4318
4263
|
}
|
|
4319
|
-
}
|
|
4320
|
-
|
|
4321
|
-
const
|
|
4322
|
-
if (
|
|
4323
|
-
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);
|
|
4324
4269
|
}
|
|
4325
|
-
}
|
|
4326
|
-
if (cleanupResult.branchWarning) {
|
|
4327
|
-
return res.status(200).json({ warning: cleanupResult.branchWarning });
|
|
4328
|
-
}
|
|
4329
|
-
res.status(204).end();
|
|
4270
|
+
},
|
|
4330
4271
|
});
|
|
4331
4272
|
app.patch('/bundles/:id', async (req, res) => {
|
|
4332
4273
|
const result = getRequiredProject(req);
|
|
@@ -8532,6 +8473,9 @@ const assertProjectDeletionEligible = (projectId, claimId) => {
|
|
|
8532
8473
|
if (projectCount <= 1) {
|
|
8533
8474
|
throw new ProjectDeletionError(409, 'Cannot delete the last remaining project');
|
|
8534
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
|
+
}
|
|
8535
8479
|
if (countInboundProjectLinks(projectId) > 0) {
|
|
8536
8480
|
throw new ProjectDeletionError(409, 'Cannot delete a project that is linked from another project');
|
|
8537
8481
|
}
|
|
@@ -8634,7 +8578,9 @@ const completeClaimedProjectDeletion = async (projectId, claimId) => {
|
|
|
8634
8578
|
SET parent_run_id = NULL, retry_of_run_id = NULL
|
|
8635
8579
|
WHERE project_id = ?
|
|
8636
8580
|
`).run(projectId);
|
|
8581
|
+
deleteArchivedAbandonedProjectEnvironments(projectId);
|
|
8637
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);
|
|
8638
8584
|
const deleted = db.prepare('DELETE FROM projects WHERE id = ? AND deletion_claim_id = ?').run(projectId, claimId);
|
|
8639
8585
|
if (deleted.changes !== 1) {
|
|
8640
8586
|
throw new ProjectDeletionError(409, 'Project deletion ownership was lost');
|
|
@@ -8900,34 +8846,7 @@ app.patch('/tickets/:id', async (req, res) => {
|
|
|
8900
8846
|
}
|
|
8901
8847
|
}
|
|
8902
8848
|
const bundleChanged = previousBundleId !== resolvedBundleId || ticket.project_id !== nextProjectId;
|
|
8903
|
-
let
|
|
8904
|
-
if (bundleChanged && previousBundleId && previousProject) {
|
|
8905
|
-
const previousBundle = getBundle(previousBundleId, previousProject.id);
|
|
8906
|
-
if (previousBundle
|
|
8907
|
-
&& !isProjectRootBundle(previousBundle)
|
|
8908
|
-
&& countBundleTickets(previousBundle.id, previousProject.id) === 1) {
|
|
8909
|
-
try {
|
|
8910
|
-
await cleanupWorktreeExecutionTarget({
|
|
8911
|
-
project: previousProject,
|
|
8912
|
-
targetKind: 'bundle-worktree',
|
|
8913
|
-
targetKey: createExecutionTargetKey(previousProject.id, {
|
|
8914
|
-
kind: 'bundle-worktree',
|
|
8915
|
-
bundleId: previousBundle.id,
|
|
8916
|
-
branch: previousBundle.branch,
|
|
8917
|
-
}),
|
|
8918
|
-
targetLabel: 'bundle worktree',
|
|
8919
|
-
worktreeTarget: 'bundle',
|
|
8920
|
-
branch: previousBundle.branch,
|
|
8921
|
-
});
|
|
8922
|
-
previousBundleResourcesCleaned = true;
|
|
8923
|
-
}
|
|
8924
|
-
catch (error) {
|
|
8925
|
-
return res.status(error instanceof HttpError ? error.status : 500).json({
|
|
8926
|
-
error: error instanceof Error ? error.message : 'Unable to clean up the empty bundle',
|
|
8927
|
-
});
|
|
8928
|
-
}
|
|
8929
|
-
}
|
|
8930
|
-
}
|
|
8849
|
+
let bundleCleanup;
|
|
8931
8850
|
try {
|
|
8932
8851
|
db.transaction(() => {
|
|
8933
8852
|
if (bundleChanged) {
|
|
@@ -8937,28 +8856,20 @@ app.patch('/tickets/:id', async (req, res) => {
|
|
|
8937
8856
|
deleteTicketDependenciesForTicket(ticket.id);
|
|
8938
8857
|
}
|
|
8939
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
|
+
}
|
|
8940
8862
|
}).immediate();
|
|
8941
8863
|
}
|
|
8942
8864
|
catch (error) {
|
|
8943
|
-
if (error instanceof HttpError) {
|
|
8944
|
-
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 });
|
|
8945
8867
|
}
|
|
8946
8868
|
throw error;
|
|
8947
8869
|
}
|
|
8948
8870
|
upsertTicketDescriptionMessage(req.params.id, description ?? ticket.description);
|
|
8949
8871
|
const updated = getTicket(req.params.id);
|
|
8950
|
-
|
|
8951
|
-
try {
|
|
8952
|
-
await cleanupBundleIfEmpty(previousBundleId, previousProject, {
|
|
8953
|
-
resourcesAlreadyCleaned: previousBundleResourcesCleaned,
|
|
8954
|
-
});
|
|
8955
|
-
}
|
|
8956
|
-
catch (error) {
|
|
8957
|
-
return res.status(error instanceof HttpError ? error.status : 500).json({
|
|
8958
|
-
error: error instanceof Error ? error.message : 'Unable to clean up the empty bundle',
|
|
8959
|
-
});
|
|
8960
|
-
}
|
|
8961
|
-
}
|
|
8872
|
+
await finishAutomaticBundleDeletion(bundleCleanup);
|
|
8962
8873
|
io.emit('ticket:updated', updated);
|
|
8963
8874
|
res.json(updated);
|
|
8964
8875
|
});
|
|
@@ -10682,6 +10593,7 @@ export const runExecutionArtifactCleanup = (mode) => {
|
|
|
10682
10593
|
if (mode === 'periodic') {
|
|
10683
10594
|
await resumeInterruptedProjectDeletions();
|
|
10684
10595
|
}
|
|
10596
|
+
await resumeBundleDeletions();
|
|
10685
10597
|
const orphanedEnvironments = await reconcileOrphanedExecutionEnvironments();
|
|
10686
10598
|
if (orphanedEnvironments.scanned > 0) {
|
|
10687
10599
|
console.log(`Reconciled ${orphanedEnvironments.cleaned} orphaned execution environment(s); ${orphanedEnvironments.failed} cleanup(s) retained for retry`);
|
|
@@ -10760,6 +10672,7 @@ const runExecutionEnvironmentLifecycleReconciliation = () => {
|
|
|
10760
10672
|
isProjectOwnedInMemory: projectId => getActiveRunStatusForProject(projectId) !== null,
|
|
10761
10673
|
})
|
|
10762
10674
|
.then(() => reconcileExecutionEnvironmentLifecycleOperations())
|
|
10675
|
+
.then(() => resumeBundleDeletions())
|
|
10763
10676
|
.then(() => undefined)
|
|
10764
10677
|
.catch(error => {
|
|
10765
10678
|
console.error('Failed to reconcile execution environment lifecycle operations', error);
|