@straiffi/archon 1.10.7 → 1.10.8

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.
Files changed (98) hide show
  1. package/dist/client/assets/{ActivityView-CZdw9ypU.js → ActivityView-PGEex8sS.js} +1 -1
  2. package/dist/client/assets/{GitHubActionsDialog-C4xENL_U.js → GitHubActionsDialog-CwsZRevc.js} +1 -1
  3. package/dist/client/assets/{VerificationDialog-Bl63STnx.js → VerificationDialog-BYE1M9T2.js} +1 -1
  4. package/dist/client/assets/{architectureDiagram-3BPJPVTR-B-Dbav9P.js → architectureDiagram-3BPJPVTR-CzUKSWJZ.js} +1 -1
  5. package/dist/client/assets/{blockDiagram-GPEHLZMM-V8QgIP4y.js → blockDiagram-GPEHLZMM-DoNm6BnS.js} +1 -1
  6. package/dist/client/assets/{c4Diagram-AAUBKEIU-CK1CFah9.js → c4Diagram-AAUBKEIU-C7ocs3wQ.js} +1 -1
  7. package/dist/client/assets/channel-b-KoJfeL.js +1 -0
  8. package/dist/client/assets/{chunk-2J33WTMH-DebzCgcQ.js → chunk-2J33WTMH-DUmaxCq8.js} +1 -1
  9. package/dist/client/assets/{chunk-3OPIFGDE-iAUuccWS.js → chunk-3OPIFGDE-CX2MPsMQ.js} +1 -1
  10. package/dist/client/assets/{chunk-5ZQYHXKU-DeuVJv0c.js → chunk-5ZQYHXKU-B103kU_y.js} +1 -1
  11. package/dist/client/assets/{chunk-727SXJPM-C6mHzmPL.js → chunk-727SXJPM-BmLJ4h5X.js} +1 -1
  12. package/dist/client/assets/{chunk-AQP2D5EJ-D_qASYNv.js → chunk-AQP2D5EJ-Cnv1b4Vd.js} +1 -1
  13. package/dist/client/assets/{chunk-CSCIHK7Q-rSaGnDPo.js → chunk-CSCIHK7Q-CkIUhpY9.js} +1 -1
  14. package/dist/client/assets/{chunk-KSCS5N6A-CFmDkyBp.js → chunk-KSCS5N6A-wmkCuZ5U.js} +1 -1
  15. package/dist/client/assets/{chunk-L5ZTLDWV-Bxoonz0d.js → chunk-L5ZTLDWV-D9AdL5mv.js} +1 -1
  16. package/dist/client/assets/{chunk-LZXEDZCA-DVwYlvwm.js → chunk-LZXEDZCA-rMyTvR3J.js} +2 -2
  17. package/dist/client/assets/{chunk-ND2GUHAM-CrYlkN7l.js → chunk-ND2GUHAM-19HTozu8.js} +1 -1
  18. package/dist/client/assets/{chunk-NZK2D7GU-B3F5DD0o.js → chunk-NZK2D7GU-DBHgwKcS.js} +1 -1
  19. package/dist/client/assets/{chunk-O5CBEL6O-6Yr7XpqV.js → chunk-O5CBEL6O-4WpSG06s.js} +1 -1
  20. package/dist/client/assets/{chunk-WU5MYG2G-CXpOzbd6.js → chunk-WU5MYG2G-loqCl7ki.js} +1 -1
  21. package/dist/client/assets/classDiagram-4FO5ZUOK-2c5Acffy.js +1 -0
  22. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-ClrW9LEB.js +1 -0
  23. package/dist/client/assets/{dagre-BM42HDAG-B5BJffrd.js → dagre-BM42HDAG-FVpIr0qZ.js} +1 -1
  24. package/dist/client/assets/{diagram-2AECGRRQ-C4DOfy9O.js → diagram-2AECGRRQ-Dgj8FaDO.js} +1 -1
  25. package/dist/client/assets/{diagram-5GNKFQAL-CC0n3BGG.js → diagram-5GNKFQAL-DNpYKz_a.js} +1 -1
  26. package/dist/client/assets/{diagram-KO2AKTUF-CRzcdj4F.js → diagram-KO2AKTUF-N7R7efvN.js} +1 -1
  27. package/dist/client/assets/{diagram-LMA3HP47-DsJtR4ne.js → diagram-LMA3HP47-hoOvd_tG.js} +1 -1
  28. package/dist/client/assets/{diagram-OG6HWLK6-DrY7fZ_a.js → diagram-OG6HWLK6-ze25ZIy6.js} +1 -1
  29. package/dist/client/assets/{erDiagram-TEJ5UH35-qYLRrYyX.js → erDiagram-TEJ5UH35-780zvNNZ.js} +1 -1
  30. package/dist/client/assets/{flowDiagram-I6XJVG4X-DZwnzJns.js → flowDiagram-I6XJVG4X-x4UB_M_4.js} +1 -1
  31. package/dist/client/assets/{ganttDiagram-6RSMTGT7-f4AoXkmX.js → ganttDiagram-6RSMTGT7-B5Wl0y91.js} +1 -1
  32. package/dist/client/assets/{gitGraphDiagram-PVQCEYII-_CHXhrED.js → gitGraphDiagram-PVQCEYII-CZtVHpBH.js} +1 -1
  33. package/dist/client/assets/index-Cy3ZoenG.js +276 -0
  34. package/dist/client/assets/{infoDiagram-5YYISTIA-DPN-bmpB.js → infoDiagram-5YYISTIA-BOzLwsnA.js} +1 -1
  35. package/dist/client/assets/{ishikawaDiagram-YF4QCWOH-D4y7nv2X.js → ishikawaDiagram-YF4QCWOH-ButBhNg6.js} +1 -1
  36. package/dist/client/assets/{journeyDiagram-JHISSGLW-BqU9b2Ls.js → journeyDiagram-JHISSGLW-CO0Db0Gn.js} +1 -1
  37. package/dist/client/assets/{kanban-definition-UN3LZRKU-B3Ff_dpA.js → kanban-definition-UN3LZRKU-B6KT8yln.js} +1 -1
  38. package/dist/client/assets/{line-Jpug3yeg.js → line-Cv-WaOjt.js} +1 -1
  39. package/dist/client/assets/{mermaid-parser.core-BIMhUApg.js → mermaid-parser.core-DgJy4Qlx.js} +1 -1
  40. package/dist/client/assets/{mermaid.core-Bksciss2.js → mermaid.core-YpvipwZo.js} +3 -3
  41. package/dist/client/assets/{mindmap-definition-RKZ34NQL-D4jePVLd.js → mindmap-definition-RKZ34NQL-DGgtT98Y.js} +1 -1
  42. package/dist/client/assets/{pieDiagram-4H26LBE5-DVizXBtA.js → pieDiagram-4H26LBE5-CgqnLewO.js} +1 -1
  43. package/dist/client/assets/{quadrantDiagram-W4KKPZXB-Daakxhvx.js → quadrantDiagram-W4KKPZXB-Ci_0SzdR.js} +1 -1
  44. package/dist/client/assets/{requirementDiagram-4Y6WPE33-CLcCdr6S.js → requirementDiagram-4Y6WPE33-wJl0DP74.js} +1 -1
  45. package/dist/client/assets/{sankeyDiagram-5OEKKPKP-DNrYDsXq.js → sankeyDiagram-5OEKKPKP-CE7aDnzF.js} +1 -1
  46. package/dist/client/assets/{sequenceDiagram-3UESZ5HK-B09pYUTV.js → sequenceDiagram-3UESZ5HK-CW0bF1i6.js} +1 -1
  47. package/dist/client/assets/{stateDiagram-AJRCARHV-B8xFgv9d.js → stateDiagram-AJRCARHV-Ccl0RXsJ.js} +1 -1
  48. package/dist/client/assets/stateDiagram-v2-BHNVJYJU-k65NSsdn.js +1 -0
  49. package/dist/client/assets/{timeline-definition-PNZ67QCA-BheFMJuC.js → timeline-definition-PNZ67QCA-TuF6SUnb.js} +1 -1
  50. package/dist/client/assets/{vennDiagram-CIIHVFJN-wEvykl0Z.js → vennDiagram-CIIHVFJN-D8OvSROI.js} +1 -1
  51. package/dist/client/assets/{wardleyDiagram-YWT4CUSO-GXXHVTTa.js → wardleyDiagram-YWT4CUSO-DXW3R5NJ.js} +1 -1
  52. package/dist/client/assets/{xychartDiagram-2RQKCTM6-BIR4svhm.js → xychartDiagram-2RQKCTM6-qdiUQDW9.js} +1 -1
  53. package/dist/client/index.html +1 -1
  54. package/dist/server/db.js +12 -2
  55. package/dist/server/db.js.map +1 -1
  56. package/dist/server/index.js +69 -2
  57. package/dist/server/index.js.map +1 -1
  58. package/dist/server/lib/agent.js +35 -6
  59. package/dist/server/lib/agent.js.map +1 -1
  60. package/dist/server/lib/agentRuns.js +15 -0
  61. package/dist/server/lib/agentRuns.js.map +1 -1
  62. package/dist/server/lib/automationDeliveryDecision.js +34 -0
  63. package/dist/server/lib/automationDeliveryDecision.js.map +1 -0
  64. package/dist/server/lib/automationProgression.js +5 -2
  65. package/dist/server/lib/automationProgression.js.map +1 -1
  66. package/dist/server/lib/chatOperations.js +140 -1
  67. package/dist/server/lib/chatOperations.js.map +1 -1
  68. package/dist/server/lib/chats.js +22 -2
  69. package/dist/server/lib/chats.js.map +1 -1
  70. package/dist/server/lib/conversationResume.js +152 -0
  71. package/dist/server/lib/conversationResume.js.map +1 -0
  72. package/dist/server/lib/mobileAccess.js +18 -2
  73. package/dist/server/lib/mobileAccess.js.map +1 -1
  74. package/dist/server/lib/reviewRepairCompletion.js +9 -1
  75. package/dist/server/lib/reviewRepairCompletion.js.map +1 -1
  76. package/dist/server/lib/reviewRepairReconciliation.js +8 -13
  77. package/dist/server/lib/reviewRepairReconciliation.js.map +1 -1
  78. package/dist/server/lib/specOperations.js +135 -2
  79. package/dist/server/lib/specOperations.js.map +1 -1
  80. package/dist/server/lib/specs.js +36 -8
  81. package/dist/server/lib/specs.js.map +1 -1
  82. package/dist/server/lib/ticketAutomation.js +27 -4
  83. package/dist/server/lib/ticketAutomation.js.map +1 -1
  84. package/dist/server/lib/tickets.js +26 -12
  85. package/dist/server/lib/tickets.js.map +1 -1
  86. package/dist/server/lib/types.js.map +1 -1
  87. package/dist/server/workers/chat.js +102 -42
  88. package/dist/server/workers/chat.js.map +1 -1
  89. package/dist/server/workers/review.js +14 -26
  90. package/dist/server/workers/review.js.map +1 -1
  91. package/dist/server/workers/specSession.js +199 -92
  92. package/dist/server/workers/specSession.js.map +1 -1
  93. package/package.json +1 -1
  94. package/dist/client/assets/channel-CGJSY1zl.js +0 -1
  95. package/dist/client/assets/classDiagram-4FO5ZUOK-C8R3tolL.js +0 -1
  96. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-BQyANHoJ.js +0 -1
  97. package/dist/client/assets/index-Go0iENKj.js +0 -276
  98. package/dist/client/assets/stateDiagram-v2-BHNVJYJU-CanC5-kF.js +0 -1
