@straiffi/archon 1.7.3 → 1.7.4
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-BgMz3NNT.js +1 -0
- package/dist/client/assets/GitHubActionsDialog-CNqWDS3E.js +1 -0
- package/dist/client/assets/TestsDialog-Cll7xJe0.js +4 -0
- package/dist/client/assets/{architectureDiagram-3BPJPVTR-BzzGotac.js → architectureDiagram-3BPJPVTR-BL5dYiul.js} +1 -1
- package/dist/client/assets/{blockDiagram-GPEHLZMM-DIcqLHKg.js → blockDiagram-GPEHLZMM-Bi5x2v80.js} +1 -1
- package/dist/client/assets/{c4Diagram-AAUBKEIU-Hc1Sqmhs.js → c4Diagram-AAUBKEIU-esEm54Ay.js} +1 -1
- package/dist/client/assets/channel-CqNHF3FD.js +1 -0
- package/dist/client/assets/{chunk-2J33WTMH-CWSwjsgk.js → chunk-2J33WTMH-DACfY8xM.js} +1 -1
- package/dist/client/assets/{chunk-3OPIFGDE-BnX3DljN.js → chunk-3OPIFGDE-DZNUiaUs.js} +1 -1
- package/dist/client/assets/{chunk-5ZQYHXKU-EyyJlI-d.js → chunk-5ZQYHXKU-ClDfkMAp.js} +1 -1
- package/dist/client/assets/{chunk-727SXJPM-Ci6CfxWj.js → chunk-727SXJPM-CCwA3GWT.js} +1 -1
- package/dist/client/assets/{chunk-AQP2D5EJ-RKZNsJTm.js → chunk-AQP2D5EJ-Byl4DRZ9.js} +1 -1
- package/dist/client/assets/{chunk-CSCIHK7Q-DBf0Euoj.js → chunk-CSCIHK7Q-CXymZQ7A.js} +1 -1
- package/dist/client/assets/{chunk-KSCS5N6A-BmLQI_2S.js → chunk-KSCS5N6A-VV3D8g6a.js} +1 -1
- package/dist/client/assets/{chunk-L5ZTLDWV-DKwVAb8G.js → chunk-L5ZTLDWV-BL9N5GRI.js} +1 -1
- package/dist/client/assets/{chunk-LZXEDZCA-axotKkbz.js → chunk-LZXEDZCA-DKNN-zno.js} +2 -2
- package/dist/client/assets/{chunk-ND2GUHAM-BBlJ4nXC.js → chunk-ND2GUHAM-CHCpQ1yr.js} +1 -1
- package/dist/client/assets/{chunk-NZK2D7GU-BKJGoZZt.js → chunk-NZK2D7GU-B0Dm74mt.js} +1 -1
- package/dist/client/assets/{chunk-O5CBEL6O-C-Ghigo1.js → chunk-O5CBEL6O-DAa5z2xG.js} +1 -1
- package/dist/client/assets/{chunk-WU5MYG2G-CCzVWYcd.js → chunk-WU5MYG2G-DrVJWKVR.js} +1 -1
- package/dist/client/assets/classDiagram-4FO5ZUOK-BjEgQwKg.js +1 -0
- package/dist/client/assets/classDiagram-v2-Q7XG4LA2-gGomH-F_.js +1 -0
- package/dist/client/assets/{dagre-BM42HDAG-rwIdPcjI.js → dagre-BM42HDAG-B0fsl8mE.js} +1 -1
- package/dist/client/assets/{diagram-2AECGRRQ-DJWxOQsg.js → diagram-2AECGRRQ-Cx_VEbSS.js} +1 -1
- package/dist/client/assets/{diagram-5GNKFQAL-C-mQeFtd.js → diagram-5GNKFQAL-Cv7VVn5r.js} +1 -1
- package/dist/client/assets/{diagram-KO2AKTUF-BAu6u4sR.js → diagram-KO2AKTUF-C8Sl5czV.js} +1 -1
- package/dist/client/assets/{diagram-LMA3HP47-U6CbvBn0.js → diagram-LMA3HP47-C3dZaLz2.js} +1 -1
- package/dist/client/assets/{diagram-OG6HWLK6-DNTwFcs1.js → diagram-OG6HWLK6-BAU9DtTr.js} +1 -1
- package/dist/client/assets/{erDiagram-TEJ5UH35-DaHX6Xw5.js → erDiagram-TEJ5UH35-DpNc-eBD.js} +1 -1
- package/dist/client/assets/{flowDiagram-I6XJVG4X-B9hKqL2l.js → flowDiagram-I6XJVG4X-DGwTAT1S.js} +1 -1
- package/dist/client/assets/{ganttDiagram-6RSMTGT7-BQ1HPHVO.js → ganttDiagram-6RSMTGT7-MPZhEgzJ.js} +1 -1
- package/dist/client/assets/{gitGraphDiagram-PVQCEYII-BBRcsmMK.js → gitGraphDiagram-PVQCEYII-CY0tHL5N.js} +1 -1
- package/dist/client/assets/index-CJRgmWwY.js +273 -0
- package/dist/client/assets/index-mRkJWu70.css +2 -0
- package/dist/client/assets/{infoDiagram-5YYISTIA-DUoNiSGC.js → infoDiagram-5YYISTIA-BBZeKjf4.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-YF4QCWOH-DJN1y2w8.js → ishikawaDiagram-YF4QCWOH-CM_iFrES.js} +1 -1
- package/dist/client/assets/{journeyDiagram-JHISSGLW-BxLLMT70.js → journeyDiagram-JHISSGLW-Do_lY94X.js} +1 -1
- package/dist/client/assets/{kanban-definition-UN3LZRKU-BdCPistJ.js → kanban-definition-UN3LZRKU-brxg3j37.js} +1 -1
- package/dist/client/assets/{line-4mIgXsZd.js → line-PHHbm9rj.js} +1 -1
- package/dist/client/assets/{mermaid-parser.core-Pu1QGWJ-.js → mermaid-parser.core-CLxAkW69.js} +1 -1
- package/dist/client/assets/{mermaid.core-DnH9DrgZ.js → mermaid.core-CebvSmx9.js} +3 -3
- package/dist/client/assets/{mindmap-definition-RKZ34NQL-DqE4ba3C.js → mindmap-definition-RKZ34NQL-CX_-ZaJF.js} +1 -1
- package/dist/client/assets/{pieDiagram-4H26LBE5-Crjy9ejf.js → pieDiagram-4H26LBE5-BJuHvql_.js} +1 -1
- package/dist/client/assets/{quadrantDiagram-W4KKPZXB-BUSZ2VB7.js → quadrantDiagram-W4KKPZXB-DiJll7l9.js} +1 -1
- package/dist/client/assets/{requirementDiagram-4Y6WPE33-CBhFKEoV.js → requirementDiagram-4Y6WPE33-Bg-tXgcj.js} +1 -1
- package/dist/client/assets/{sankeyDiagram-5OEKKPKP-CgdbY41v.js → sankeyDiagram-5OEKKPKP-BoK2egfZ.js} +1 -1
- package/dist/client/assets/{sequenceDiagram-3UESZ5HK-hkp_H7AE.js → sequenceDiagram-3UESZ5HK-DcrDWNzv.js} +1 -1
- package/dist/client/assets/{stateDiagram-AJRCARHV-C6x45RWp.js → stateDiagram-AJRCARHV-B995qt1S.js} +1 -1
- package/dist/client/assets/stateDiagram-v2-BHNVJYJU-Dn8P7Sa1.js +1 -0
- package/dist/client/assets/{timeline-definition-PNZ67QCA-CjnZWttQ.js → timeline-definition-PNZ67QCA-DqSVAhqL.js} +1 -1
- package/dist/client/assets/{vennDiagram-CIIHVFJN-DU5ZyYju.js → vennDiagram-CIIHVFJN-D9wVraVY.js} +1 -1
- package/dist/client/assets/{wardleyDiagram-YWT4CUSO-CaCEnCB0.js → wardleyDiagram-YWT4CUSO-CLff9zVd.js} +1 -1
- package/dist/client/assets/{xychartDiagram-2RQKCTM6-Ce3xa37U.js → xychartDiagram-2RQKCTM6-BuHUMMH2.js} +1 -1
- package/dist/client/index.html +2 -2
- package/dist/server/db.js +1 -0
- package/dist/server/db.js.map +1 -1
- package/dist/server/index.js +145 -88
- package/dist/server/index.js.map +1 -1
- package/dist/server/lib/attachments.js +24 -0
- package/dist/server/lib/attachments.js.map +1 -1
- package/dist/server/lib/buildOpenQuestionOperations.js +17 -2
- package/dist/server/lib/buildOpenQuestionOperations.js.map +1 -1
- package/dist/server/lib/bundleReviewOperations.js +9 -0
- package/dist/server/lib/bundleReviewOperations.js.map +1 -1
- package/dist/server/lib/chatOperations.js +40 -16
- package/dist/server/lib/chatOperations.js.map +1 -1
- package/dist/server/lib/executionRetry.js +694 -0
- package/dist/server/lib/executionRetry.js.map +1 -0
- package/dist/server/lib/executionRetryContext.js +489 -0
- package/dist/server/lib/executionRetryContext.js.map +1 -0
- package/dist/server/lib/executionRetryPredicates.js +74 -0
- package/dist/server/lib/executionRetryPredicates.js.map +1 -0
- package/dist/server/lib/executionRuns.js +141 -38
- package/dist/server/lib/executionRuns.js.map +1 -1
- package/dist/server/lib/executionTimeline.js +45 -0
- package/dist/server/lib/executionTimeline.js.map +1 -0
- package/dist/server/lib/specOperations.js +51 -16
- package/dist/server/lib/specOperations.js.map +1 -1
- package/dist/server/lib/specReviewContext.js +10 -4
- package/dist/server/lib/specReviewContext.js.map +1 -1
- package/dist/server/lib/specReviewOperations.js +33 -5
- package/dist/server/lib/specReviewOperations.js.map +1 -1
- package/dist/server/lib/testSessions.js +2 -5
- package/dist/server/lib/testSessions.js.map +1 -1
- package/dist/server/lib/ticketFollowUpOperations.js +74 -27
- package/dist/server/lib/ticketFollowUpOperations.js.map +1 -1
- package/dist/server/lib/ticketMessages.js +16 -0
- package/dist/server/lib/ticketMessages.js.map +1 -1
- package/dist/server/lib/types.js +1 -0
- package/dist/server/lib/types.js.map +1 -1
- package/dist/server/workers/build.js +1 -1
- package/dist/server/workers/build.js.map +1 -1
- package/dist/server/workers/chat.js +26 -20
- package/dist/server/workers/chat.js.map +1 -1
- package/dist/server/workers/followUp.js +24 -9
- package/dist/server/workers/followUp.js.map +1 -1
- package/dist/server/workers/plan.js +23 -4
- package/dist/server/workers/plan.js.map +1 -1
- package/dist/server/workers/planFollowUp.js +12 -9
- package/dist/server/workers/planFollowUp.js.map +1 -1
- package/dist/server/workers/review.js +2 -2
- package/dist/server/workers/review.js.map +1 -1
- package/dist/server/workers/specReview.js +2 -2
- package/dist/server/workers/specReview.js.map +1 -1
- package/dist/server/workers/specSession.js +29 -22
- package/dist/server/workers/specSession.js.map +1 -1
- package/package.json +1 -1
- package/dist/client/assets/ActivityView-DTEenQas.js +0 -1
- package/dist/client/assets/GitHubActionsDialog-BKWHonUS.js +0 -1
- package/dist/client/assets/TestsDialog-cBaoffCs.js +0 -4
- package/dist/client/assets/channel-Cym17waa.js +0 -1
- package/dist/client/assets/classDiagram-4FO5ZUOK-BcigvvIC.js +0 -1
- package/dist/client/assets/classDiagram-v2-Q7XG4LA2-3AdSuE0N.js +0 -1
- package/dist/client/assets/index-CEMaJCPS.js +0 -273
- package/dist/client/assets/index-lWftq65U.css +0 -2
- package/dist/client/assets/stateDiagram-v2-BHNVJYJU-DVzPjRPN.js +0 -1
package/dist/server/index.js
CHANGED
|
@@ -54,7 +54,9 @@ import { clearAutomationPause, countBlockingFindings, depthAtLeast, getBundleAut
|
|
|
54
54
|
import { runAutomationCommitChain } from './lib/ticketAutomationCommit.js';
|
|
55
55
|
import { getDiscoveredSkills, normalizeSkillNames } from './lib/skills.js';
|
|
56
56
|
import { clearAllPreviewProxies } from './lib/previewProxy.js';
|
|
57
|
-
import { createExecutionRun, addExecutionRunLink, addExecutionRunLinks, getExecutionRun, hasActiveBuildExecutionRunForTicket, getGitHubActionsExecutionDetails,
|
|
57
|
+
import { createExecutionRun, addExecutionRunLink, addExecutionRunLinks, getExecutionRun, hasActiveBuildExecutionRunForTicket, getGitHubActionsExecutionDetails, getTestExecutionDetails, GitHubActionsDispatchConflictError, listExecutionRunLinks, listExecutionRuns, listLifecycleEvents, listProjectLifecycleEvents, toCanonicalLifecycleEvent, transitionExecutionRun, withExecutionRunTransaction, } from './lib/executionRuns.js';
|
|
58
|
+
import { dispatchExecutionRetry, getExecutionRetryEligibility, resolveExecutionRetry } from './lib/executionRetry.js';
|
|
59
|
+
import { listExecutionTimeline } from './lib/executionTimeline.js';
|
|
58
60
|
import { cleanupAllExecutionEvidence, EXECUTION_EVIDENCE_CLEANUP_INTERVAL_MS, resolveExecutionEvidenceRetentionDays, } from './lib/executionRunRetention.js';
|
|
59
61
|
import { shutdownRealtimeServer } from './lib/shutdown.js';
|
|
60
62
|
import { resolveClientBuildPaths, shouldServeClientApp, STATIC_CLIENT_DIRECTORY_OPTIONS, STATIC_CLIENT_INDEX_OPTIONS, } from './lib/staticClient.js';
|
|
@@ -72,7 +74,7 @@ import { buildCommitMessagePrompt, normalizeGeneratedCommitMessage } from './lib
|
|
|
72
74
|
import { BUNDLE_PULL_REQUEST_SYNC_TTL_MS, getBundlePullRequestRecord, hasOpenBundlePullRequest, serializeBundlePullRequest, shouldSyncBundlePullRequest, syncTrackedBundlePullRequestIfNeeded, upsertBundlePullRequest, upsertBundlePullRequestFromGitHub, } from './lib/bundlePullRequests.js';
|
|
73
75
|
import { countInboundProjectLinks, getEffectiveProject, getProjectById, getProjectTicketStats, hasProjectRunIde, listProjects, replaceProjectLinks, serializeProject, updateProjectActiveTarget, validateProjectPayload, } from './lib/projects.js';
|
|
74
76
|
import { getActiveRunStatus, getActiveRunStatusForProject, getPreviewStatusForProject, hasRunConfig, isRunning, openIde, openTicketIdeInWorkspace, resolveProjectRunContextKey, runSetupCommandsInWorkspace, runTicketInWorkspace, stopAllTickets, stopTicket } from './lib/run.js';
|
|
75
|
-
import { clearAllTestSessions, createTestSession, deleteTestSession, discoverTestsForSession,
|
|
77
|
+
import { clearAllTestSessions, createTestSession, deleteTestSession, discoverTestsForSession, doesTestFileExistForSession, findProjectTestCommand, getTestSession, isTestSelectionSupported, startTestSessionRun, stopTestSessionRun } from './lib/testSessions.js';
|
|
76
78
|
import { getBundleBuildBlockerResponse, shouldStartBundleBuildNow, transitionTicketState, } from './lib/ticketWorkflowOperations.js';
|
|
77
79
|
import { submitPlanTicketFollowUp, submitTicketFollowUp, } from './lib/ticketFollowUpOperations.js';
|
|
78
80
|
import { canUseBuildControls, formatActiveRunConflict, getTicketRunReadinessError, resumeBuildTicketRun, runProjectTarget, runTicketRun, stopBuildTicketRun, stopReviewTicketRun, stopProjectTargetRun, stopTicketRun, } from './lib/ticketRunOperations.js';
|
|
@@ -167,6 +169,44 @@ const parseLifecycleListQuery = (query) => {
|
|
|
167
169
|
limit,
|
|
168
170
|
};
|
|
169
171
|
};
|
|
172
|
+
const parseExecutionTimelineScope = (projectId, query) => {
|
|
173
|
+
const hasScopeType = query.scope_type != null && query.scope_type !== '';
|
|
174
|
+
const hasScopeId = query.scope_id != null && query.scope_id !== '';
|
|
175
|
+
if (hasScopeType !== hasScopeId) {
|
|
176
|
+
return { error: 'scope_type and scope_id must be provided together' };
|
|
177
|
+
}
|
|
178
|
+
if (!hasScopeType) {
|
|
179
|
+
return { scopeEntity: undefined };
|
|
180
|
+
}
|
|
181
|
+
if (typeof query.scope_type !== 'string' || typeof query.scope_id !== 'string') {
|
|
182
|
+
return { error: 'scope_type and scope_id must be strings' };
|
|
183
|
+
}
|
|
184
|
+
const scopeType = query.scope_type;
|
|
185
|
+
const scopeId = query.scope_id.trim();
|
|
186
|
+
if (!['ticket', 'bundle', 'chat_session', 'spec_session'].includes(scopeType)) {
|
|
187
|
+
return { error: 'scope_type must be ticket, bundle, chat_session, or spec_session' };
|
|
188
|
+
}
|
|
189
|
+
if (!scopeId) {
|
|
190
|
+
return { error: 'scope_id must not be empty' };
|
|
191
|
+
}
|
|
192
|
+
const table = scopeType === 'ticket'
|
|
193
|
+
? 'tickets'
|
|
194
|
+
: scopeType === 'bundle'
|
|
195
|
+
? 'worktree_bundles'
|
|
196
|
+
: scopeType === 'chat_session'
|
|
197
|
+
? 'chat_sessions'
|
|
198
|
+
: 'spec_sessions';
|
|
199
|
+
const exists = db.prepare(`SELECT 1 FROM ${table} WHERE id = ? AND project_id = ?`).get(scopeId, projectId);
|
|
200
|
+
if (!exists) {
|
|
201
|
+
return { error: 'Scope not found', status: 404 };
|
|
202
|
+
}
|
|
203
|
+
return {
|
|
204
|
+
scopeEntity: {
|
|
205
|
+
type: scopeType,
|
|
206
|
+
id: scopeId,
|
|
207
|
+
},
|
|
208
|
+
};
|
|
209
|
+
};
|
|
170
210
|
const serializeTestExecutionDetail = (details, retryEligibility) => {
|
|
171
211
|
if (!details) {
|
|
172
212
|
return { kind: 'test', available: false, retry_eligibility: retryEligibility };
|
|
@@ -947,63 +987,6 @@ const parseTestTargetSelection = (value) => {
|
|
|
947
987
|
}
|
|
948
988
|
return null;
|
|
949
989
|
};
|
|
950
|
-
const unavailableTestRetry = (reason, status, message) => ({
|
|
951
|
-
eligibility: { eligible: false, reason },
|
|
952
|
-
error: { status, message },
|
|
953
|
-
});
|
|
954
|
-
const resolveTestRetry = (project, executionRun, details) => {
|
|
955
|
-
if (!executionRun || executionRun.kind !== 'test' || !details) {
|
|
956
|
-
return unavailableTestRetry('details_unavailable', 404, 'Test execution run not found');
|
|
957
|
-
}
|
|
958
|
-
if (!['failed', 'cancelled', 'interrupted'].includes(executionRun.status)) {
|
|
959
|
-
return unavailableTestRetry('run_not_terminal', 409, 'Only failed, cancelled, or interrupted test runs can be retried.');
|
|
960
|
-
}
|
|
961
|
-
if (hasDirectRetryChild(executionRun.id)) {
|
|
962
|
-
return unavailableTestRetry('retry_already_created', 409, 'A retry has already been created for this test run.');
|
|
963
|
-
}
|
|
964
|
-
if (!details.command_id) {
|
|
965
|
-
return unavailableTestRetry('command_unidentifiable', 409, 'The original test command is no longer identifiable.');
|
|
966
|
-
}
|
|
967
|
-
const command = findProjectTestCommand(project, details.command_id);
|
|
968
|
-
if (!command) {
|
|
969
|
-
return unavailableTestRetry('command_missing', 404, 'Test command not found');
|
|
970
|
-
}
|
|
971
|
-
const selection = parseTestTargetSelection(details.selection);
|
|
972
|
-
if (!selection) {
|
|
973
|
-
return unavailableTestRetry('selection_invalid', 409, 'The original test selection is no longer valid.');
|
|
974
|
-
}
|
|
975
|
-
let workspace;
|
|
976
|
-
if (details.ticket_id) {
|
|
977
|
-
const ticket = getTicket(details.ticket_id);
|
|
978
|
-
if (!ticket || ticket.project_id !== project.id) {
|
|
979
|
-
return unavailableTestRetry('target_missing', 404, 'Ticket not found');
|
|
980
|
-
}
|
|
981
|
-
workspace = resolveTicketTestWorkspace(ticket);
|
|
982
|
-
}
|
|
983
|
-
else if (details.target_kind === 'repo_root' || details.target_kind === 'bundle') {
|
|
984
|
-
workspace = resolveExplicitProjectTestWorkspace(project, details.target_kind, details.target_bundle_id);
|
|
985
|
-
}
|
|
986
|
-
else {
|
|
987
|
-
return unavailableTestRetry('target_invalid', 409, 'The original test target is no longer valid.');
|
|
988
|
-
}
|
|
989
|
-
if ('error' in workspace) {
|
|
990
|
-
return unavailableTestRetry('target_unavailable', workspace.status, workspace.error);
|
|
991
|
-
}
|
|
992
|
-
if (!isTestSelectionSupported(command, selection, workspace.cwd)) {
|
|
993
|
-
return unavailableTestRetry('selection_unsupported', 409, 'The selected test command only supports running the full suite.');
|
|
994
|
-
}
|
|
995
|
-
if (selection.kind !== 'all' && !doesTestFileExist(workspace.cwd, selection.path, command)) {
|
|
996
|
-
return unavailableTestRetry('test_file_missing', 404, 'Selected test file no longer exists in this target workspace.');
|
|
997
|
-
}
|
|
998
|
-
return {
|
|
999
|
-
eligibility: { eligible: true, reason: null },
|
|
1000
|
-
details,
|
|
1001
|
-
executionRun,
|
|
1002
|
-
command,
|
|
1003
|
-
selection,
|
|
1004
|
-
workspace,
|
|
1005
|
-
};
|
|
1006
|
-
};
|
|
1007
990
|
const resolveProjectFileSuggestionWorkspace = (project, options = {}) => {
|
|
1008
991
|
const ticketId = options.ticketId?.trim() || null;
|
|
1009
992
|
if (ticketId) {
|
|
@@ -7582,6 +7565,70 @@ app.get('/projects/:id/lifecycle-events', (req, res) => {
|
|
|
7582
7565
|
throw error;
|
|
7583
7566
|
}
|
|
7584
7567
|
});
|
|
7568
|
+
app.get('/projects/:id/execution-timeline', (req, res) => {
|
|
7569
|
+
const project = getProjectById(req.params.id);
|
|
7570
|
+
if (!project) {
|
|
7571
|
+
return res.status(404).json({ error: 'Not found' });
|
|
7572
|
+
}
|
|
7573
|
+
const query = parseLifecycleListQuery(req.query);
|
|
7574
|
+
if ('error' in query) {
|
|
7575
|
+
return res.status(400).json({ error: query.error });
|
|
7576
|
+
}
|
|
7577
|
+
const scope = parseExecutionTimelineScope(project.id, req.query);
|
|
7578
|
+
if ('error' in scope) {
|
|
7579
|
+
return res.status(scope.status ?? 400).json({ error: scope.error });
|
|
7580
|
+
}
|
|
7581
|
+
try {
|
|
7582
|
+
return res.json(listExecutionTimeline({
|
|
7583
|
+
project,
|
|
7584
|
+
kind: query.kind,
|
|
7585
|
+
statuses: query.statuses,
|
|
7586
|
+
cursor: query.cursor,
|
|
7587
|
+
limit: query.limit,
|
|
7588
|
+
scope: scope.scopeEntity,
|
|
7589
|
+
}));
|
|
7590
|
+
}
|
|
7591
|
+
catch (error) {
|
|
7592
|
+
if (error instanceof Error && error.message === 'Invalid execution run cursor') {
|
|
7593
|
+
return res.status(400).json({ error: 'cursor must be a valid execution run cursor' });
|
|
7594
|
+
}
|
|
7595
|
+
throw error;
|
|
7596
|
+
}
|
|
7597
|
+
});
|
|
7598
|
+
app.get('/projects/:id/execution-timeline/:runId', (req, res) => {
|
|
7599
|
+
const project = getProjectById(req.params.id);
|
|
7600
|
+
if (!project) {
|
|
7601
|
+
return res.status(404).json({ error: 'Not found' });
|
|
7602
|
+
}
|
|
7603
|
+
const scope = parseExecutionTimelineScope(project.id, req.query);
|
|
7604
|
+
if ('error' in scope) {
|
|
7605
|
+
return res.status(scope.status ?? 400).json({ error: scope.error });
|
|
7606
|
+
}
|
|
7607
|
+
const run = getExecutionRun(project.id, req.params.runId);
|
|
7608
|
+
if (!run) {
|
|
7609
|
+
return res.status(404).json({ error: 'Execution run not found' });
|
|
7610
|
+
}
|
|
7611
|
+
const links = listExecutionRunLinks(project.id, run.id);
|
|
7612
|
+
if (scope.scopeEntity && !links.some(link => link.entity_type === scope.scopeEntity?.type && link.entity_id === scope.scopeEntity.id)) {
|
|
7613
|
+
return res.status(404).json({ error: 'Execution run not found' });
|
|
7614
|
+
}
|
|
7615
|
+
const testDetails = run.kind === 'test' ? getTestExecutionDetails(project.id, run.id) : null;
|
|
7616
|
+
const retryEligibility = getExecutionRetryEligibility(project.id, run.id);
|
|
7617
|
+
const details = run.kind === 'test'
|
|
7618
|
+
? serializeTestExecutionDetail(testDetails, retryEligibility)
|
|
7619
|
+
: run.kind === 'github_actions'
|
|
7620
|
+
? { ...serializeGitHubActionsExecutionDetail(getGitHubActionsExecutionDetails(project.id, run.id)), retry_eligibility: retryEligibility }
|
|
7621
|
+
: ['automation_commit', 'automation_push', 'automation_pull_request'].includes(run.kind)
|
|
7622
|
+
? { kind: run.kind, available: true, automation_stage: getAutomationStageDetails(project.id, run.id), retry_eligibility: retryEligibility }
|
|
7623
|
+
: { kind: run.kind, available: true, retry_eligibility: retryEligibility };
|
|
7624
|
+
return res.json({
|
|
7625
|
+
run,
|
|
7626
|
+
links,
|
|
7627
|
+
events: listLifecycleEvents(project.id, run.id).map(toCanonicalLifecycleEvent),
|
|
7628
|
+
details,
|
|
7629
|
+
agent_runs: listAgentRunsForExecutionRun(run.id),
|
|
7630
|
+
});
|
|
7631
|
+
});
|
|
7585
7632
|
app.get('/projects/:id/execution-runs/:runId', (req, res) => {
|
|
7586
7633
|
const project = getProjectById(req.params.id);
|
|
7587
7634
|
if (!project) {
|
|
@@ -7592,46 +7639,55 @@ app.get('/projects/:id/execution-runs/:runId', (req, res) => {
|
|
|
7592
7639
|
return res.status(404).json({ error: 'Execution run not found' });
|
|
7593
7640
|
}
|
|
7594
7641
|
const testDetails = run.kind === 'test' ? getTestExecutionDetails(project.id, run.id) : null;
|
|
7642
|
+
const retryEligibility = getExecutionRetryEligibility(project.id, run.id);
|
|
7595
7643
|
const details = run.kind === 'test'
|
|
7596
|
-
? serializeTestExecutionDetail(testDetails,
|
|
7644
|
+
? serializeTestExecutionDetail(testDetails, retryEligibility)
|
|
7597
7645
|
: run.kind === 'github_actions'
|
|
7598
|
-
? serializeGitHubActionsExecutionDetail(getGitHubActionsExecutionDetails(project.id, run.id))
|
|
7599
|
-
:
|
|
7646
|
+
? { ...serializeGitHubActionsExecutionDetail(getGitHubActionsExecutionDetails(project.id, run.id)), retry_eligibility: retryEligibility }
|
|
7647
|
+
: ['automation_commit', 'automation_push', 'automation_pull_request'].includes(run.kind)
|
|
7648
|
+
? { kind: run.kind, available: true, automation_stage: getAutomationStageDetails(project.id, run.id), retry_eligibility: retryEligibility }
|
|
7649
|
+
: { kind: run.kind, available: true, retry_eligibility: retryEligibility };
|
|
7600
7650
|
const links = listExecutionRunLinks(project.id, run.id);
|
|
7601
7651
|
const agentRuns = listAgentRunsForExecutionRun(run.id);
|
|
7602
|
-
|
|
7652
|
+
const events = listLifecycleEvents(project.id, run.id).map(toCanonicalLifecycleEvent);
|
|
7653
|
+
return res.json({ run, links, events, details, agent_runs: agentRuns });
|
|
7603
7654
|
});
|
|
7604
7655
|
app.post('/projects/:id/execution-runs/:runId/retry', async (req, res) => {
|
|
7605
7656
|
const project = getProjectById(req.params.id);
|
|
7606
7657
|
if (!project) {
|
|
7607
7658
|
return res.status(404).json({ error: 'Not found' });
|
|
7608
7659
|
}
|
|
7609
|
-
const
|
|
7610
|
-
const
|
|
7611
|
-
|
|
7612
|
-
|
|
7613
|
-
|
|
7614
|
-
|
|
7615
|
-
|
|
7616
|
-
|
|
7617
|
-
|
|
7618
|
-
|
|
7619
|
-
|
|
7620
|
-
|
|
7621
|
-
|
|
7660
|
+
const resolution = resolveExecutionRetry(project.id, req.params.runId);
|
|
7661
|
+
const result = await dispatchExecutionRetry(resolution, io, async (retry) => {
|
|
7662
|
+
const session = createTestSession({
|
|
7663
|
+
projectId: project.id,
|
|
7664
|
+
ticketId: retry.ticketId,
|
|
7665
|
+
targetKind: retry.workspace.target_kind,
|
|
7666
|
+
targetBundleId: retry.workspace.target_bundle_id,
|
|
7667
|
+
cwd: retry.workspace.cwd,
|
|
7668
|
+
targetLabel: retry.workspace.target_label,
|
|
7669
|
+
});
|
|
7670
|
+
try {
|
|
7671
|
+
const started = await startTestSessionRun(session.id, project, {
|
|
7672
|
+
command: retry.command,
|
|
7673
|
+
selection: retry.selection,
|
|
7674
|
+
retryOfRunId: req.params.runId,
|
|
7675
|
+
deleteOnComplete: true,
|
|
7676
|
+
}, io);
|
|
7677
|
+
if (!started.run_id) {
|
|
7678
|
+
throw new Error('Test retry did not create a replacement execution');
|
|
7679
|
+
}
|
|
7680
|
+
return started.run_id;
|
|
7681
|
+
}
|
|
7682
|
+
catch (error) {
|
|
7683
|
+
await deleteTestSession(session.id, io).catch(() => undefined);
|
|
7684
|
+
throw error;
|
|
7685
|
+
}
|
|
7622
7686
|
});
|
|
7623
|
-
|
|
7624
|
-
return res.status(
|
|
7625
|
-
command: retry.command,
|
|
7626
|
-
selection: retry.selection,
|
|
7627
|
-
retryOfRunId: retry.executionRun.id,
|
|
7628
|
-
deleteOnComplete: true,
|
|
7629
|
-
}, io));
|
|
7630
|
-
}
|
|
7631
|
-
catch (error) {
|
|
7632
|
-
await deleteTestSession(session.id, io).catch(() => undefined);
|
|
7633
|
-
return res.status(409).json({ error: getErrorMessage(error) });
|
|
7687
|
+
if (!result.ok) {
|
|
7688
|
+
return res.status(result.status).json({ error: result.error, reason: result.reason });
|
|
7634
7689
|
}
|
|
7690
|
+
return res.status(201).json(result.value);
|
|
7635
7691
|
});
|
|
7636
7692
|
app.post('/tickets/:id/test-sessions', (req, res) => {
|
|
7637
7693
|
const ticket = getTicket(req.params.id);
|
|
@@ -7735,7 +7791,7 @@ app.post('/test-sessions/:id/stop', async (req, res) => {
|
|
|
7735
7791
|
}
|
|
7736
7792
|
});
|
|
7737
7793
|
app.delete('/test-sessions/:id', async (req, res) => {
|
|
7738
|
-
const deleted = await deleteTestSession(req.params.id, io
|
|
7794
|
+
const deleted = await deleteTestSession(req.params.id, io);
|
|
7739
7795
|
if (!deleted) {
|
|
7740
7796
|
return res.status(404).json({ error: 'Not found' });
|
|
7741
7797
|
}
|
|
@@ -7890,6 +7946,7 @@ app.post('/tickets/:id/plan', (req, res) => {
|
|
|
7890
7946
|
}
|
|
7891
7947
|
const scheduling = startPlan(ticket, io, {
|
|
7892
7948
|
branch,
|
|
7949
|
+
targetBundleId,
|
|
7893
7950
|
});
|
|
7894
7951
|
if (!scheduling.accepted) {
|
|
7895
7952
|
const status = scheduling.reason === 'queue_failed' ? 503 : 409;
|