@straiffi/archon 1.2.33 → 1.3.0
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/TestsDialog-j0fYZ2AP.js +5 -0
- package/dist/client/assets/arc-4YfDy8uK.js +1 -0
- package/dist/client/assets/architecture-7EHR7CIX-B0ldQ5d_.js +1 -0
- package/dist/client/assets/architectureDiagram-3BPJPVTR-CnivPz9K.js +36 -0
- package/dist/client/assets/array-DOVTz2Mq.js +1 -0
- package/dist/client/assets/blockDiagram-GPEHLZMM-Rd0YcyFR.js +132 -0
- package/dist/client/assets/c4Diagram-AAUBKEIU-kFDNpO2v.js +10 -0
- package/dist/client/assets/channel-C2eURzdr.js +1 -0
- package/dist/client/assets/chunk-2J33WTMH-CaqozdP8.js +1 -0
- package/dist/client/assets/chunk-3OPIFGDE-B8iFdxrR.js +62 -0
- package/dist/client/assets/chunk-4BX2VUAB-B3mVWXS4.js +1 -0
- package/dist/client/assets/chunk-4EGX6M5U-DTB6Um9X.js +1 -0
- package/dist/client/assets/chunk-55IACEB6-BB-0ajVZ.js +1 -0
- package/dist/client/assets/chunk-5DO6E6H7-DtX6GYFY.js +1 -0
- package/dist/client/assets/chunk-5ZQYHXKU-BrIMEXNE.js +2 -0
- package/dist/client/assets/chunk-727SXJPM-BtIA7PWh.js +206 -0
- package/dist/client/assets/chunk-AQP2D5EJ-fZoHENDl.js +231 -0
- package/dist/client/assets/chunk-BR22UD5L-CkJbcXZG.js +1 -0
- package/dist/client/assets/chunk-BSJP7CBP-X4eyoSqE.js +1 -0
- package/dist/client/assets/chunk-CSCIHK7Q-DWO1NEfx.js +124 -0
- package/dist/client/assets/chunk-FHYWG6QK-Cb5GzaCb.js +1 -0
- package/dist/client/assets/chunk-FMBD7UC4-mGY2D2YB.js +15 -0
- package/dist/client/assets/chunk-KSCS5N6A-CF2IF83X.js +10 -0
- package/dist/client/assets/chunk-L5ZTLDWV-BUItv2Ib.js +1 -0
- package/dist/client/assets/chunk-LZXEDZCA-BXfHhDLH.js +2 -0
- package/dist/client/assets/chunk-MPE355IW-WT7jTCZh.js +1 -0
- package/dist/client/assets/chunk-MZUSXYTE-CClXfOSj.js +1 -0
- package/dist/client/assets/chunk-N66VUXT2-Bpe4yprw.js +1 -0
- package/dist/client/assets/chunk-ND2GUHAM-C2qj1a3s.js +1 -0
- package/dist/client/assets/chunk-NNHCCRGN-C4ci-Gd6.js +159 -0
- package/dist/client/assets/chunk-NZK2D7GU-Chir8aJ-.js +1 -0
- package/dist/client/assets/chunk-O5CBEL6O-DEfSochA.js +70 -0
- package/dist/client/assets/chunk-PUPMXCY4-Dh45WL5s.js +1 -0
- package/dist/client/assets/chunk-QZHKN3VN-U_l6-b5-.js +1 -0
- package/dist/client/assets/chunk-UIBZB4QT-Cp2xSKlH.js +1 -0
- package/dist/client/assets/chunk-WCWK7LTN-BOt5FFIu.js +1 -0
- package/dist/client/assets/chunk-WU5MYG2G-BszlpZ1y.js +1 -0
- package/dist/client/assets/chunk-XPW4576I-YiT9fJFY.js +32 -0
- package/dist/client/assets/classDiagram-4FO5ZUOK-D8cVFEdr.js +1 -0
- package/dist/client/assets/classDiagram-v2-Q7XG4LA2-BJah23qn.js +1 -0
- package/dist/client/assets/cose-bilkent-S5V4N54A-CtC2zeMr.js +1 -0
- package/dist/client/assets/cytoscape.esm-DnmRtSj9.js +321 -0
- package/dist/client/assets/dagre-5AiD43Uu.js +1 -0
- package/dist/client/assets/dagre-BM42HDAG-D-czLNXn.js +4 -0
- package/dist/client/assets/defaultLocale-Ck2Xxk-C.js +1 -0
- package/dist/client/assets/diagram-2AECGRRQ-DswYVROV.js +43 -0
- package/dist/client/assets/diagram-5GNKFQAL-C2LB4-Kp.js +10 -0
- package/dist/client/assets/diagram-KO2AKTUF-4pdiiwxL.js +3 -0
- package/dist/client/assets/diagram-LMA3HP47-DgGvwfLE.js +24 -0
- package/dist/client/assets/diagram-OG6HWLK6-ODytJwCx.js +24 -0
- package/dist/client/assets/dist-BQjeO--f.js +1 -0
- package/dist/client/assets/erDiagram-TEJ5UH35-CE_PuRb6.js +85 -0
- package/dist/client/assets/eventmodeling-FCH6USID-BczyucFk.js +1 -0
- package/dist/client/assets/flowDiagram-I6XJVG4X-COhcCfz4.js +162 -0
- package/dist/client/assets/ganttDiagram-6RSMTGT7-Dj2Sy2Gh.js +292 -0
- package/dist/client/assets/gitGraph-WXDBUCRP-x5mlGyzr.js +1 -0
- package/dist/client/assets/gitGraphDiagram-PVQCEYII-D_3GyFuB.js +106 -0
- package/dist/client/assets/graphlib-CMVU3iJd.js +1 -0
- package/dist/client/assets/index-C4OZfq84.js +260 -0
- package/dist/client/assets/index-I0a6lbgt.css +2 -0
- package/dist/client/assets/info-J43DQDTF-BsM_Qb70.js +1 -0
- package/dist/client/assets/infoDiagram-5YYISTIA-CBWV9QV_.js +2 -0
- package/dist/client/assets/init-Bft5Ffpj.js +1 -0
- package/dist/client/assets/ishikawaDiagram-YF4QCWOH-Ddnuid12.js +70 -0
- package/dist/client/assets/journeyDiagram-JHISSGLW-BpXuCD5H.js +139 -0
- package/dist/client/assets/kanban-definition-UN3LZRKU-BuZe6GDK.js +89 -0
- package/dist/client/assets/katex-Bpz5kWH9.js +257 -0
- package/dist/client/assets/line-BE4yoTuW.js +1 -0
- package/dist/client/assets/linear-BYVhmsFj.js +1 -0
- package/dist/client/assets/mermaid-parser.core-B2H_eqyg.js +4 -0
- package/dist/client/assets/mermaid.core-CU1B0VWF.js +9 -0
- package/dist/client/assets/mindmap-definition-RKZ34NQL-CG4XJeRx.js +96 -0
- package/dist/client/assets/ordinal-DIg8h6NI.js +1 -0
- package/dist/client/assets/packet-YPE3B663-DeVIMHV5.js +1 -0
- package/dist/client/assets/path-DfRbCp9y.js +1 -0
- package/dist/client/assets/pie-LRSECV5Y-ByRF4_cj.js +1 -0
- package/dist/client/assets/pieDiagram-4H26LBE5-CN5uHxOy.js +30 -0
- package/dist/client/assets/quadrantDiagram-W4KKPZXB-Bxov0SvH.js +7 -0
- package/dist/client/assets/radar-GUYGQ44K-XEreg8aR.js +1 -0
- package/dist/client/assets/requirementDiagram-4Y6WPE33-DWw4nY3R.js +84 -0
- package/dist/client/assets/rough.esm-DCvqWubr.js +1 -0
- package/dist/client/assets/sankeyDiagram-5OEKKPKP-CzRuy9FU.js +40 -0
- package/dist/client/assets/sequenceDiagram-3UESZ5HK-BLAdf9cH.js +162 -0
- package/dist/client/assets/src-BcaL9nCM.js +1 -0
- package/dist/client/assets/stateDiagram-AJRCARHV-Dbwp6kCy.js +1 -0
- package/dist/client/assets/stateDiagram-v2-BHNVJYJU-BWC0_Q2v.js +1 -0
- package/dist/client/assets/timeline-definition-PNZ67QCA-HajxAhbq.js +120 -0
- package/dist/client/assets/treeView-BLDUP644-D85P2Ji3.js +1 -0
- package/dist/client/assets/treemap-LRROVOQU-BnXG5utQ.js +1 -0
- package/dist/client/assets/vennDiagram-CIIHVFJN-DWaZcpRa.js +34 -0
- package/dist/client/assets/wardley-L42UT6IY-Ct9qHtJP.js +1 -0
- package/dist/client/assets/wardleyDiagram-YWT4CUSO-BPhQM7CW.js +78 -0
- package/dist/client/assets/xychartDiagram-2RQKCTM6-CITmODsS.js +7 -0
- package/dist/client/index.html +3 -4
- package/dist/server/db.js +91 -0
- package/dist/server/db.js.map +1 -1
- package/dist/server/index.js +384 -123
- package/dist/server/index.js.map +1 -1
- package/dist/server/lib/agent.js +475 -1
- package/dist/server/lib/agent.js.map +1 -1
- package/dist/server/lib/attachments.js +146 -0
- package/dist/server/lib/attachments.js.map +1 -1
- package/dist/server/lib/buildFlow.js +2 -1
- package/dist/server/lib/buildFlow.js.map +1 -1
- package/dist/server/lib/chatOperations.js +2 -0
- package/dist/server/lib/chatOperations.js.map +1 -1
- package/dist/server/lib/chats.js +9 -4
- package/dist/server/lib/chats.js.map +1 -1
- package/dist/server/lib/externalSource.js +32 -0
- package/dist/server/lib/externalSource.js.map +1 -0
- package/dist/server/lib/httpError.js +9 -0
- package/dist/server/lib/httpError.js.map +1 -0
- package/dist/server/lib/mobileAccess.js +3 -0
- package/dist/server/lib/mobileAccess.js.map +1 -1
- package/dist/server/lib/specArtifact.js +256 -0
- package/dist/server/lib/specArtifact.js.map +1 -0
- package/dist/server/lib/specDraft.js +33 -0
- package/dist/server/lib/specDraft.js.map +1 -0
- package/dist/server/lib/specExport.js +70 -0
- package/dist/server/lib/specExport.js.map +1 -0
- package/dist/server/lib/specFormatting.js +26 -0
- package/dist/server/lib/specFormatting.js.map +1 -0
- package/dist/server/lib/specMessages.js +47 -0
- package/dist/server/lib/specMessages.js.map +1 -0
- package/dist/server/lib/specOperations.js +150 -0
- package/dist/server/lib/specOperations.js.map +1 -0
- package/dist/server/lib/specSections.js +82 -0
- package/dist/server/lib/specSections.js.map +1 -0
- package/dist/server/lib/specs.js +318 -0
- package/dist/server/lib/specs.js.map +1 -0
- package/dist/server/lib/thinkingState.js +26 -0
- package/dist/server/lib/thinkingState.js.map +1 -1
- package/dist/server/lib/ticketBatch.js +55 -0
- package/dist/server/lib/ticketBatch.js.map +1 -0
- package/dist/server/lib/ticketDescriptionContract.js +52 -0
- package/dist/server/lib/ticketDescriptionContract.js.map +1 -0
- package/dist/server/lib/tickets.js +29 -3
- package/dist/server/lib/tickets.js.map +1 -1
- package/dist/server/workers/build.js +14 -1
- package/dist/server/workers/build.js.map +1 -1
- package/dist/server/workers/chat.js +10 -1
- package/dist/server/workers/chat.js.map +1 -1
- package/dist/server/workers/followUp.js +10 -1
- package/dist/server/workers/followUp.js.map +1 -1
- package/dist/server/workers/plan.js +23 -33
- package/dist/server/workers/plan.js.map +1 -1
- package/dist/server/workers/planFollowUp.js +10 -0
- package/dist/server/workers/planFollowUp.js.map +1 -1
- package/dist/server/workers/review.js +14 -1
- package/dist/server/workers/review.js.map +1 -1
- package/dist/server/workers/specSession.js +418 -0
- package/dist/server/workers/specSession.js.map +1 -0
- package/package.json +1 -1
- package/dist/client/assets/TestsDialog-B7r9ZQQo.js +0 -5
- package/dist/client/assets/dropdown-menu-BULYW006.js +0 -43
- package/dist/client/assets/index-B62IAkKy.js +0 -150
- package/dist/client/assets/index-DoNUd8CK.css +0 -2
- /package/dist/client/assets/{rolldown-runtime-BYbx6iT9.js → chunk-BNv3lrIs.js} +0 -0
package/dist/server/index.js
CHANGED
|
@@ -9,8 +9,10 @@ import cors from 'cors';
|
|
|
9
9
|
import { adjectives, animals, uniqueNamesGenerator } from 'unique-names-generator';
|
|
10
10
|
import db from './db.js';
|
|
11
11
|
import { deleteChatSession, getChatSession, listChatSessions, updateChatSessionMode } from './lib/chats.js';
|
|
12
|
-
import {
|
|
12
|
+
import { deleteSpecSession, getCreatedTicketIdsForSnapshot, getSpecSession, getSpecSnapshot, listSpecSessions } from './lib/specs.js';
|
|
13
|
+
import { createChatAttachment, createSpecAttachment, deleteChatAttachment, deleteSpecSessionAttachment, getChatAttachmentById, getChatAttachmentForSession, getChatAttachmentLinkedMessageId, getSpecAttachmentForSession, getSpecAttachmentLinkedMessageId, MAX_CHAT_ATTACHMENTS_PER_TURN, MAX_CHAT_ATTACHMENT_SIZE_BYTES, MAX_SPEC_ATTACHMENTS_PER_TURN, MAX_SPEC_ATTACHMENT_SIZE_BYTES, readChatAttachmentContent, readSpecAttachmentContent, validateImageAttachment, } from './lib/attachments.js';
|
|
13
14
|
import { createProjectChatSession, resolveChatMode, resolveChatTool, stopProjectChatSessionResponse, submitProjectChatSessionMessage, } from './lib/chatOperations.js';
|
|
15
|
+
import { createProjectSpecSession, deleteProjectSpecSession, stopProjectSpecSessionResponse, submitProjectSpecSessionMessage, } from './lib/specOperations.js';
|
|
14
16
|
import { resolveChatSessionTarget } from './lib/chatTargets.js';
|
|
15
17
|
import { createChatMessage, getChatMessage, updateChatMessageContent } from './lib/chatMessages.js';
|
|
16
18
|
import { attachChatTicketProposalsToMessageContent } from './lib/chatTicketProposals.js';
|
|
@@ -23,7 +25,7 @@ import { browseRepoPath } from './lib/directoryPicker.js';
|
|
|
23
25
|
import { resolveDesktopServerHost } from './lib/desktopServerHost.js';
|
|
24
26
|
import { emitMobileRealtime, getMobileAccessStatus, regenerateMobilePairingChallenge, revokeMobileSession, setMobileAccessStatusChangeListener, startMobileAccess, stopMobileAccess, } from './lib/mobileAccess.js';
|
|
25
27
|
import { validateDesktopMobileAccessRequest } from './lib/mobileAccessSecurity.js';
|
|
26
|
-
import { refreshChatSessionUsageSnapshot, stopAllBuildAgents } from './lib/agent.js';
|
|
28
|
+
import { refreshChatSessionUsageSnapshot, refreshSpecSessionUsageSnapshot, stopAllBuildAgents } from './lib/agent.js';
|
|
27
29
|
import { runLightweightPrompt } from './lib/lightweightPrompt.js';
|
|
28
30
|
import { startBuildChain, stopBuildChain } from './lib/buildChains.js';
|
|
29
31
|
import { enqueueBundledBuild, getDependencyOrderedBundleTicketIds, removeTicketFromBuildSequence, stopAllActiveBuildSequences, } from './lib/buildSequences.js';
|
|
@@ -58,19 +60,17 @@ import { getActiveBundleStageChangeHead, undoTicketStage } from './lib/ticketUnd
|
|
|
58
60
|
import { closeAllTerminalSessions, createProjectTerminalSession, createTerminalSessionForWorkspace, destroyTerminalSessionById, registerTerminalSocketHandlers, } from './lib/terminal.js';
|
|
59
61
|
import { countBundleTickets, createTicketRecord, createBundle, deleteBundle, ensureProjectRootBundle, autoParkTicketIfStale, autoParkSettledBundleTicket, isBundleSettledForAutoPark, clearAutoParkDismissalIfNeeded, assignTicketDoneOrder, assignTicketLaneOrder, getBundle, getBundleByName, getBundleByBranch, getBundleRepresentativeTicketContext, isProjectRootBundle, getTicket, isBundledTicket, listTicketsInRunContext, listBundles, listBoardTicketEnrichment, listTickets, resolveTicketBranch, resolveTicketTool, } from './lib/tickets.js';
|
|
60
62
|
import { createTicketMessage, upsertTicketDescriptionMessage } from './lib/ticketMessages.js';
|
|
63
|
+
import { HttpError } from './lib/httpError.js';
|
|
64
|
+
import { parseIncomingExternalSource } from './lib/externalSource.js';
|
|
65
|
+
import { materializeTicketsWithDependencies } from './lib/ticketBatch.js';
|
|
66
|
+
import { IMPLEMENTATION_READY_DESCRIPTION_SECTION_HEADINGS } from './lib/ticketDescriptionContract.js';
|
|
67
|
+
import { buildSpecImplementationDraft } from './lib/specDraft.js';
|
|
68
|
+
import { exportSpecToDocs, checkSpecExportCollision } from './lib/specExport.js';
|
|
61
69
|
import { createTicketDependency, deleteTicketDependenciesForTicket, deleteTicketDependency, getTicketDependency, isTicketRunSetupReady, listTicketDependencies, validateCreateTicketDependency, } from './lib/ticketDependencies.js';
|
|
62
70
|
import { startBuild } from './workers/build.js';
|
|
63
71
|
import { startFollowUp } from './workers/followUp.js';
|
|
64
|
-
import { expandPlanningTicketOutline, generatePlanningTicketOutline, generatePlanText,
|
|
72
|
+
import { expandPlanningTicketOutline, generatePlanningTicketOutline, generatePlanText, prepareLinkedPlanningResearch, startPlan, } from './workers/plan.js';
|
|
65
73
|
import { startReview } from './workers/review.js';
|
|
66
|
-
class HttpError extends Error {
|
|
67
|
-
status;
|
|
68
|
-
constructor(status, message) {
|
|
69
|
-
super(message);
|
|
70
|
-
this.name = 'HttpError';
|
|
71
|
-
this.status = status;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
74
|
const getErrorMessage = (error) => {
|
|
75
75
|
return error instanceof Error ? error.message : String(error);
|
|
76
76
|
};
|
|
@@ -296,6 +296,24 @@ const getChatAttachmentUploadErrorMessage = (error) => {
|
|
|
296
296
|
}
|
|
297
297
|
return 'Unable to upload the selected images right now.';
|
|
298
298
|
};
|
|
299
|
+
const specAttachmentUpload = multer({
|
|
300
|
+
storage: multer.memoryStorage(),
|
|
301
|
+
limits: {
|
|
302
|
+
files: MAX_SPEC_ATTACHMENTS_PER_TURN,
|
|
303
|
+
fileSize: MAX_SPEC_ATTACHMENT_SIZE_BYTES,
|
|
304
|
+
},
|
|
305
|
+
});
|
|
306
|
+
const getSpecAttachmentUploadErrorMessage = (error) => {
|
|
307
|
+
if (error instanceof multer.MulterError) {
|
|
308
|
+
if (error.code === 'LIMIT_FILE_COUNT') {
|
|
309
|
+
return 'You can attach up to 4 images per turn.';
|
|
310
|
+
}
|
|
311
|
+
if (error.code === 'LIMIT_FILE_SIZE') {
|
|
312
|
+
return 'Images must be 10 MB or smaller.';
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
return 'Unable to upload the selected images right now.';
|
|
316
|
+
};
|
|
299
317
|
const normalizeGeneratedCommitMessage = (value) => {
|
|
300
318
|
const firstLine = value
|
|
301
319
|
.replace(/\r/g, '\n')
|
|
@@ -396,33 +414,6 @@ const listBundleReviewTicketContextsStatement = db.prepare(`
|
|
|
396
414
|
AND tickets.worktree_bundle_id = ?
|
|
397
415
|
ORDER BY tickets.updated_at DESC, tickets.id DESC
|
|
398
416
|
`);
|
|
399
|
-
const parseIncomingExternalSource = (value) => {
|
|
400
|
-
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
401
|
-
return null;
|
|
402
|
-
}
|
|
403
|
-
const payload = value;
|
|
404
|
-
const provider = asTrimmedString(payload.provider);
|
|
405
|
-
const externalId = asTrimmedString(payload.external_id);
|
|
406
|
-
const externalKey = asTrimmedString(payload.external_key);
|
|
407
|
-
const externalUrl = asTrimmedString(payload.external_url);
|
|
408
|
-
if (provider === '' && externalId === '' && externalKey === '' && externalUrl === '') {
|
|
409
|
-
return null;
|
|
410
|
-
}
|
|
411
|
-
if ((provider !== 'jira' && provider !== 'github') || !externalId || !externalKey) {
|
|
412
|
-
throw new HttpError(400, 'external_source must contain a supported provider, external_id, and external_key');
|
|
413
|
-
}
|
|
414
|
-
const metadata = payload.metadata;
|
|
415
|
-
if (metadata != null && (typeof metadata !== 'object' || Array.isArray(metadata))) {
|
|
416
|
-
throw new HttpError(400, 'external_source metadata must be an object when provided');
|
|
417
|
-
}
|
|
418
|
-
return {
|
|
419
|
-
provider: provider,
|
|
420
|
-
external_id: externalId,
|
|
421
|
-
external_key: externalKey,
|
|
422
|
-
external_url: externalUrl || null,
|
|
423
|
-
metadata: metadata ?? null,
|
|
424
|
-
};
|
|
425
|
-
};
|
|
426
417
|
const slugify = (text) => {
|
|
427
418
|
const slug = String(text ?? '')
|
|
428
419
|
.replace(/[^a-zA-Z0-9\s-]/g, '')
|
|
@@ -574,6 +565,13 @@ const getProjectChatSession = (projectId, chatSessionId) => {
|
|
|
574
565
|
}
|
|
575
566
|
return chatSession;
|
|
576
567
|
};
|
|
568
|
+
const getProjectSpecSession = (projectId, specSessionId) => {
|
|
569
|
+
const specSession = getSpecSession(specSessionId, { includeMessages: true });
|
|
570
|
+
if (!specSession || specSession.project_id !== projectId) {
|
|
571
|
+
return null;
|
|
572
|
+
}
|
|
573
|
+
return specSession;
|
|
574
|
+
};
|
|
577
575
|
const resolveTicketDiffWorkspace = (ticket) => {
|
|
578
576
|
const project = getEffectiveProject(ticket);
|
|
579
577
|
if (!project) {
|
|
@@ -1910,35 +1908,25 @@ const validateExpandedPlanningAgentDraft = (outline, draft) => {
|
|
|
1910
1908
|
return draft;
|
|
1911
1909
|
};
|
|
1912
1910
|
const createPlanningAgentTickets = db.transaction(({ draft, initiativePrompt, projectId, model, variant, skills, tool, worktreeBundleId, }) => {
|
|
1913
|
-
const createdTickets =
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
const createdTicketDefinitions = new Map();
|
|
1917
|
-
for (const ticket of draft.tickets) {
|
|
1918
|
-
const planningContext = {
|
|
1919
|
-
source_kind: 'multi',
|
|
1920
|
-
initiative_prompt: initiativePrompt,
|
|
1921
|
-
related_ticket_ids: [],
|
|
1922
|
-
open_questions: ticket.open_questions,
|
|
1923
|
-
};
|
|
1924
|
-
const createdTicket = createTicketRecord({
|
|
1925
|
-
title: ticket.title,
|
|
1926
|
-
description: ticket.description,
|
|
1911
|
+
const { createdTickets, createdDependencies, ticketIdsByKey } = materializeTicketsWithDependencies({
|
|
1912
|
+
options: {
|
|
1913
|
+
projectId,
|
|
1927
1914
|
model,
|
|
1928
1915
|
variant,
|
|
1929
1916
|
skills,
|
|
1930
1917
|
tool: tool ?? 'opencode',
|
|
1931
|
-
agentStatus: 'done',
|
|
1932
|
-
projectId,
|
|
1933
1918
|
worktreeBundleId,
|
|
1934
|
-
|
|
1935
|
-
}
|
|
1936
|
-
|
|
1937
|
-
|
|
1919
|
+
agentStatus: 'done',
|
|
1920
|
+
},
|
|
1921
|
+
tickets: draft.tickets,
|
|
1922
|
+
dependencies: draft.dependencies,
|
|
1923
|
+
});
|
|
1924
|
+
const createdTicketDefinitions = new Map();
|
|
1925
|
+
for (const ticket of draft.tickets) {
|
|
1926
|
+
const createdId = ticketIdsByKey.get(ticket.key);
|
|
1927
|
+
if (createdId) {
|
|
1928
|
+
createdTicketDefinitions.set(createdId, ticket);
|
|
1938
1929
|
}
|
|
1939
|
-
createdTickets.push(createdTicket);
|
|
1940
|
-
ticketIdsByKey.set(ticket.key, createdTicket.id);
|
|
1941
|
-
createdTicketDefinitions.set(createdTicket.id, ticket);
|
|
1942
1930
|
}
|
|
1943
1931
|
const createdTicketIds = createdTickets.map(ticket => ticket.id);
|
|
1944
1932
|
for (const createdTicket of createdTickets) {
|
|
@@ -1961,72 +1949,28 @@ const createPlanningAgentTickets = db.transaction(({ draft, initiativePrompt, pr
|
|
|
1961
1949
|
});
|
|
1962
1950
|
}
|
|
1963
1951
|
}
|
|
1964
|
-
for (const dependency of draft.dependencies) {
|
|
1965
|
-
const blockerTicketId = ticketIdsByKey.get(dependency.blocker_key);
|
|
1966
|
-
const dependentTicketId = ticketIdsByKey.get(dependency.dependent_key);
|
|
1967
|
-
if (!blockerTicketId || !dependentTicketId) {
|
|
1968
|
-
throw new HttpError(500, 'Planning agent returned a dependency for an unknown generated ticket.');
|
|
1969
|
-
}
|
|
1970
|
-
const validation = validateCreateTicketDependency({
|
|
1971
|
-
blockerTicketId,
|
|
1972
|
-
dependentTicketId,
|
|
1973
|
-
projectId,
|
|
1974
|
-
});
|
|
1975
|
-
if (!validation.ok) {
|
|
1976
|
-
throw new HttpError(validation.status, validation.error);
|
|
1977
|
-
}
|
|
1978
|
-
const createdDependency = createTicketDependency(blockerTicketId, dependentTicketId);
|
|
1979
|
-
if (!createdDependency) {
|
|
1980
|
-
throw new HttpError(500, 'Unable to create the generated ticket dependency.');
|
|
1981
|
-
}
|
|
1982
|
-
createdDependencies.push(createdDependency);
|
|
1983
|
-
}
|
|
1984
1952
|
return { tickets: createdTickets, dependencies: createdDependencies };
|
|
1985
1953
|
});
|
|
1986
1954
|
const createChatProposedTickets = db.transaction(({ project, chatSession, proposals, }) => {
|
|
1987
|
-
const
|
|
1988
|
-
const createdDependencies =
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
const bundle = createAutoBundleForName(proposedTicket.title, project.id);
|
|
1992
|
-
const createdTicket = createTicketRecord({
|
|
1993
|
-
title: proposedTicket.title,
|
|
1994
|
-
description: proposedTicket.description,
|
|
1955
|
+
const bundle = createAutoPlanningBundle(project.id);
|
|
1956
|
+
const { createdTickets, createdDependencies, ticketIdsByKey } = materializeTicketsWithDependencies({
|
|
1957
|
+
options: {
|
|
1958
|
+
projectId: project.id,
|
|
1995
1959
|
model: chatSession.model,
|
|
1996
1960
|
variant: chatSession.variant,
|
|
1997
1961
|
skills: chatSession.skills,
|
|
1998
|
-
tool:
|
|
1999
|
-
projectId: project.id,
|
|
1962
|
+
tool: null,
|
|
2000
1963
|
worktreeBundleId: bundle.id,
|
|
2001
|
-
}
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
ticketIdsByKey.
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
const blockerTicketId = ticketIdsByKey.get(dependency.blocker_key);
|
|
2010
|
-
const dependentTicketId = ticketIdsByKey.get(dependency.dependent_key);
|
|
2011
|
-
if (!blockerTicketId || !dependentTicketId) {
|
|
2012
|
-
throw new HttpError(500, 'Chat ticket proposal returned a dependency for an unknown ticket key.');
|
|
2013
|
-
}
|
|
2014
|
-
const validation = validateCreateTicketDependency({
|
|
2015
|
-
blockerTicketId,
|
|
2016
|
-
dependentTicketId,
|
|
2017
|
-
projectId: project.id,
|
|
2018
|
-
});
|
|
2019
|
-
if (!validation.ok) {
|
|
2020
|
-
throw new HttpError(validation.status, validation.error);
|
|
2021
|
-
}
|
|
2022
|
-
const createdDependency = createTicketDependency(blockerTicketId, dependentTicketId);
|
|
2023
|
-
if (!createdDependency) {
|
|
2024
|
-
throw new HttpError(500, 'Unable to create the proposed ticket dependency.');
|
|
2025
|
-
}
|
|
2026
|
-
createdDependencies.push(createdDependency);
|
|
2027
|
-
}
|
|
1964
|
+
},
|
|
1965
|
+
tickets: proposals.tickets,
|
|
1966
|
+
dependencies: proposals.dependencies,
|
|
1967
|
+
});
|
|
1968
|
+
const createdTicketsWithKeys = createdTickets.map(ticket => {
|
|
1969
|
+
const key = [...ticketIdsByKey.entries()].find(([, id]) => id === ticket.id)?.[0] ?? '';
|
|
1970
|
+
return { key, ticket };
|
|
1971
|
+
});
|
|
2028
1972
|
return {
|
|
2029
|
-
createdTickets,
|
|
1973
|
+
createdTickets: createdTicketsWithKeys,
|
|
2030
1974
|
createdDependencies,
|
|
2031
1975
|
};
|
|
2032
1976
|
});
|
|
@@ -2441,6 +2385,323 @@ app.post('/projects/:id/chat-sessions/:sessionId/messages/:messageId/create-tick
|
|
|
2441
2385
|
return res.status(500).json({ error: getErrorMessage(error) || 'Unable to create tickets from the chat proposal.' });
|
|
2442
2386
|
}
|
|
2443
2387
|
});
|
|
2388
|
+
app.get('/projects/:id/spec-sessions', (req, res) => {
|
|
2389
|
+
const project = getProjectById(req.params.id);
|
|
2390
|
+
if (!project) {
|
|
2391
|
+
return res.status(404).json({ error: 'Not found' });
|
|
2392
|
+
}
|
|
2393
|
+
return res.json(listSpecSessions(project.id));
|
|
2394
|
+
});
|
|
2395
|
+
app.post('/projects/:id/spec-sessions', (req, res) => {
|
|
2396
|
+
const result = createProjectSpecSession({
|
|
2397
|
+
projectId: req.params.id,
|
|
2398
|
+
payload: req.body,
|
|
2399
|
+
broadcaster: io,
|
|
2400
|
+
});
|
|
2401
|
+
if (!result.ok) {
|
|
2402
|
+
return res.status(result.status).json({ error: result.error });
|
|
2403
|
+
}
|
|
2404
|
+
return res.status(result.status).json(result.value);
|
|
2405
|
+
});
|
|
2406
|
+
app.get('/projects/:id/spec-sessions/:sessionId', (req, res) => {
|
|
2407
|
+
const project = getProjectById(req.params.id);
|
|
2408
|
+
if (!project) {
|
|
2409
|
+
return res.status(404).json({ error: 'Not found' });
|
|
2410
|
+
}
|
|
2411
|
+
const specSession = getProjectSpecSession(project.id, req.params.sessionId);
|
|
2412
|
+
if (!specSession) {
|
|
2413
|
+
return res.status(404).json({ error: 'Not found' });
|
|
2414
|
+
}
|
|
2415
|
+
const refreshedSpecSession = refreshSpecSessionUsageSnapshot(specSession.id, { includeMessages: true });
|
|
2416
|
+
if (!refreshedSpecSession || refreshedSpecSession.project_id !== project.id) {
|
|
2417
|
+
return res.json(specSession);
|
|
2418
|
+
}
|
|
2419
|
+
return res.json(refreshedSpecSession);
|
|
2420
|
+
});
|
|
2421
|
+
app.post('/projects/:id/spec-sessions/:sessionId/messages', (req, res) => {
|
|
2422
|
+
const result = submitProjectSpecSessionMessage({
|
|
2423
|
+
projectId: req.params.id,
|
|
2424
|
+
specSessionId: req.params.sessionId,
|
|
2425
|
+
payload: req.body,
|
|
2426
|
+
broadcaster: io,
|
|
2427
|
+
});
|
|
2428
|
+
if (!result.ok) {
|
|
2429
|
+
return res.status(result.status).json({ error: result.error });
|
|
2430
|
+
}
|
|
2431
|
+
return res.status(result.status).json(result.value);
|
|
2432
|
+
});
|
|
2433
|
+
app.post('/projects/:id/spec-sessions/:sessionId/attachments', (req, res) => {
|
|
2434
|
+
const project = getProjectById(req.params.id);
|
|
2435
|
+
if (!project) {
|
|
2436
|
+
return res.status(404).json({ error: 'Not found' });
|
|
2437
|
+
}
|
|
2438
|
+
const specSession = getProjectSpecSession(project.id, req.params.sessionId);
|
|
2439
|
+
if (!specSession) {
|
|
2440
|
+
return res.status(404).json({ error: 'Not found' });
|
|
2441
|
+
}
|
|
2442
|
+
if (specSession.agent_status === 'running') {
|
|
2443
|
+
return res.status(409).json({ error: 'Wait for the current spec response to finish before uploading images.' });
|
|
2444
|
+
}
|
|
2445
|
+
return specAttachmentUpload.array('files', MAX_SPEC_ATTACHMENTS_PER_TURN)(req, res, error => {
|
|
2446
|
+
if (error) {
|
|
2447
|
+
return res.status(400).json({ error: getSpecAttachmentUploadErrorMessage(error) });
|
|
2448
|
+
}
|
|
2449
|
+
const files = (req.files ?? []);
|
|
2450
|
+
if (files.length === 0) {
|
|
2451
|
+
return res.status(400).json({ error: 'At least one image is required.' });
|
|
2452
|
+
}
|
|
2453
|
+
for (const file of files) {
|
|
2454
|
+
const validationError = validateImageAttachment({
|
|
2455
|
+
mimeType: file.mimetype,
|
|
2456
|
+
originalName: file.originalname,
|
|
2457
|
+
});
|
|
2458
|
+
if (validationError) {
|
|
2459
|
+
return res.status(400).json({ error: validationError });
|
|
2460
|
+
}
|
|
2461
|
+
}
|
|
2462
|
+
const createdAttachmentIds = [];
|
|
2463
|
+
try {
|
|
2464
|
+
const attachments = files.map(file => {
|
|
2465
|
+
const attachment = createSpecAttachment({
|
|
2466
|
+
specSessionId: specSession.id,
|
|
2467
|
+
originalName: file.originalname,
|
|
2468
|
+
mimeType: file.mimetype,
|
|
2469
|
+
buffer: file.buffer,
|
|
2470
|
+
projectId: project.id,
|
|
2471
|
+
});
|
|
2472
|
+
createdAttachmentIds.push(attachment.id);
|
|
2473
|
+
return attachment;
|
|
2474
|
+
});
|
|
2475
|
+
return res.status(201).json({ attachments });
|
|
2476
|
+
}
|
|
2477
|
+
catch (uploadError) {
|
|
2478
|
+
for (const attachmentId of createdAttachmentIds) {
|
|
2479
|
+
const attachment = getChatAttachmentById(attachmentId);
|
|
2480
|
+
if (!attachment) {
|
|
2481
|
+
continue;
|
|
2482
|
+
}
|
|
2483
|
+
deleteSpecSessionAttachment(attachment);
|
|
2484
|
+
}
|
|
2485
|
+
return res.status(500).json({ error: uploadError instanceof Error ? uploadError.message : 'Unable to upload the selected images right now.' });
|
|
2486
|
+
}
|
|
2487
|
+
});
|
|
2488
|
+
});
|
|
2489
|
+
app.delete('/projects/:id/spec-sessions/:sessionId/attachments/:attachmentId', (req, res) => {
|
|
2490
|
+
const project = getProjectById(req.params.id);
|
|
2491
|
+
if (!project) {
|
|
2492
|
+
return res.status(404).json({ error: 'Not found' });
|
|
2493
|
+
}
|
|
2494
|
+
const specSession = getProjectSpecSession(project.id, req.params.sessionId);
|
|
2495
|
+
if (!specSession) {
|
|
2496
|
+
return res.status(404).json({ error: 'Not found' });
|
|
2497
|
+
}
|
|
2498
|
+
const attachment = getSpecAttachmentForSession(req.params.attachmentId, specSession.id);
|
|
2499
|
+
if (!attachment) {
|
|
2500
|
+
return res.status(404).json({ error: 'Attachment not found' });
|
|
2501
|
+
}
|
|
2502
|
+
if (getSpecAttachmentLinkedMessageId(attachment.id)) {
|
|
2503
|
+
return res.status(409).json({ error: 'Sent message attachments cannot be removed.' });
|
|
2504
|
+
}
|
|
2505
|
+
deleteSpecSessionAttachment(attachment);
|
|
2506
|
+
return res.json({ ok: true });
|
|
2507
|
+
});
|
|
2508
|
+
app.get('/projects/:id/spec-sessions/:sessionId/attachments/:attachmentId/content', (req, res) => {
|
|
2509
|
+
const project = getProjectById(req.params.id);
|
|
2510
|
+
if (!project) {
|
|
2511
|
+
return res.status(404).json({ error: 'Not found' });
|
|
2512
|
+
}
|
|
2513
|
+
const specSession = getProjectSpecSession(project.id, req.params.sessionId);
|
|
2514
|
+
if (!specSession) {
|
|
2515
|
+
return res.status(404).json({ error: 'Not found' });
|
|
2516
|
+
}
|
|
2517
|
+
const attachment = getSpecAttachmentForSession(req.params.attachmentId, specSession.id);
|
|
2518
|
+
if (!attachment) {
|
|
2519
|
+
return res.status(404).json({ error: 'Attachment not found' });
|
|
2520
|
+
}
|
|
2521
|
+
try {
|
|
2522
|
+
const content = readSpecAttachmentContent(attachment);
|
|
2523
|
+
res.type(attachment.mime_type);
|
|
2524
|
+
return res.send(content);
|
|
2525
|
+
}
|
|
2526
|
+
catch {
|
|
2527
|
+
return res.status(404).json({ error: 'Attachment content not found' });
|
|
2528
|
+
}
|
|
2529
|
+
});
|
|
2530
|
+
app.post('/projects/:id/spec-sessions/:sessionId/stop', async (req, res) => {
|
|
2531
|
+
const result = await stopProjectSpecSessionResponse({
|
|
2532
|
+
projectId: req.params.id,
|
|
2533
|
+
specSessionId: req.params.sessionId,
|
|
2534
|
+
});
|
|
2535
|
+
if (!result.ok) {
|
|
2536
|
+
return res.status(result.status).json({ error: result.error });
|
|
2537
|
+
}
|
|
2538
|
+
return res.status(result.status).json(result.value);
|
|
2539
|
+
});
|
|
2540
|
+
app.delete('/projects/:id/spec-sessions/:sessionId', (req, res) => {
|
|
2541
|
+
const project = getProjectById(req.params.id);
|
|
2542
|
+
if (!project) {
|
|
2543
|
+
return res.status(404).json({ error: 'Not found' });
|
|
2544
|
+
}
|
|
2545
|
+
const specSession = getProjectSpecSession(project.id, req.params.sessionId);
|
|
2546
|
+
if (!specSession) {
|
|
2547
|
+
return res.status(404).json({ error: 'Not found' });
|
|
2548
|
+
}
|
|
2549
|
+
if (specSession.agent_status === 'running') {
|
|
2550
|
+
return res.status(409).json({ error: 'Wait for the current spec response to finish before deleting this session.' });
|
|
2551
|
+
}
|
|
2552
|
+
deleteProjectSpecSession({
|
|
2553
|
+
projectId: project.id,
|
|
2554
|
+
specSessionId: specSession.id,
|
|
2555
|
+
broadcaster: io,
|
|
2556
|
+
});
|
|
2557
|
+
return res.json({ ok: true });
|
|
2558
|
+
});
|
|
2559
|
+
app.get('/projects/:id/spec-sessions/:sessionId/snapshots/:snapshotId', (req, res) => {
|
|
2560
|
+
const project = getProjectById(req.params.id);
|
|
2561
|
+
if (!project) {
|
|
2562
|
+
return res.status(404).json({ error: 'Not found' });
|
|
2563
|
+
}
|
|
2564
|
+
const specSession = getProjectSpecSession(project.id, req.params.sessionId);
|
|
2565
|
+
if (!specSession) {
|
|
2566
|
+
return res.status(404).json({ error: 'Not found' });
|
|
2567
|
+
}
|
|
2568
|
+
const snapshot = getSpecSnapshot(req.params.snapshotId);
|
|
2569
|
+
if (!snapshot) {
|
|
2570
|
+
return res.status(404).json({ error: 'Not found' });
|
|
2571
|
+
}
|
|
2572
|
+
if (snapshot.spec_session_id !== specSession.id) {
|
|
2573
|
+
return res.status(404).json({ error: 'Not found' });
|
|
2574
|
+
}
|
|
2575
|
+
return res.json({
|
|
2576
|
+
...snapshot,
|
|
2577
|
+
created_ticket_ids: getCreatedTicketIdsForSnapshot(snapshot.id, project.id),
|
|
2578
|
+
});
|
|
2579
|
+
});
|
|
2580
|
+
app.post('/projects/:id/spec-sessions/:sessionId/snapshots/:snapshotId/create-tickets', (req, res) => {
|
|
2581
|
+
const project = getProjectById(req.params.id);
|
|
2582
|
+
if (!project) {
|
|
2583
|
+
return res.status(404).json({ error: 'Not found' });
|
|
2584
|
+
}
|
|
2585
|
+
const specSession = getProjectSpecSession(project.id, req.params.sessionId);
|
|
2586
|
+
if (!specSession) {
|
|
2587
|
+
return res.status(404).json({ error: 'Not found' });
|
|
2588
|
+
}
|
|
2589
|
+
const snapshot = getSpecSnapshot(req.params.snapshotId);
|
|
2590
|
+
if (!snapshot || snapshot.spec_session_id !== specSession.id) {
|
|
2591
|
+
return res.status(404).json({ error: 'Not found' });
|
|
2592
|
+
}
|
|
2593
|
+
if (snapshot.tickets.length === 0) {
|
|
2594
|
+
return res.status(409).json({ error: 'This spec snapshot has no proposed tickets to create.' });
|
|
2595
|
+
}
|
|
2596
|
+
const existingTicketIds = getCreatedTicketIdsForSnapshot(snapshot.id, project.id);
|
|
2597
|
+
if (existingTicketIds.length > 0) {
|
|
2598
|
+
return res.status(409).json({
|
|
2599
|
+
error: 'Tickets have already been created from this spec snapshot.',
|
|
2600
|
+
existing_ticket_ids: existingTicketIds,
|
|
2601
|
+
});
|
|
2602
|
+
}
|
|
2603
|
+
const ticketsWithoutTestingGuidance = snapshot.tickets.filter(ticket => !hasTestingGuidance(ticket.description));
|
|
2604
|
+
if (ticketsWithoutTestingGuidance.length > 0) {
|
|
2605
|
+
const ticketKeys = ticketsWithoutTestingGuidance.map(ticket => ticket.key).join(', ');
|
|
2606
|
+
return res.status(422).json({
|
|
2607
|
+
error: `Spec tickets must include testing or verification guidance in their descriptions before they can be created on the board. Missing guidance in: ${ticketKeys}`,
|
|
2608
|
+
});
|
|
2609
|
+
}
|
|
2610
|
+
try {
|
|
2611
|
+
const bundleName = specSession.external_source?.external_key ?? '';
|
|
2612
|
+
const bundle = bundleName
|
|
2613
|
+
? createAutoBundleForName(bundleName, project.id)
|
|
2614
|
+
: createAutoPlanningBundle(project.id);
|
|
2615
|
+
const { createdTickets, createdDependencies } = materializeTicketsWithDependencies({
|
|
2616
|
+
options: {
|
|
2617
|
+
projectId: project.id,
|
|
2618
|
+
model: specSession.model,
|
|
2619
|
+
variant: specSession.variant,
|
|
2620
|
+
skills: specSession.skills,
|
|
2621
|
+
tool: null,
|
|
2622
|
+
worktreeBundleId: bundle.id,
|
|
2623
|
+
originSpecSessionId: specSession.id,
|
|
2624
|
+
originSpecSnapshotId: snapshot.id,
|
|
2625
|
+
},
|
|
2626
|
+
tickets: snapshot.tickets,
|
|
2627
|
+
dependencies: snapshot.dependencies,
|
|
2628
|
+
});
|
|
2629
|
+
for (const createdTicket of createdTickets) {
|
|
2630
|
+
io.emit('ticket:created', createdTicket);
|
|
2631
|
+
}
|
|
2632
|
+
for (const createdDependency of createdDependencies) {
|
|
2633
|
+
io.emit('ticket-dependency:created', createdDependency);
|
|
2634
|
+
}
|
|
2635
|
+
return res.status(201).json({
|
|
2636
|
+
tickets: createdTickets,
|
|
2637
|
+
dependencies: createdDependencies,
|
|
2638
|
+
});
|
|
2639
|
+
}
|
|
2640
|
+
catch (error) {
|
|
2641
|
+
if (error instanceof HttpError) {
|
|
2642
|
+
return res.status(error.status).json({ error: error.message });
|
|
2643
|
+
}
|
|
2644
|
+
return res.status(500).json({ error: getErrorMessage(error) || 'Unable to create tickets from the spec snapshot.' });
|
|
2645
|
+
}
|
|
2646
|
+
});
|
|
2647
|
+
app.post('/projects/:id/spec-sessions/:sessionId/snapshots/:snapshotId/implement-in-chat', (req, res) => {
|
|
2648
|
+
const project = getProjectById(req.params.id);
|
|
2649
|
+
if (!project) {
|
|
2650
|
+
return res.status(404).json({ error: 'Not found' });
|
|
2651
|
+
}
|
|
2652
|
+
const specSession = getProjectSpecSession(project.id, req.params.sessionId);
|
|
2653
|
+
if (!specSession) {
|
|
2654
|
+
return res.status(404).json({ error: 'Not found' });
|
|
2655
|
+
}
|
|
2656
|
+
const snapshot = getSpecSnapshot(req.params.snapshotId);
|
|
2657
|
+
if (!snapshot || snapshot.spec_session_id !== specSession.id) {
|
|
2658
|
+
return res.status(404).json({ error: 'Not found' });
|
|
2659
|
+
}
|
|
2660
|
+
const draftText = buildSpecImplementationDraft({ snapshot, specSession });
|
|
2661
|
+
return res.status(200).json({
|
|
2662
|
+
draft_text: draftText,
|
|
2663
|
+
spec_session_id: specSession.id,
|
|
2664
|
+
spec_snapshot_id: snapshot.id,
|
|
2665
|
+
});
|
|
2666
|
+
});
|
|
2667
|
+
app.post('/projects/:id/spec-sessions/:sessionId/snapshots/:snapshotId/export', (req, res) => {
|
|
2668
|
+
const project = getProjectById(req.params.id);
|
|
2669
|
+
if (!project) {
|
|
2670
|
+
return res.status(404).json({ error: 'Not found' });
|
|
2671
|
+
}
|
|
2672
|
+
const specSession = getProjectSpecSession(project.id, req.params.sessionId);
|
|
2673
|
+
if (!specSession) {
|
|
2674
|
+
return res.status(404).json({ error: 'Not found' });
|
|
2675
|
+
}
|
|
2676
|
+
const snapshot = getSpecSnapshot(req.params.snapshotId);
|
|
2677
|
+
if (!snapshot || snapshot.spec_session_id !== specSession.id) {
|
|
2678
|
+
return res.status(404).json({ error: 'Not found' });
|
|
2679
|
+
}
|
|
2680
|
+
try {
|
|
2681
|
+
const collision = checkSpecExportCollision({
|
|
2682
|
+
specSession,
|
|
2683
|
+
repoPath: project.repo_path,
|
|
2684
|
+
});
|
|
2685
|
+
if (collision) {
|
|
2686
|
+
return res.status(409).json({
|
|
2687
|
+
error: 'A spec with this title has already been exported.',
|
|
2688
|
+
conflicting_path: collision,
|
|
2689
|
+
});
|
|
2690
|
+
}
|
|
2691
|
+
const result = exportSpecToDocs({
|
|
2692
|
+
snapshot,
|
|
2693
|
+
specSession,
|
|
2694
|
+
repoPath: project.repo_path,
|
|
2695
|
+
});
|
|
2696
|
+
return res.status(201).json({
|
|
2697
|
+
path: result.filePath,
|
|
2698
|
+
content: result.content,
|
|
2699
|
+
});
|
|
2700
|
+
}
|
|
2701
|
+
catch (error) {
|
|
2702
|
+
return res.status(500).json({ error: getErrorMessage(error) || 'Unable to export the spec snapshot.' });
|
|
2703
|
+
}
|
|
2704
|
+
});
|
|
2444
2705
|
app.get('/tickets', async (req, res) => {
|
|
2445
2706
|
const requestedProjectId = getRequestedProjectId(req);
|
|
2446
2707
|
const detail = req.query.detail === 'board-enrichment'
|
|
@@ -4635,9 +4896,9 @@ app.post('/tickets', (req, res) => {
|
|
|
4635
4896
|
if (!project) {
|
|
4636
4897
|
return res.status(400).json({ error: 'project_id must reference an existing project' });
|
|
4637
4898
|
}
|
|
4638
|
-
const
|
|
4639
|
-
const model =
|
|
4640
|
-
const variant =
|
|
4899
|
+
const requestTool = resolveChatTool(req.body.tool ?? config.tool);
|
|
4900
|
+
const model = requestTool === 'opencode' && req.body.model != null ? normalizeModelId(req.body.model) || null : null;
|
|
4901
|
+
const variant = requestTool === 'opencode' && typeof req.body.variant === 'string' && req.body.variant.trim()
|
|
4641
4902
|
? req.body.variant.trim()
|
|
4642
4903
|
: null;
|
|
4643
4904
|
const skills = resolveRequestedSkills(req.body.skills);
|
|
@@ -4665,7 +4926,7 @@ app.post('/tickets', (req, res) => {
|
|
|
4665
4926
|
model,
|
|
4666
4927
|
variant,
|
|
4667
4928
|
skills,
|
|
4668
|
-
tool,
|
|
4929
|
+
tool: null,
|
|
4669
4930
|
projectId: project_id,
|
|
4670
4931
|
worktreeBundleId: resolvedBundleId,
|
|
4671
4932
|
externalSource,
|