@@ -1,9 +1,9 @@
1
- import { formatAgentErrorForTranscript, getAgentErrorTranscriptRuns, registerSpecProcessPlaceholder, runSpecAgent, untrackSpecProcessById } from '../lib/agent.js';
2
- import { createAgentRun, emitAgentRunsUpdated, finalizeAgentRun, getNextAgentRunIndex, terminalizeAgentRunsForExecutionRun } from '../lib/agentRuns.js';
1
+ import { formatAgentErrorForTranscript, getAgentErrorTranscriptRuns, isAgentContinuationRejectedError, registerSpecProcessPlaceholder, runSpecAgent, untrackSpecProcessById } from '../lib/agent.js';
2
+ import { createAgentRun, emitAgentRunsUpdated, finalizeAgentRun, getNextAgentRunIndex, resolveHistoricalProviderSession, setAgentRunContinuationResponse, terminalizeAgentRunsForExecutionRun } from '../lib/agentRuns.js';
3
3
  import { deleteStagedSpecAttachments, linkSpecAttachmentsToMessage, stageSpecAttachmentsForAgent } from '../lib/attachments.js';
4
4
  import { createSpecMessage, listSpecMessages } from '../lib/specMessages.js';
5
5
  import { beginSpecSessionThinkingRun, bindSpecSessionThinkingMessage } from '../lib/thinkingState.js';
6
- import { createSpecSnapshot, deriveSpecSessionTitle, getDefaultSpecSessionTitle, getSpecSession, getSpecSnapshot, resolveSpecModeValue, updateSpecSessionMode, updateSpecSessionRunConfig, updateSpecSessionStatus, updateSpecSessionTitle, updateSpecSessionUsageSnapshot, } from '../lib/specs.js';
6
+ import { createSpecSnapshot, deriveSpecSessionTitle, getDefaultSpecSessionTitle, getSpecSession, getSpecSnapshot, resolveSpecModeValue, updateSpecSessionMode, updateSpecSessionRunConfig, updateSpecSessionResumeSourceRunId, updateSpecSessionStatus, updateSpecSessionTitle, updateSpecSessionUsageSnapshot, } from '../lib/specs.js';
7
7
  import { normalizeClaudeModelId, normalizeModelId } from '../lib/models.js';
8
8
  import { HttpError } from '../lib/httpError.js';
9
9
  import { appendPlanModeQuestionPromptRules, PLAN_MODE_RUNTIME_PROMPT_RULES } from '../lib/planModeQuestions.js';
@@ -13,7 +13,7 @@ import { persistSpecSessionProjectMemorySuggestions } from '../lib/projectMemory
13
13
  import { extractProjectMemorySuggestionsFromOutput, getProjectMemorySuggestionPromptRules, } from '../lib/projectMemorySuggestions.js';
14
14
  import { enqueue, isRunning } from '../lib/queue.js';
15
15
  import { getProjectById } from '../lib/projects.js';
16
- import { addExecutionRunLink, transitionExecutionRun } from '../lib/executionRuns.js';
16
+ import { addExecutionRunLink, appendLifecycleEvent, getExecutionRun, transitionExecutionRun, withExecutionRunTransaction } from '../lib/executionRuns.js';
17
17
  import { TurnStartError } from '../lib/turnExecutionErrors.js';
18
18
  import { buildLinkedResearchContext, prepareLinkedPlanningResearch } from './plan.js';
19
19
  import { extractGrillQuestionFromOutput, extractSpecArtifactFromOutput, projectPlannerOwnedLikelyFiles, resolveCarriedForwardArtifacts, resolveCarriedForwardStructuredFields, stripFileRelationGraphBlocks, stripMermaidBlocks } from '../lib/specArtifact.js';
@@ -23,7 +23,7 @@ import { collectSpecResourceUrls } from '../lib/specResources.js';
23
23
  import { resolveImportedSpecBundleName } from '../lib/specBundleGroups.js';
24
24
  import { resolveSessionExecutionTarget, resolveSessionTargetForExecution } from '../lib/sessionTargets.js';
25
25
  import { resolveWorkloadExecutionEnvironment } from '../lib/workloadExecutionEnvironment.js';
26
- import { assertProviderSessionAuthorityMatches } from '../lib/executionRunAuthority.js';
26
+ import { assertProviderSessionAuthorityMatches, getExecutionRunAuthority } from '../lib/executionRunAuthority.js';
27
27
  import { getModelProvider } from '../lib/models.js';
28
28
  import { unavailableSpecSnapshotRepositoryEvidence } from '../lib/specSnapshotEvidenceSerialization.js';
29
29
  import { SPEC_REPOSITORY_GRAPH_DISABLED_REASON } from '../lib/specGraphFeature.js';
@@ -414,7 +414,7 @@ const emitUpdatedSpecSession = (specSessionId, io, skipTargetResolution = false)
414
414
  io.emit('spec-session:updated', specSession);
415
415
  }
416
416
  };
417
- const failSpecTurn = ({ specSessionId, executionContext, existingTriggerMessageId, providerContinuation, conversationContext, priorSnapshotId, turnMode, mainRunId, error, io, skipTargetResolution = false, }) => {
417
+ const failSpecTurn = ({ specSessionId, executionContext, existingTriggerMessageId, providerContinuation, conversationContext, priorSnapshotId, turnMode, providerSessionId, internalContinuation, mainRunId, error, io, skipTargetResolution = false, }) => {
418
418
  const failureMessage = getErrorMessage(error);
419
419
  untrackSpecProcessById(specSessionId);
420
420
  try {
@@ -427,7 +427,7 @@ const failSpecTurn = ({ specSessionId, executionContext, existingTriggerMessageI
427
427
  // Preserve the top-level execution even if detail finalization fails.
428
428
  }
429
429
  try {
430
- const errorContent = formatAgentErrorForTranscript(error);
430
+ const errorContent = internalContinuation ? null : formatAgentErrorForTranscript(error);
431
431
  if (errorContent) {
432
432
  const errorMessageId = createSpecMessage({
433
433
  specSessionId,
@@ -464,12 +464,19 @@ const failSpecTurn = ({ specSessionId, executionContext, existingTriggerMessageI
464
464
  runId: executionContext.runId,
465
465
  status: 'failed',
466
466
  domainStatus: 'error',
467
- failure: { code: 'spec_turn_failed', message: failureMessage },
468
- eventType: 'spec_turn_failed',
467
+ failure: {
468
+ code: internalContinuation
469
+ ? isAgentContinuationRejectedError(error)
470
+ ? 'resume_provider_continuation_rejected'
471
+ : 'resume_continuation_failed'
472
+ : 'spec_turn_failed',
473
+ message: failureMessage,
474
+ },
475
+ eventType: internalContinuation ? 'resume_failed' : 'spec_turn_failed',
469
476
  }, io);
470
477
  emitAgentRunsUpdated(io, 'spec', specSessionId);
471
478
  };
472
- export const startSpecSessionTurn = ({ specSessionId, instruction, attachments = [], tool, model, variant, skills = [], io, executionContext, existingTriggerMessageId, providerContinuation, conversationContext, priorSnapshotId, turnMode, }) => {
479
+ export const startSpecSessionTurn = ({ specSessionId, instruction, attachments = [], tool, model, variant, skills = [], io, executionContext, existingTriggerMessageId, providerContinuation, conversationContext, priorSnapshotId, turnMode, providerSessionId, internalContinuation, }) => {
473
480
  const specSession = getSpecSession(specSessionId, { includeMessages: true });
474
481
  if (!specSession) {
475
482
  return false;
@@ -488,13 +495,13 @@ export const startSpecSessionTurn = ({ specSessionId, instruction, attachments =
488
495
  if (specSession.agent_status === 'running' || isRunning(jobKey)) {
489
496
  return false;
490
497
  }
491
- const userMessageId = existingTriggerMessageId ?? createSpecMessage({
498
+ const userMessageId = existingTriggerMessageId ?? (internalContinuation ? null : createSpecMessage({
492
499
  specSessionId,
493
500
  role: 'user',
494
501
  kind: 'prompt',
495
502
  content: trimmedInstruction,
496
- });
497
- if (!existingTriggerMessageId) {
503
+ }));
504
+ if (!existingTriggerMessageId && userMessageId) {
498
505
  addExecutionRunLink({
499
506
  projectId: executionContext.projectId,
500
507
  runId: executionContext.runId,
@@ -506,7 +513,7 @@ export const startSpecSessionTurn = ({ specSessionId, instruction, attachments =
506
513
  },
507
514
  });
508
515
  }
509
- if (!existingTriggerMessageId && attachments.length > 0) {
516
+ if (!existingTriggerMessageId && userMessageId && attachments.length > 0) {
510
517
  linkSpecAttachmentsToMessage({
511
518
  specMessageId: userMessageId,
512
519
  attachments,
@@ -520,14 +527,16 @@ export const startSpecSessionTurn = ({ specSessionId, instruction, attachments =
520
527
  : null;
521
528
  const resolvedVariant = resolvedTool === 'opencode' && resolvedModel ? variant ?? null : null;
522
529
  const resolvedSkills = skills;
523
- updateSpecSessionRunConfig({
524
- specSessionId,
525
- tool: resolvedTool,
526
- model: resolvedModel,
527
- variant: resolvedVariant,
528
- skills: resolvedSkills,
529
- });
530
- if (resolvedTool !== 'opencode' && specSession.title === getDefaultSpecSessionTitle()) {
530
+ if (!internalContinuation) {
531
+ updateSpecSessionRunConfig({
532
+ specSessionId,
533
+ tool: resolvedTool,
534
+ model: resolvedModel,
535
+ variant: resolvedVariant,
536
+ skills: resolvedSkills,
537
+ });
538
+ }
539
+ if (!internalContinuation && resolvedTool !== 'opencode' && specSession.title === getDefaultSpecSessionTitle()) {
531
540
  updateSpecSessionTitle(specSessionId, deriveSpecSessionTitle(trimmedInstruction));
532
541
  }
533
542
  beginSpecSessionThinkingRun(specSessionId);
@@ -578,6 +587,12 @@ export const startSpecSessionTurn = ({ specSessionId, instruction, attachments =
578
587
  workspacePath: canonicalTarget.target.workspacePath,
579
588
  workspaceEvidence: canonicalTarget.target.workspaceEvidence,
580
589
  };
590
+ const sourceAuthority = internalContinuation
591
+ ? getExecutionRunAuthority(latestProject.id, internalContinuation.sourceRunId)
592
+ : null;
593
+ if (internalContinuation && !sourceAuthority) {
594
+ throw new Error('The exact Resume execution authority is unavailable.');
595
+ }
581
596
  workloadEnvironment = await resolveWorkloadExecutionEnvironment({
582
597
  project: latestProject,
583
598
  executionRunId: executionContext.runId,
@@ -586,17 +601,26 @@ export const startSpecSessionTurn = ({ specSessionId, instruction, attachments =
586
601
  skills: resolvedSkills,
587
602
  target: executionTarget,
588
603
  workload: 'spec',
604
+ pinnedProvider: sourceAuthority?.effective.provider,
605
+ pinnedEnvironmentId: sourceAuthority?.effective.environment_id ?? undefined,
606
+ pinnedEnvironmentRevision: sourceAuthority?.effective.environment_revision ?? undefined,
607
+ pinnedEnvironmentValidationFingerprint: sourceAuthority?.effective.environment_validation_fingerprint ?? undefined,
589
608
  signal: specCancellation.abortController?.signal,
590
609
  onLeaseAcquired: release => {
591
610
  leasedEnvironmentRelease = release;
592
611
  },
593
612
  });
594
613
  leasedEnvironmentRelease = null;
595
- const resumeSessionId = providerContinuation === 'fresh'
596
- ? null
597
- : resolvedTool === 'claude'
598
- ? latestSession.claude_session_id
599
- : latestSession.opencode_session_id;
614
+ const resumeSessionId = providerSessionId !== undefined
615
+ ? providerSessionId
616
+ : providerContinuation === 'fresh'
617
+ ? null
618
+ : resolvedTool === 'claude'
619
+ ? latestSession.claude_session_id
620
+ : latestSession.opencode_session_id;
621
+ if (internalContinuation && resumeSessionId !== internalContinuation.providerSessionId) {
622
+ throw new Error('The exact Resume provider session is unavailable.');
623
+ }
600
624
  if (resumeSessionId) {
601
625
  assertProviderSessionAuthorityMatches({
602
626
  projectId: latestProject.id,
@@ -648,7 +672,7 @@ export const startSpecSessionTurn = ({ specSessionId, instruction, attachments =
648
672
  currentInstruction: trimmedInstruction,
649
673
  invocation: { parentType: 'spec', parentId: specSessionId, agentRunId: mainRunId },
650
674
  });
651
- const preResponseContext = await raceAgainstSpecCancellation(trimmedInstruction
675
+ const preResponseContext = await raceAgainstSpecCancellation(trimmedInstruction && !internalContinuation
652
676
  ? prepareLinkedPlanningResearch(trimmedInstruction, {
653
677
  projectId: latestProject.id,
654
678
  cwd,
@@ -671,16 +695,18 @@ export const startSpecSessionTurn = ({ specSessionId, instruction, attachments =
671
695
  // to 'stopped' (worker-owned, mirroring how Chat finalizes after a stop).
672
696
  if (specCancellation.stopRequested) {
673
697
  untrackSpecProcessById(specSessionId);
674
- finalizeAgentRun(mainRunId, 'stopped', null);
675
- updateSpecSessionStatus(specSessionId, 'stopped', null, null);
676
- emitUpdatedSpecSession(specSessionId, io);
677
- transitionExecutionRun({
678
- projectId: executionContext.projectId,
679
- runId: executionContext.runId,
680
- status: 'cancelled',
681
- domainStatus: 'stopped',
682
- eventType: 'spec_turn_cancelled',
698
+ withExecutionRunTransaction(transaction => {
699
+ finalizeAgentRun(mainRunId, 'stopped', null);
700
+ updateSpecSessionStatus(specSessionId, 'stopped', null, null);
701
+ transaction.transitionExecutionRun({
702
+ projectId: executionContext.projectId,
703
+ runId: executionContext.runId,
704
+ status: 'cancelled',
705
+ domainStatus: 'stopped',
706
+ eventType: internalContinuation ? 'resume_interrupted' : 'spec_turn_cancelled',
707
+ });
683
708
  }, io);
709
+ emitUpdatedSpecSession(specSessionId, io);
684
710
  emitAgentRunsUpdated(io, 'spec', specSessionId);
685
711
  return;
686
712
  }
@@ -709,30 +735,64 @@ export const startSpecSessionTurn = ({ specSessionId, instruction, attachments =
709
735
  targetBundleName: latestSession.target_branch_snapshot,
710
736
  externalKey: latestSession.external_source?.external_key ?? null,
711
737
  });
712
- const specRunResult = await runSpecAgent({
713
- specSessionId,
714
- tool: resolvedTool,
715
- prompt: specPrompt,
716
- cwd,
717
- files: stagedAgentAttachments.files,
718
- io,
719
- model: resolvedModel,
720
- variant: resolvedVariant,
721
- resumeSessionId,
722
- executionContext,
723
- mainRun: { id: mainRunId, label: 'Main agent', role: 'main', runIndex: mainRunIndex },
724
- executionProvider: workloadEnvironment.provider,
725
- executionTarget,
726
- executionLauncherEnvironment: workloadEnvironment.launcherEnvironment,
727
- executionRuntimeEnvironment: workloadEnvironment.runtimeEnvironment,
728
- });
738
+ if (internalContinuation) {
739
+ appendLifecycleEvent({
740
+ projectId: executionContext.projectId,
741
+ runId: executionContext.runId,
742
+ domainStatus: 'running',
743
+ eventType: 'provider_continuation_requested',
744
+ metadata: { source_run_id: internalContinuation.sourceRunId },
745
+ }, io);
746
+ }
747
+ let specRunResult;
748
+ try {
749
+ specRunResult = await runSpecAgent({
750
+ specSessionId,
751
+ tool: resolvedTool,
752
+ prompt: specPrompt,
753
+ cwd,
754
+ files: stagedAgentAttachments.files,
755
+ io,
756
+ model: resolvedModel,
757
+ variant: resolvedVariant,
758
+ resumeSessionId,
759
+ executionContext,
760
+ mainRun: { id: mainRunId, label: 'Main agent', role: 'main', runIndex: mainRunIndex },
761
+ executionProvider: workloadEnvironment.provider,
762
+ executionTarget,
763
+ executionLauncherEnvironment: workloadEnvironment.launcherEnvironment,
764
+ executionRuntimeEnvironment: workloadEnvironment.runtimeEnvironment,
765
+ });
766
+ if (internalContinuation) {
767
+ appendLifecycleEvent({
768
+ projectId: executionContext.projectId,
769
+ runId: executionContext.runId,
770
+ domainStatus: 'running',
771
+ eventType: 'provider_continuation_resumed',
772
+ }, io);
773
+ }
774
+ }
775
+ catch (error) {
776
+ if (internalContinuation) {
777
+ appendLifecycleEvent({
778
+ projectId: executionContext.projectId,
779
+ runId: executionContext.runId,
780
+ domainStatus: isAgentContinuationRejectedError(error) ? 'continuation_rejected_before_execution' : 'continuation_failed',
781
+ eventType: isAgentContinuationRejectedError(error) ? 'provider_continuation_rejected_before_execution' : 'provider_continuation_unsafe_failure',
782
+ }, io);
783
+ }
784
+ throw error;
785
+ }
729
786
  if (specRunResult.completionStatus === 'stopped') {
730
787
  const interruptedSession = getSpecSession(specSessionId);
731
- const interruptedResponse = stripSpecFileGraph(interruptedSession?.streaming_response ?? '')
732
- || stripSpecFileGraph(specRunResult.finalResponse ?? '')
733
- || stripSpecFileGraph(getTranscriptContentFallback(specRunResult.transcriptRuns))
788
+ const rawInterruptedResponse = specRunResult.finalResponse?.trim()
789
+ ? specRunResult.finalResponse
790
+ : interruptedSession?.streaming_response?.trim()
791
+ ? interruptedSession.streaming_response
792
+ : getTranscriptContentFallback(specRunResult.transcriptRuns);
793
+ const interruptedResponse = stripSpecFileGraph(rawInterruptedResponse ?? '')
734
794
  || '';
735
- if (resolvedTool === 'opencode' && interruptedSession?.title === getDefaultSpecSessionTitle()) {
795
+ if (!internalContinuation && resolvedTool === 'opencode' && interruptedSession?.title === getDefaultSpecSessionTitle()) {
736
796
  updateSpecSessionTitle(specSessionId, deriveSpecSessionTitle(trimmedInstruction));
737
797
  }
738
798
  if (interruptedResponse) {
@@ -759,16 +819,29 @@ export const startSpecSessionTurn = ({ specSessionId, instruction, attachments =
759
819
  }
760
820
  }
761
821
  updateSpecSessionUsageSnapshot(specSessionId, specRunResult.usageSnapshot);
762
- updateSpecSessionStatus(specSessionId, 'stopped', interruptedSession?.agent_log ?? null, null);
763
- emitUpdatedSpecSession(specSessionId, io);
764
- finalizeAgentRun(mainRunId, 'stopped', interruptedResponse || null);
765
- transitionExecutionRun({
766
- projectId: executionContext.projectId,
767
- runId: executionContext.runId,
768
- status: 'cancelled',
769
- domainStatus: 'stopped',
770
- eventType: 'spec_turn_cancelled',
822
+ withExecutionRunTransaction(transaction => {
823
+ updateSpecSessionStatus(specSessionId, 'stopped', interruptedSession?.agent_log ?? null, null);
824
+ setAgentRunContinuationResponse(mainRunId, rawInterruptedResponse ?? '');
825
+ finalizeAgentRun(mainRunId, 'stopped', rawInterruptedResponse || null);
826
+ transaction.transitionExecutionRun({
827
+ projectId: executionContext.projectId,
828
+ runId: executionContext.runId,
829
+ status: 'cancelled',
830
+ domainStatus: 'stopped',
831
+ eventType: internalContinuation ? 'resume_interrupted' : 'spec_turn_cancelled',
832
+ });
833
+ if (getExecutionRun(executionContext.projectId, executionContext.runId)?.initiated_by === 'user') {
834
+ try {
835
+ if (resolveHistoricalProviderSession(executionContext.runId).sessionId) {
836
+ updateSpecSessionResumeSourceRunId(specSessionId, executionContext.runId);
837
+ }
838
+ }
839
+ catch {
840
+ updateSpecSessionResumeSourceRunId(specSessionId, null);
841
+ }
842
+ }
771
843
  }, io);
844
+ emitUpdatedSpecSession(specSessionId, io);
772
845
  emitAgentRunsUpdated(io, 'spec', specSessionId);
773
846
  return;
774
847
  }
@@ -781,7 +854,14 @@ export const startSpecSessionTurn = ({ specSessionId, instruction, attachments =
781
854
  streamingResponse: streamedAssistantMessage,
782
855
  transcriptRuns: specRunResult.transcriptRuns,
783
856
  };
784
- const rawResponse = resolveCompletedAssistantOutput(completedOutputOptions);
857
+ const completedOutput = resolveCompletedAssistantOutput(completedOutputOptions);
858
+ const currentRawResponse = specRunResult.finalResponse?.trim()
859
+ ? specRunResult.finalResponse
860
+ : completedOutput;
861
+ const rawResponse = `${internalContinuation?.priorResponseText ?? ''}${currentRawResponse}`;
862
+ const parsingOutputOptions = internalContinuation
863
+ ? { finalResponse: rawResponse }
864
+ : completedOutputOptions;
785
865
  // Resolve the session mode at the moment the turn completed. NULL/legacy
786
866
  // values normalize to 'standard'. The worker's per-mode output branch is
787
867
  // decided here, not from a re-read after the artifact path, so an
@@ -798,7 +878,8 @@ export const startSpecSessionTurn = ({ specSessionId, instruction, attachments =
798
878
  throw new Error(`Invalid Grill question envelope: ${grillResult.error}`);
799
879
  }
800
880
  if (grillResult.grillQuestion) {
801
- const cleanedGrillText = stripSpecFileGraph(grillResult.cleanedOutput)
881
+ const currentGrillResult = internalContinuation ? extractGrillQuestionFromOutput(currentRawResponse) : grillResult;
882
+ const cleanedGrillText = stripSpecFileGraph(currentGrillResult.error ? '' : currentGrillResult.cleanedOutput)
802
883
  || grillResult.grillQuestion.question
803
884
  || stripSpecFileGraph(streamedAssistantMessage);
804
885
  const grillMessageId = createSpecMessage({
@@ -823,7 +904,7 @@ export const startSpecSessionTurn = ({ specSessionId, instruction, attachments =
823
904
  },
824
905
  });
825
906
  }
826
- if (resolvedTool === 'opencode' && latestCompletedSession?.title === getDefaultSpecSessionTitle()) {
907
+ if (!internalContinuation && resolvedTool === 'opencode' && latestCompletedSession?.title === getDefaultSpecSessionTitle()) {
827
908
  updateSpecSessionTitle(specSessionId, deriveSpecSessionTitle(trimmedInstruction));
828
909
  }
829
910
  updateSpecSessionUsageSnapshot(specSessionId, specRunResult.usageSnapshot);
@@ -835,14 +916,14 @@ export const startSpecSessionTurn = ({ specSessionId, instruction, attachments =
835
916
  runId: executionContext.runId,
836
917
  status: 'completed',
837
918
  domainStatus: 'done',
838
- eventType: 'spec_turn_completed',
919
+ eventType: internalContinuation ? 'resume_completed' : 'spec_turn_completed',
839
920
  }, io);
840
921
  emitAgentRunsUpdated(io, 'spec', specSessionId);
841
922
  return;
842
923
  }
843
924
  }
844
925
  const completedQuestionOutput = sessionMode !== 'grill'
845
- ? resolveCompletedQuestionOutput(completedOutputOptions)
926
+ ? resolveCompletedQuestionOutput(parsingOutputOptions)
846
927
  : null;
847
928
  const askQuestionResult = completedQuestionOutput
848
929
  ? completedQuestionOutput.extraction
@@ -851,7 +932,11 @@ export const startSpecSessionTurn = ({ specSessionId, instruction, attachments =
851
932
  throw new Error(`Invalid question envelope: ${askQuestionResult.error}`);
852
933
  }
853
934
  if (askQuestionResult.questionSet) {
854
- const questionMessage = stripSpecFileGraph(askQuestionResult.cleanedOutput) || formatAskQuestionMessage(askQuestionResult.questionSet);
935
+ const currentQuestionResult = internalContinuation
936
+ ? resolveCompletedQuestionOutput(completedOutputOptions).extraction
937
+ : askQuestionResult;
938
+ const questionMessage = stripSpecFileGraph(currentQuestionResult.error ? '' : currentQuestionResult.cleanedOutput)
939
+ || formatAskQuestionMessage(askQuestionResult.questionSet);
855
940
  const messageId = createSpecMessage({
856
941
  specSessionId,
857
942
  role: 'assistant',
@@ -872,7 +957,7 @@ export const startSpecSessionTurn = ({ specSessionId, instruction, attachments =
872
957
  expectedParent: { entityType: 'spec_session', entityId: specSessionId },
873
958
  },
874
959
  });
875
- if (resolvedTool === 'opencode' && latestCompletedSession?.title === getDefaultSpecSessionTitle()) {
960
+ if (!internalContinuation && resolvedTool === 'opencode' && latestCompletedSession?.title === getDefaultSpecSessionTitle()) {
876
961
  updateSpecSessionTitle(specSessionId, deriveSpecSessionTitle(trimmedInstruction));
877
962
  }
878
963
  updateSpecSessionUsageSnapshot(specSessionId, specRunResult.usageSnapshot);
@@ -883,7 +968,7 @@ export const startSpecSessionTurn = ({ specSessionId, instruction, attachments =
883
968
  runId: executionContext.runId,
884
969
  status: 'completed',
885
970
  domainStatus: 'done',
886
- eventType: 'spec_turn_completed',
971
+ eventType: internalContinuation ? 'resume_completed' : 'spec_turn_completed',
887
972
  }, io);
888
973
  emitUpdatedSpecSession(specSessionId, io);
889
974
  emitAgentRunsUpdated(io, 'spec', specSessionId);
@@ -917,7 +1002,17 @@ export const startSpecSessionTurn = ({ specSessionId, instruction, attachments =
917
1002
  const cleanPlannerResponse = (value) => stripSpecFileGraph(stripMermaidBlocks(value));
918
1003
  const cleanedPlanText = cleanPlannerResponse(parsedResponse.cleanedOutput)
919
1004
  || cleanPlannerResponse(streamedAssistantMessage);
920
- let assistantMessage = cleanedPlanText
1005
+ const currentQuestionResult = internalContinuation
1006
+ ? resolveCompletedQuestionOutput(completedOutputOptions).extraction
1007
+ : askQuestionResult;
1008
+ const currentArtifactResult = internalContinuation
1009
+ ? extractSpecArtifactFromOutput(currentQuestionResult.error ? currentRawResponse : currentQuestionResult.cleanedOutput)
1010
+ : artifactResult;
1011
+ const currentParsedResponse = internalContinuation
1012
+ ? extractProjectMemorySuggestionsFromOutput(currentArtifactResult.cleanedOutput)
1013
+ : parsedResponse;
1014
+ let assistantMessage = cleanPlannerResponse(currentParsedResponse.cleanedOutput)
1015
+ || (internalContinuation ? cleanPlannerResponse(currentRawResponse) : '')
921
1016
  || stripSpecFileGraph(getTranscriptContentFallback(specRunResult.transcriptRuns))
922
1017
  || (attachments.length > 0 ? ATTACHMENT_ONLY_EMPTY_RESPONSE_FALLBACK : '');
923
1018
  let artifactValidationError = artifactResult.error;
@@ -1030,7 +1125,7 @@ export const startSpecSessionTurn = ({ specSessionId, instruction, attachments =
1030
1125
  });
1031
1126
  }
1032
1127
  }
1033
- if (resolvedTool === 'opencode' && latestCompletedSession?.title === getDefaultSpecSessionTitle()) {
1128
+ if (!internalContinuation && resolvedTool === 'opencode' && latestCompletedSession?.title === getDefaultSpecSessionTitle()) {
1034
1129
  updateSpecSessionTitle(specSessionId, deriveSpecSessionTitle(trimmedInstruction));
1035
1130
  }
1036
1131
  updateSpecSessionUsageSnapshot(specSessionId, specRunResult.usageSnapshot);
@@ -1045,33 +1140,45 @@ export const startSpecSessionTurn = ({ specSessionId, instruction, attachments =
1045
1140
  runId: executionContext.runId,
1046
1141
  status: 'completed',
1047
1142
  domainStatus: 'done',
1048
- eventType: 'spec_turn_completed',
1143
+ eventType: internalContinuation ? 'resume_completed' : 'spec_turn_completed',
1049
1144
  }, io);
1050
1145
  emitAgentRunsUpdated(io, 'spec', specSessionId);
1051
1146
  }
1052
1147
  catch (error) {
1053
1148
  if (specCancellation.stopRequested) {
1054
1149
  untrackSpecProcessById(specSessionId);
1055
- if (mainRunId) {
1056
- finalizeAgentRun(mainRunId, 'stopped', null);
1057
- }
1058
- updateSpecSessionStatus(specSessionId, 'stopped', null, null);
1059
- emitUpdatedSpecSession(specSessionId, io);
1060
- transitionExecutionRun({
1061
- projectId: executionContext.projectId,
1062
- runId: executionContext.runId,
1063
- status: 'cancelled',
1064
- domainStatus: 'stopped',
1065
- eventType: 'spec_turn_cancelled',
1150
+ withExecutionRunTransaction(transaction => {
1151
+ if (mainRunId) {
1152
+ finalizeAgentRun(mainRunId, 'stopped', null);
1153
+ }
1154
+ updateSpecSessionStatus(specSessionId, 'stopped', null, null);
1155
+ transaction.transitionExecutionRun({
1156
+ projectId: executionContext.projectId,
1157
+ runId: executionContext.runId,
1158
+ status: 'cancelled',
1159
+ domainStatus: 'stopped',
1160
+ eventType: internalContinuation ? 'resume_interrupted' : 'spec_turn_cancelled',
1161
+ });
1162
+ if (getExecutionRun(executionContext.projectId, executionContext.runId)?.initiated_by === 'user') {
1163
+ try {
1164
+ if (resolveHistoricalProviderSession(executionContext.runId).sessionId) {
1165
+ updateSpecSessionResumeSourceRunId(specSessionId, executionContext.runId);
1166
+ }
1167
+ }
1168
+ catch {
1169
+ updateSpecSessionResumeSourceRunId(specSessionId, null);
1170
+ }
1171
+ }
1066
1172
  }, io);
1173
+ emitUpdatedSpecSession(specSessionId, io);
1067
1174
  emitAgentRunsUpdated(io, 'spec', specSessionId);
1068
1175
  return;
1069
1176
  }
1070
1177
  const latestSession = getSpecSession(specSessionId, hasValidatedTarget ? {} : { skipTargetResolution: true });
1071
- if (resolvedTool === 'opencode' && latestSession?.title === getDefaultSpecSessionTitle()) {
1178
+ if (!internalContinuation && resolvedTool === 'opencode' && latestSession?.title === getDefaultSpecSessionTitle()) {
1072
1179
  updateSpecSessionTitle(specSessionId, deriveSpecSessionTitle(trimmedInstruction));
1073
1180
  }
1074
- failSpecTurn({ specSessionId, executionContext, mainRunId, error, io, skipTargetResolution: !hasValidatedTarget });
1181
+ failSpecTurn({ specSessionId, executionContext, mainRunId, error, io, skipTargetResolution: !hasValidatedTarget, internalContinuation });
1075
1182
  }
1076
1183
  finally {
1077
1184
  try {
@@ -1097,7 +1204,7 @@ export const startSpecSessionTurn = ({ specSessionId, instruction, attachments =
1097
1204
  }
1098
1205
  queuedJob.catch(error => {
1099
1206
  try {
1100
- failSpecTurn({ specSessionId, executionContext, mainRunId: null, error, io });
1207
+ failSpecTurn({ specSessionId, executionContext, mainRunId: null, error, io, internalContinuation });
1101
1208
  }
1102
1209
  catch {
1103
1210
  // Startup reconciliation remains the final repair boundary for database failures.