@straiffi/archon 1.8.13 → 1.8.15
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-DD1Fom-k.js → ActivityView-DpY9GZez.js} +1 -1
- package/dist/client/assets/{GitHubActionsDialog-aDf_6y6m.js → GitHubActionsDialog-BCr-hjvp.js} +1 -1
- package/dist/client/assets/{RepositoryGraphDialog-B4Ij2xmA.js → RepositoryGraphDialog-_hm-x1Dx.js} +1 -1
- package/dist/client/assets/{VerificationDialog-BBCRKTdr.js → VerificationDialog-CsjDzpE1.js} +1 -1
- package/dist/client/assets/{architectureDiagram-3BPJPVTR-DW3kiEe6.js → architectureDiagram-3BPJPVTR-Dx7MJofM.js} +1 -1
- package/dist/client/assets/{blockDiagram-GPEHLZMM-BVWrspTj.js → blockDiagram-GPEHLZMM-CSSJR8F_.js} +1 -1
- package/dist/client/assets/{c4Diagram-AAUBKEIU-CDqXjYR1.js → c4Diagram-AAUBKEIU-C4F9969r.js} +1 -1
- package/dist/client/assets/channel-DVkhJzgx.js +1 -0
- package/dist/client/assets/{chunk-2J33WTMH-Db4bg8nj.js → chunk-2J33WTMH-23HW6i-y.js} +1 -1
- package/dist/client/assets/{chunk-3OPIFGDE-DqziKs5m.js → chunk-3OPIFGDE-C3ZmRDMb.js} +1 -1
- package/dist/client/assets/{chunk-5ZQYHXKU-wdN7gQNo.js → chunk-5ZQYHXKU-B_-UQvCg.js} +1 -1
- package/dist/client/assets/{chunk-727SXJPM-Dj3aKc06.js → chunk-727SXJPM-5Dua6u1B.js} +1 -1
- package/dist/client/assets/{chunk-AQP2D5EJ-CAlE_C_J.js → chunk-AQP2D5EJ-DcpcaFRO.js} +1 -1
- package/dist/client/assets/{chunk-CSCIHK7Q-KstW8JRv.js → chunk-CSCIHK7Q-CLCpOw52.js} +1 -1
- package/dist/client/assets/{chunk-KSCS5N6A-DrY-9bi9.js → chunk-KSCS5N6A-uOZFe2JI.js} +1 -1
- package/dist/client/assets/{chunk-L5ZTLDWV-C6fw_Dab.js → chunk-L5ZTLDWV-DNnzSNoX.js} +1 -1
- package/dist/client/assets/{chunk-LZXEDZCA-DaZP5hZq.js → chunk-LZXEDZCA-CSA3Xnoh.js} +2 -2
- package/dist/client/assets/{chunk-ND2GUHAM-ChYXM7G3.js → chunk-ND2GUHAM-BQR5rD7w.js} +1 -1
- package/dist/client/assets/{chunk-NZK2D7GU-JajrDJgw.js → chunk-NZK2D7GU-DyIwcQYQ.js} +1 -1
- package/dist/client/assets/{chunk-O5CBEL6O-C4HoCf_q.js → chunk-O5CBEL6O-DfW60w5V.js} +1 -1
- package/dist/client/assets/{chunk-WU5MYG2G-BdjAiDOI.js → chunk-WU5MYG2G-CzMMPYdi.js} +1 -1
- package/dist/client/assets/classDiagram-4FO5ZUOK-6oZ93s5p.js +1 -0
- package/dist/client/assets/classDiagram-v2-Q7XG4LA2-DYO_YnkU.js +1 -0
- package/dist/client/assets/{dagre-BM42HDAG-CpXupRCy.js → dagre-BM42HDAG-Ba7Pz-ZI.js} +1 -1
- package/dist/client/assets/{diagram-2AECGRRQ-DyopuXBv.js → diagram-2AECGRRQ-BxYfgpZ6.js} +1 -1
- package/dist/client/assets/{diagram-5GNKFQAL-C8lS8Vro.js → diagram-5GNKFQAL-Bc4-O-UJ.js} +1 -1
- package/dist/client/assets/{diagram-KO2AKTUF-CsyC_xgK.js → diagram-KO2AKTUF-Ds1Lmrzm.js} +1 -1
- package/dist/client/assets/{diagram-LMA3HP47-DWsQdA71.js → diagram-LMA3HP47-VFQabxKS.js} +1 -1
- package/dist/client/assets/{diagram-OG6HWLK6-YkNa_Tt6.js → diagram-OG6HWLK6-Baff59Zc.js} +1 -1
- package/dist/client/assets/{erDiagram-TEJ5UH35-VmMz5G_L.js → erDiagram-TEJ5UH35-Da5zHjOK.js} +1 -1
- package/dist/client/assets/{flowDiagram-I6XJVG4X-DZAu6IJl.js → flowDiagram-I6XJVG4X-pdGMAuog.js} +1 -1
- package/dist/client/assets/{ganttDiagram-6RSMTGT7-CHJRz_2M.js → ganttDiagram-6RSMTGT7-DjJGX0nm.js} +1 -1
- package/dist/client/assets/{gitGraphDiagram-PVQCEYII-BfA1qYTR.js → gitGraphDiagram-PVQCEYII-kErlwtcT.js} +1 -1
- package/dist/client/assets/index-CS4TzG41.css +2 -0
- package/dist/client/assets/{index-B4x8ZZ8N.js → index-D5TEfELZ.js} +107 -107
- package/dist/client/assets/{infoDiagram-5YYISTIA-CyqqEYvx.js → infoDiagram-5YYISTIA-Bx78HhDi.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-YF4QCWOH-DnXMvxPm.js → ishikawaDiagram-YF4QCWOH-BP0YQCMl.js} +1 -1
- package/dist/client/assets/{journeyDiagram-JHISSGLW-DBumlehf.js → journeyDiagram-JHISSGLW-DUgrCOuD.js} +1 -1
- package/dist/client/assets/{kanban-definition-UN3LZRKU-D-QvfQhh.js → kanban-definition-UN3LZRKU-K0Zc5_M8.js} +1 -1
- package/dist/client/assets/{line-BgwSCyaP.js → line-Dtg-krjk.js} +1 -1
- package/dist/client/assets/{mermaid-parser.core-qDA1puIK.js → mermaid-parser.core-Dw5BI77W.js} +1 -1
- package/dist/client/assets/{mermaid.core-CFM-fuJi.js → mermaid.core-BE-qlWNj.js} +3 -3
- package/dist/client/assets/{mindmap-definition-RKZ34NQL-B93RAITk.js → mindmap-definition-RKZ34NQL-1Piz4H9I.js} +1 -1
- package/dist/client/assets/{pieDiagram-4H26LBE5-C9arDpCE.js → pieDiagram-4H26LBE5-zlWdJ7lL.js} +1 -1
- package/dist/client/assets/{quadrantDiagram-W4KKPZXB-Tma_RsUu.js → quadrantDiagram-W4KKPZXB-DRQY8Adp.js} +1 -1
- package/dist/client/assets/{requirementDiagram-4Y6WPE33-C6GCxpqT.js → requirementDiagram-4Y6WPE33-B8_GJFxl.js} +1 -1
- package/dist/client/assets/{sankeyDiagram-5OEKKPKP-CY8ukFyG.js → sankeyDiagram-5OEKKPKP-BaI0Njo8.js} +1 -1
- package/dist/client/assets/{sequenceDiagram-3UESZ5HK-B6MbhJm7.js → sequenceDiagram-3UESZ5HK-DjqAXbRF.js} +1 -1
- package/dist/client/assets/{stateDiagram-AJRCARHV-Bjy1dBVk.js → stateDiagram-AJRCARHV-Ovr_3LeO.js} +1 -1
- package/dist/client/assets/stateDiagram-v2-BHNVJYJU-9pmeAorV.js +1 -0
- package/dist/client/assets/{timeline-definition-PNZ67QCA-KrbaHcGf.js → timeline-definition-PNZ67QCA-DkczYKeZ.js} +1 -1
- package/dist/client/assets/{vennDiagram-CIIHVFJN-DQ-5gbga.js → vennDiagram-CIIHVFJN-BZyo4ESi.js} +1 -1
- package/dist/client/assets/{wardleyDiagram-YWT4CUSO-Cwpu-hvf.js → wardleyDiagram-YWT4CUSO-BEISrECX.js} +1 -1
- package/dist/client/assets/{xychartDiagram-2RQKCTM6-DoP4HxQE.js → xychartDiagram-2RQKCTM6-CyNzGK7f.js} +1 -1
- package/dist/client/index.html +2 -2
- package/dist/server/db.js +2 -0
- package/dist/server/db.js.map +1 -1
- package/dist/server/index.js +37 -47
- package/dist/server/index.js.map +1 -1
- package/dist/server/lib/agent.js +231 -181
- package/dist/server/lib/agent.js.map +1 -1
- package/dist/server/lib/agentProvider.js +74 -0
- package/dist/server/lib/agentProvider.js.map +1 -0
- package/dist/server/lib/agentRuns.js +21 -9
- package/dist/server/lib/agentRuns.js.map +1 -1
- package/dist/server/lib/automationProgression.js +6 -2
- package/dist/server/lib/automationProgression.js.map +1 -1
- package/dist/server/lib/automationResumeDecision.js +4 -3
- package/dist/server/lib/automationResumeDecision.js.map +1 -1
- package/dist/server/lib/chatOperations.js +30 -1
- package/dist/server/lib/chatOperations.js.map +1 -1
- package/dist/server/lib/claudeAgentProvider.js +48 -0
- package/dist/server/lib/claudeAgentProvider.js.map +1 -0
- package/dist/server/lib/executionEnvironment.js +20 -0
- package/dist/server/lib/executionEnvironment.js.map +1 -0
- package/dist/server/lib/git.js +32 -1
- package/dist/server/lib/git.js.map +1 -1
- package/dist/server/lib/hostExecutionEnvironment.js +77 -0
- package/dist/server/lib/hostExecutionEnvironment.js.map +1 -0
- package/dist/server/lib/lightweightPrompt.js +18 -18
- package/dist/server/lib/lightweightPrompt.js.map +1 -1
- package/dist/server/lib/opencodeAgentProvider.js +139 -0
- package/dist/server/lib/opencodeAgentProvider.js.map +1 -0
- package/dist/server/lib/opencodeExecutionReliability.js +52 -0
- package/dist/server/lib/opencodeExecutionReliability.js.map +1 -0
- package/dist/server/lib/opencodeSessionOperations.js +358 -0
- package/dist/server/lib/opencodeSessionOperations.js.map +1 -0
- package/dist/server/lib/opencodeTaskSessions.js +13 -225
- package/dist/server/lib/opencodeTaskSessions.js.map +1 -1
- package/dist/server/lib/projectAutoConfig.js +23 -19
- package/dist/server/lib/projectAutoConfig.js.map +1 -1
- package/dist/server/lib/reviewFindings.js +15 -0
- package/dist/server/lib/reviewFindings.js.map +1 -1
- package/dist/server/lib/specArtifact.js +19 -0
- package/dist/server/lib/specArtifact.js.map +1 -1
- package/dist/server/lib/specBundleGroups.js +151 -0
- package/dist/server/lib/specBundleGroups.js.map +1 -0
- package/dist/server/lib/specExport.js +1 -0
- package/dist/server/lib/specExport.js.map +1 -1
- package/dist/server/lib/specFormatting.js +13 -0
- package/dist/server/lib/specFormatting.js.map +1 -1
- package/dist/server/lib/specReviewValidation.js +22 -1
- package/dist/server/lib/specReviewValidation.js.map +1 -1
- package/dist/server/lib/specReviews.js +1 -1
- package/dist/server/lib/specSections.js +8 -1
- package/dist/server/lib/specSections.js.map +1 -1
- package/dist/server/lib/specTicketMaterialization.js +119 -0
- package/dist/server/lib/specTicketMaterialization.js.map +1 -0
- package/dist/server/lib/specs.js +27 -5
- package/dist/server/lib/specs.js.map +1 -1
- package/dist/server/lib/types.js.map +1 -1
- package/dist/server/workers/followUp.js +35 -10
- package/dist/server/workers/followUp.js.map +1 -1
- package/dist/server/workers/plan.js +28 -23
- package/dist/server/workers/plan.js.map +1 -1
- package/dist/server/workers/review.js +62 -24
- package/dist/server/workers/review.js.map +1 -1
- package/dist/server/workers/specReview.js +7 -1
- package/dist/server/workers/specReview.js.map +1 -1
- package/dist/server/workers/specSession.js +68 -36
- package/dist/server/workers/specSession.js.map +1 -1
- package/package.json +1 -1
- package/dist/client/assets/channel-C14KP1Bm.js +0 -1
- package/dist/client/assets/classDiagram-4FO5ZUOK-CR20Gg9e.js +0 -1
- package/dist/client/assets/classDiagram-v2-Q7XG4LA2-B-N-i15d.js +0 -1
- package/dist/client/assets/index-CCQwWUKF.css +0 -2
- package/dist/client/assets/stateDiagram-v2-BHNVJYJU-asf2fsPC.js +0 -1
package/dist/server/lib/agent.js
CHANGED
|
@@ -2,73 +2,30 @@ import { spawn } from 'child_process';
|
|
|
2
2
|
import { randomUUID } from 'crypto';
|
|
3
3
|
import { readFileSync } from 'fs';
|
|
4
4
|
import { extname, isAbsolute, join, relative, resolve, sep } from 'path';
|
|
5
|
-
import Database from 'better-sqlite3';
|
|
6
5
|
import db from '../db.js';
|
|
7
6
|
import { getChatSession, updateChatSessionUsageSnapshot } from './chats.js';
|
|
8
7
|
import { normalizeClaudeModelId, normalizeModelId } from './models.js';
|
|
9
8
|
import { createOpencodeTaskSessionReader, parseOpencodeTaskEnvelope } from './opencodeTaskSessions.js';
|
|
9
|
+
import { hostOpencodeSessionOperations } from './opencodeSessionOperations.js';
|
|
10
|
+
import { hostExecutionEnvironment } from './hostExecutionEnvironment.js';
|
|
11
|
+
import { markRetryableOpenCodeDatabaseLock, RetryableOpenCodeDatabaseLockError, retryOpenCodeDatabaseLocks } from './opencodeExecutionReliability.js';
|
|
12
|
+
import { claudeAgentProvider } from './claudeAgentProvider.js';
|
|
13
|
+
import { buildOpencodeSpecReviewInvocation, createOpencodeExitError, createOpencodeRecoveryTracker, interpretOpencodeRecoveryEvent, isSuccessfulOpencodeExit, opencodeAgentProvider, } from './opencodeAgentProvider.js';
|
|
10
14
|
import { appendClaudeHeadlessPlanPrompt, buildClaudeHeadlessPlanArgs } from './planModeQuestions.js';
|
|
11
15
|
import { formatGenericToolCall } from './genericToolCall.js';
|
|
12
16
|
import { getSpecSession, updateSpecSessionResumeId, updateSpecSessionStatus, updateSpecSessionTitle, updateSpecSessionUsageSnapshot } from './specs.js';
|
|
13
17
|
import { updateSpecReviewRunStatus, updateSpecReviewRunStreaming, updateSpecReviewRunUsage } from './specReviews.js';
|
|
14
18
|
import { createAgentRun, emitAgentRunsUpdated, finalizeAgentRun, getNextAgentRunIndex, listAgentRunsForExecutionRun, listAgentRunsForParent, updateAgentRunLabel, updateAgentRunSessionId, updateAgentRunStatus } from './agentRuns.js';
|
|
15
|
-
import { resolveOpencodeDbPath } from './runtimePaths.js';
|
|
16
19
|
import { markTicketDescriptionVisibleInBundle } from './ticketMessages.js';
|
|
17
20
|
import { getTicket } from './tickets.js';
|
|
18
21
|
import { createTranscriptRunSnapshots } from './transcriptRuns.js';
|
|
19
22
|
import { createWorkspaceProcessEnvironment } from './workspaceProcessEnvironment.js';
|
|
20
|
-
import { OpencodeModelResolutionError,
|
|
23
|
+
import { OpencodeModelResolutionError, parseOpencodeStderrModelResolutionError, } from './opencodeErrors.js';
|
|
21
24
|
import { extractLeadingOpencodeJsonObjects } from './opencodePromptOutput.js';
|
|
22
25
|
import { assertAgentSpawnPermit, AgentSpawnPermitRevokedError } from './agentSpawnPermits.js';
|
|
23
26
|
const withTranscriptRuns = (value, transcriptRuns) => {
|
|
24
27
|
return transcriptRuns ? { ...value, transcriptRuns } : value;
|
|
25
28
|
};
|
|
26
|
-
const createOpencodeRecoveryTracker = () => {
|
|
27
|
-
let sawError = false;
|
|
28
|
-
let latestEvent = null;
|
|
29
|
-
let recoveredFinalAnswerFragments = [];
|
|
30
|
-
let latestError = null;
|
|
31
|
-
let modelResolutionError = null;
|
|
32
|
-
return {
|
|
33
|
-
consume: events => {
|
|
34
|
-
let shouldResetResponse = false;
|
|
35
|
-
for (const event of events ?? []) {
|
|
36
|
-
latestEvent = event.type;
|
|
37
|
-
if (event.type === 'error') {
|
|
38
|
-
sawError = true;
|
|
39
|
-
latestError = event.error;
|
|
40
|
-
if (event.error?.isModelResolutionError) {
|
|
41
|
-
modelResolutionError = event.error;
|
|
42
|
-
}
|
|
43
|
-
recoveredFinalAnswerFragments = [];
|
|
44
|
-
shouldResetResponse = true;
|
|
45
|
-
continue;
|
|
46
|
-
}
|
|
47
|
-
if (sawError) {
|
|
48
|
-
recoveredFinalAnswerFragments.push(event.text);
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
return shouldResetResponse;
|
|
52
|
-
},
|
|
53
|
-
hasRecoveredFinalAnswer: () => (sawError
|
|
54
|
-
&& latestEvent === 'final_answer'
|
|
55
|
-
&& recoveredFinalAnswerFragments.length > 0),
|
|
56
|
-
hasUnrecoveredError: () => latestEvent === 'error',
|
|
57
|
-
getModelResolutionError: () => modelResolutionError,
|
|
58
|
-
getUnrecoveredError: () => latestEvent === 'error' ? latestError : null,
|
|
59
|
-
getRecoveredResponse: () => recoveredFinalAnswerFragments.join('\n\n'),
|
|
60
|
-
};
|
|
61
|
-
};
|
|
62
|
-
const isSuccessfulOpencodeExit = (code, stderrModelError, recoveryTracker) => (!stderrModelError
|
|
63
|
-
&& !recoveryTracker.getModelResolutionError()
|
|
64
|
-
&& !recoveryTracker.hasUnrecoveredError()
|
|
65
|
-
&& (code === 0 || recoveryTracker.hasRecoveredFinalAnswer()));
|
|
66
|
-
const createOpencodeExitError = (message, stderrModelError, recoveryTracker) => {
|
|
67
|
-
const structuredError = recoveryTracker.getModelResolutionError() ?? recoveryTracker.getUnrecoveredError();
|
|
68
|
-
return structuredError?.isModelResolutionError || stderrModelError
|
|
69
|
-
? new OpencodeModelResolutionError(message)
|
|
70
|
-
: new Error(message);
|
|
71
|
-
};
|
|
72
29
|
export class AgentContinuationRejectedError extends Error {
|
|
73
30
|
constructor(message) {
|
|
74
31
|
super(message);
|
|
@@ -91,9 +48,9 @@ export const getAgentErrorTranscriptRuns = (error) => {
|
|
|
91
48
|
const transcriptRuns = error.transcriptRuns;
|
|
92
49
|
return Array.isArray(transcriptRuns) && transcriptRuns.length > 0 ? transcriptRuns : null;
|
|
93
50
|
};
|
|
94
|
-
const OPENCODE_PLACEHOLDER_TITLE_PATTERN = /^New session\b/i;
|
|
95
51
|
const OPENCODE_SESSION_METADATA_LOOKUP_DELAY_MS = 1500;
|
|
96
52
|
const AGENT_PROGRESS_UPDATE_INTERVAL_MS = 100;
|
|
53
|
+
const OPENCODE_SUBAGENT_POLL_INTERVAL_MS = 750;
|
|
97
54
|
const resolveAgentModel = (tool, model) => {
|
|
98
55
|
if (tool === 'claude') {
|
|
99
56
|
return normalizeClaudeModelId(model) || null;
|
|
@@ -157,13 +114,6 @@ const buildClaudeArgs = (mode, model, resumeSessionId, sessionId) => {
|
|
|
157
114
|
sessionId,
|
|
158
115
|
];
|
|
159
116
|
};
|
|
160
|
-
const OPENCODE_SESSION_BY_ID_QUERY = 'SELECT id, title FROM session WHERE id = ? LIMIT 1';
|
|
161
|
-
const OPENCODE_SESSION_BY_CREATED_AT_QUERY = 'SELECT id, title FROM session WHERE time_created > ? ORDER BY time_created DESC LIMIT 1';
|
|
162
|
-
const OPENCODE_COMPLETED_ASSISTANT_MESSAGE_USAGE_QUERY = "SELECT data FROM message WHERE session_id = ? AND json_extract(data, '$.role') = 'assistant' AND json_extract(data, '$.time.completed') IS NOT NULL ORDER BY time_created DESC LIMIT 1";
|
|
163
|
-
const OPENCODE_COMPLETED_ASSISTANT_MESSAGE_USAGE_SINCE_QUERY = "SELECT data FROM message WHERE session_id = ? AND time_created >= ? AND json_extract(data, '$.role') = 'assistant' AND json_extract(data, '$.time.completed') IS NOT NULL ORDER BY time_created DESC LIMIT 1";
|
|
164
|
-
const OPENCODE_STEP_FINISH_USAGE_QUERY = "SELECT data FROM part WHERE session_id = ? AND json_extract(data, '$.type') = 'step-finish' ORDER BY time_created DESC LIMIT 1";
|
|
165
|
-
const OPENCODE_STEP_FINISH_USAGE_SINCE_QUERY = "SELECT data FROM part WHERE session_id = ? AND time_created >= ? AND json_extract(data, '$.type') = 'step-finish' ORDER BY time_created DESC LIMIT 1";
|
|
166
|
-
const OPENCODE_SESSION_COST_QUERY = 'SELECT cost FROM session WHERE id = ?';
|
|
167
117
|
const GET_PERSISTED_CHAT_STATE_QUERY = 'SELECT agent_log, streaming_response, opencode_session_id FROM chat_sessions WHERE id = ?';
|
|
168
118
|
const GET_PERSISTED_SPEC_STATE_QUERY = 'SELECT agent_log, streaming_response, opencode_session_id FROM spec_sessions WHERE id = ?';
|
|
169
119
|
export const EMPTY_CHAT_USAGE_SNAPSHOT = {
|
|
@@ -327,9 +277,6 @@ export const mergeUsageFromJsonLine = (current, line) => {
|
|
|
327
277
|
}
|
|
328
278
|
return next;
|
|
329
279
|
};
|
|
330
|
-
const hasUsageSnapshot = (value) => {
|
|
331
|
-
return Object.values(value).some(candidate => candidate !== null && candidate !== undefined);
|
|
332
|
-
};
|
|
333
280
|
const hasMatchingUsageSnapshot = (current, next) => {
|
|
334
281
|
return current.usage_snapshot_context_window === next.usage_snapshot_context_window
|
|
335
282
|
&& current.usage_snapshot_max_output_tokens === next.usage_snapshot_max_output_tokens
|
|
@@ -500,67 +447,12 @@ const createThrottledProgressUpdater = (flushUpdate) => {
|
|
|
500
447
|
},
|
|
501
448
|
};
|
|
502
449
|
};
|
|
503
|
-
const openOpencodeDatabase = () => new Database(resolveOpencodeDbPath(), { readonly: true });
|
|
504
|
-
const readOpencodeSession = (statement, value) => {
|
|
505
|
-
const opencodeDb = openOpencodeDatabase();
|
|
506
|
-
try {
|
|
507
|
-
const row = opencodeDb.prepare(statement).get(value);
|
|
508
|
-
if (!row?.id) {
|
|
509
|
-
return null;
|
|
510
|
-
}
|
|
511
|
-
const normalizedTitle = typeof row.title === 'string'
|
|
512
|
-
? row.title.trim() || null
|
|
513
|
-
: null;
|
|
514
|
-
const title = normalizedTitle && !OPENCODE_PLACEHOLDER_TITLE_PATTERN.test(normalizedTitle)
|
|
515
|
-
? normalizedTitle
|
|
516
|
-
: null;
|
|
517
|
-
return {
|
|
518
|
-
id: row.id,
|
|
519
|
-
title,
|
|
520
|
-
};
|
|
521
|
-
}
|
|
522
|
-
finally {
|
|
523
|
-
opencodeDb.close();
|
|
524
|
-
}
|
|
525
|
-
};
|
|
526
450
|
export const readOpencodeUsageSnapshot = ({ sessionId, spawnTimestamp = null, }) => {
|
|
527
|
-
const
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
const sessionCost = typeof costRow?.cost === 'number' ? { usage_snapshot_cost: costRow.cost } : {};
|
|
531
|
-
const completedAssistantMessage = (spawnTimestamp === null
|
|
532
|
-
? opencodeDb.prepare(OPENCODE_COMPLETED_ASSISTANT_MESSAGE_USAGE_QUERY).get(sessionId)
|
|
533
|
-
: opencodeDb.prepare(OPENCODE_COMPLETED_ASSISTANT_MESSAGE_USAGE_SINCE_QUERY).get(sessionId, spawnTimestamp));
|
|
534
|
-
if (typeof completedAssistantMessage?.data === 'string') {
|
|
535
|
-
try {
|
|
536
|
-
const parsedUsage = extractChatUsageSnapshot(JSON.parse(completedAssistantMessage.data));
|
|
537
|
-
if (hasUsageSnapshot(parsedUsage)) {
|
|
538
|
-
return mergeChatUsageSnapshot(mergeChatUsageSnapshot(EMPTY_CHAT_USAGE_SNAPSHOT, parsedUsage), sessionCost);
|
|
539
|
-
}
|
|
540
|
-
}
|
|
541
|
-
catch {
|
|
542
|
-
// Fall through to the step-finish lookup below.
|
|
543
|
-
}
|
|
544
|
-
}
|
|
545
|
-
const latestStepFinish = (spawnTimestamp === null
|
|
546
|
-
? opencodeDb.prepare(OPENCODE_STEP_FINISH_USAGE_QUERY).get(sessionId)
|
|
547
|
-
: opencodeDb.prepare(OPENCODE_STEP_FINISH_USAGE_SINCE_QUERY).get(sessionId, spawnTimestamp));
|
|
548
|
-
if (typeof latestStepFinish?.data === 'string') {
|
|
549
|
-
try {
|
|
550
|
-
const parsedUsage = extractChatUsageSnapshot(JSON.parse(latestStepFinish.data));
|
|
551
|
-
if (hasUsageSnapshot(parsedUsage)) {
|
|
552
|
-
return mergeChatUsageSnapshot(mergeChatUsageSnapshot(EMPTY_CHAT_USAGE_SNAPSHOT, parsedUsage), sessionCost);
|
|
553
|
-
}
|
|
554
|
-
}
|
|
555
|
-
catch {
|
|
556
|
-
return mergeChatUsageSnapshot(EMPTY_CHAT_USAGE_SNAPSHOT, sessionCost);
|
|
557
|
-
}
|
|
558
|
-
}
|
|
559
|
-
return mergeChatUsageSnapshot(EMPTY_CHAT_USAGE_SNAPSHOT, sessionCost);
|
|
560
|
-
}
|
|
561
|
-
finally {
|
|
562
|
-
opencodeDb.close();
|
|
451
|
+
const snapshot = hostOpencodeSessionOperations.readUsageSnapshot(sessionId, spawnTimestamp);
|
|
452
|
+
if (!snapshot) {
|
|
453
|
+
throw new Error('OpenCode usage snapshot read failed');
|
|
563
454
|
}
|
|
455
|
+
return snapshot;
|
|
564
456
|
};
|
|
565
457
|
const resolveOpencodeUsageSnapshot = ({ current, sessionId, spawnTimestamp = null, }) => {
|
|
566
458
|
try {
|
|
@@ -655,12 +547,12 @@ const createOpencodeMetadataRefresher = ({ chatSessionId, io, spawnTimestamp, in
|
|
|
655
547
|
};
|
|
656
548
|
const readCurrentSession = () => {
|
|
657
549
|
if (knownSessionId) {
|
|
658
|
-
return
|
|
550
|
+
return hostOpencodeSessionOperations.findSessionById(knownSessionId);
|
|
659
551
|
}
|
|
660
552
|
if (spawnTimestamp === null) {
|
|
661
553
|
return null;
|
|
662
554
|
}
|
|
663
|
-
return
|
|
555
|
+
return hostOpencodeSessionOperations.findLatestSessionAfter(spawnTimestamp);
|
|
664
556
|
};
|
|
665
557
|
const refresh = () => {
|
|
666
558
|
if (stopped) {
|
|
@@ -723,12 +615,12 @@ const createSpecOpencodeMetadataRefresher = ({ specSessionId, io, spawnTimestamp
|
|
|
723
615
|
};
|
|
724
616
|
const readCurrentSession = () => {
|
|
725
617
|
if (knownSessionId) {
|
|
726
|
-
return
|
|
618
|
+
return hostOpencodeSessionOperations.findSessionById(knownSessionId);
|
|
727
619
|
}
|
|
728
620
|
if (spawnTimestamp === null) {
|
|
729
621
|
return null;
|
|
730
622
|
}
|
|
731
|
-
return
|
|
623
|
+
return hostOpencodeSessionOperations.findLatestSessionAfter(spawnTimestamp);
|
|
732
624
|
};
|
|
733
625
|
const refresh = () => {
|
|
734
626
|
if (stopped) {
|
|
@@ -1094,15 +986,14 @@ const parseOpencodeJsonEvent = (event, cwd) => {
|
|
|
1094
986
|
if (event.type === 'text' && event.part?.type === 'text' && event.part.text) {
|
|
1095
987
|
const text = event.part.text.trim();
|
|
1096
988
|
const phase = event.part.metadata?.openai?.phase;
|
|
989
|
+
const recoveryEvent = interpretOpencodeRecoveryEvent(event.type, event.error, text, phase);
|
|
1097
990
|
return {
|
|
1098
991
|
logFragment: text || null,
|
|
1099
992
|
responseFragment: text && (!phase || phase === 'final_answer') ? text : null,
|
|
1100
993
|
subagentEvents: [],
|
|
1101
994
|
providerSessionId,
|
|
1102
995
|
modelResolutionError: false,
|
|
1103
|
-
recoveryEvents:
|
|
1104
|
-
? [{ type: 'final_answer', text }]
|
|
1105
|
-
: [],
|
|
996
|
+
recoveryEvents: recoveryEvent ? [recoveryEvent] : [],
|
|
1106
997
|
};
|
|
1107
998
|
}
|
|
1108
999
|
if (event.type === 'reasoning' && event.part?.type === 'reasoning') {
|
|
@@ -1186,14 +1077,15 @@ const parseOpencodeJsonEvent = (event, cwd) => {
|
|
|
1186
1077
|
};
|
|
1187
1078
|
}
|
|
1188
1079
|
if (event.type === 'error') {
|
|
1189
|
-
const
|
|
1080
|
+
const recoveryEvent = interpretOpencodeRecoveryEvent(event.type, event.error, event.part?.text, undefined);
|
|
1081
|
+
const parsedError = recoveryEvent?.type === 'error' ? recoveryEvent.error : null;
|
|
1190
1082
|
const text = parsedError?.message ?? '';
|
|
1191
1083
|
return {
|
|
1192
1084
|
logFragment: text ? `**Error:** ${text}` : null,
|
|
1193
1085
|
responseFragment: null,
|
|
1194
1086
|
subagentEvents: [],
|
|
1195
1087
|
providerSessionId,
|
|
1196
|
-
recoveryEvents:
|
|
1088
|
+
recoveryEvents: recoveryEvent ? [recoveryEvent] : [],
|
|
1197
1089
|
};
|
|
1198
1090
|
}
|
|
1199
1091
|
return emptyParsedStreamLine();
|
|
@@ -1596,7 +1488,7 @@ export const readOpencodeSessionTranscript = ({ sessionId, since, cwd, }) => {
|
|
|
1596
1488
|
reader.close();
|
|
1597
1489
|
}
|
|
1598
1490
|
};
|
|
1599
|
-
export const createOpencodeSubagentMonitor = ({ initialParentSessionId, spawnTimestamp, cwd, tracker, onParentSessionId, taskSessionReader, discoverParentSession = true, }) => {
|
|
1491
|
+
export const createOpencodeSubagentMonitor = ({ initialParentSessionId, spawnTimestamp, cwd, tracker, onParentSessionId, taskSessionReader, discoverParentSession = true, parentDiscoveryDelayMs = 0, }) => {
|
|
1600
1492
|
const reader = taskSessionReader === undefined ? createOpencodeTaskSessionReader() : taskSessionReader;
|
|
1601
1493
|
let parentSessionId = initialParentSessionId;
|
|
1602
1494
|
let timer = null;
|
|
@@ -1639,7 +1531,7 @@ export const createOpencodeSubagentMonitor = ({ initialParentSessionId, spawnTim
|
|
|
1639
1531
|
return;
|
|
1640
1532
|
}
|
|
1641
1533
|
try {
|
|
1642
|
-
if (!parentSessionId && discoverParentSession) {
|
|
1534
|
+
if (!parentSessionId && discoverParentSession && Date.now() >= spawnTimestamp + parentDiscoveryDelayMs) {
|
|
1643
1535
|
setParentSessionId(reader.findParentSessionSince(spawnTimestamp));
|
|
1644
1536
|
}
|
|
1645
1537
|
if (!parentSessionId) {
|
|
@@ -1672,7 +1564,7 @@ export const createOpencodeSubagentMonitor = ({ initialParentSessionId, spawnTim
|
|
|
1672
1564
|
return;
|
|
1673
1565
|
}
|
|
1674
1566
|
refresh();
|
|
1675
|
-
timer = setInterval(refresh,
|
|
1567
|
+
timer = setInterval(refresh, OPENCODE_SUBAGENT_POLL_INTERVAL_MS);
|
|
1676
1568
|
timer.unref?.();
|
|
1677
1569
|
};
|
|
1678
1570
|
const stop = () => {
|
|
@@ -1724,6 +1616,16 @@ const killProcess = (proc, signal) => {
|
|
|
1724
1616
|
catch {
|
|
1725
1617
|
}
|
|
1726
1618
|
};
|
|
1619
|
+
const interruptProcess = (state, signal) => {
|
|
1620
|
+
if (!isProcessAlive(state.child)) {
|
|
1621
|
+
return;
|
|
1622
|
+
}
|
|
1623
|
+
if (state.interrupt) {
|
|
1624
|
+
state.interrupt(signal);
|
|
1625
|
+
return;
|
|
1626
|
+
}
|
|
1627
|
+
killProcess(state.child, signal);
|
|
1628
|
+
};
|
|
1727
1629
|
const waitForProcessExit = async (proc, timeoutMs) => {
|
|
1728
1630
|
const deadline = Date.now() + timeoutMs;
|
|
1729
1631
|
await new Promise(resolve => {
|
|
@@ -1757,6 +1659,7 @@ const untrackChatProcess = (chatSessionId, state) => {
|
|
|
1757
1659
|
export const registerChatProcessPlaceholder = (chatSessionId) => {
|
|
1758
1660
|
const state = {
|
|
1759
1661
|
child: null,
|
|
1662
|
+
interrupt: null,
|
|
1760
1663
|
researchChildren: new Set(),
|
|
1761
1664
|
stopRequested: false,
|
|
1762
1665
|
metadataRefresher: null,
|
|
@@ -1765,7 +1668,7 @@ export const registerChatProcessPlaceholder = (chatSessionId) => {
|
|
|
1765
1668
|
return state;
|
|
1766
1669
|
};
|
|
1767
1670
|
export const registerBuildProcessPlaceholder = (ticketId) => {
|
|
1768
|
-
const state = { child: null, stopRequested: false, sessionLookupTimer: null };
|
|
1671
|
+
const state = { child: null, interrupt: null, stopRequested: false, sessionLookupTimer: null };
|
|
1769
1672
|
activeBuildProcesses.set(ticketId, state);
|
|
1770
1673
|
return state;
|
|
1771
1674
|
};
|
|
@@ -1796,6 +1699,7 @@ const untrackSpecProcess = (specSessionId, state) => {
|
|
|
1796
1699
|
export const registerSpecProcessPlaceholder = (specSessionId) => {
|
|
1797
1700
|
const state = {
|
|
1798
1701
|
child: null,
|
|
1702
|
+
interrupt: null,
|
|
1799
1703
|
researchChildren: new Set(),
|
|
1800
1704
|
stopRequested: false,
|
|
1801
1705
|
metadataRefresher: null,
|
|
@@ -1819,6 +1723,7 @@ const untrackSpecReviewProcess = (specReviewRunId, state) => {
|
|
|
1819
1723
|
export const registerSpecReviewProcessPlaceholder = (specReviewRunId) => {
|
|
1820
1724
|
const state = {
|
|
1821
1725
|
child: null,
|
|
1726
|
+
interrupt: null,
|
|
1822
1727
|
researchChildren: new Set(),
|
|
1823
1728
|
stopRequested: false,
|
|
1824
1729
|
metadataRefresher: null,
|
|
@@ -1864,10 +1769,20 @@ export const stopBuildAgent = async (ticketId) => {
|
|
|
1864
1769
|
if (!state.child) {
|
|
1865
1770
|
return true;
|
|
1866
1771
|
}
|
|
1867
|
-
|
|
1772
|
+
if (state.interrupt) {
|
|
1773
|
+
state.interrupt('SIGTERM');
|
|
1774
|
+
}
|
|
1775
|
+
else {
|
|
1776
|
+
killProcess(state.child, 'SIGTERM');
|
|
1777
|
+
}
|
|
1868
1778
|
await waitForProcessExit(state.child, 5000);
|
|
1869
1779
|
if (isProcessAlive(state.child)) {
|
|
1870
|
-
|
|
1780
|
+
if (state.interrupt) {
|
|
1781
|
+
state.interrupt('SIGKILL');
|
|
1782
|
+
}
|
|
1783
|
+
else {
|
|
1784
|
+
killProcess(state.child, 'SIGKILL');
|
|
1785
|
+
}
|
|
1871
1786
|
await waitForProcessExit(state.child, 1000);
|
|
1872
1787
|
}
|
|
1873
1788
|
return true;
|
|
@@ -1943,15 +1858,15 @@ export const stopSpecReviewAgent = async (specReviewRunId) => {
|
|
|
1943
1858
|
if (!state.child) {
|
|
1944
1859
|
return true;
|
|
1945
1860
|
}
|
|
1946
|
-
|
|
1861
|
+
interruptProcess(state, 'SIGTERM');
|
|
1947
1862
|
await waitForProcessExit(state.child, 5000);
|
|
1948
1863
|
if (isProcessAlive(state.child)) {
|
|
1949
|
-
|
|
1864
|
+
interruptProcess(state, 'SIGKILL');
|
|
1950
1865
|
await waitForProcessExit(state.child, 1000);
|
|
1951
1866
|
}
|
|
1952
1867
|
return true;
|
|
1953
1868
|
};
|
|
1954
|
-
|
|
1869
|
+
const runAgentOnce = ({ ticketId, tool, prompt, cwd, io, model, variant = null, resumeSessionId = null, sessionField = 'session_id', trackBuildProcess = false, reviewProcessState, emitSuccessUpdate = true, opencodeAgentMode, runStream, executionContext, persistSessionIdToTicket = false, spawnPermitId, executionProvider = hostExecutionEnvironment, }) => {
|
|
1955
1870
|
const resolvedModel = resolveAgentModel(tool, model);
|
|
1956
1871
|
const subagentTracker = createSubagentRunTracker({
|
|
1957
1872
|
parentType: 'ticket',
|
|
@@ -2049,7 +1964,7 @@ export const runAgent = ({ ticketId, tool, prompt, cwd, io, model, variant = nul
|
|
|
2049
1964
|
return hasResolvedOpencodeSessionId;
|
|
2050
1965
|
}
|
|
2051
1966
|
try {
|
|
2052
|
-
const session =
|
|
1967
|
+
const session = hostOpencodeSessionOperations.findLatestSessionAfter(spawnTimestamp);
|
|
2053
1968
|
if (session?.id) {
|
|
2054
1969
|
bindResolvedOpencodeSession(session.id);
|
|
2055
1970
|
}
|
|
@@ -2059,15 +1974,19 @@ export const runAgent = ({ ticketId, tool, prompt, cwd, io, model, variant = nul
|
|
|
2059
1974
|
}
|
|
2060
1975
|
return hasResolvedOpencodeSessionId;
|
|
2061
1976
|
};
|
|
2062
|
-
const
|
|
2063
|
-
?
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
:
|
|
1977
|
+
const agentProvider = tool === 'claude'
|
|
1978
|
+
? claudeAgentProvider
|
|
1979
|
+
: opencodeAgentProvider;
|
|
1980
|
+
const invocation = agentProvider.buildStreamingInvocation({
|
|
1981
|
+
cwd,
|
|
1982
|
+
prompt,
|
|
1983
|
+
model: resolvedModel,
|
|
1984
|
+
variant,
|
|
1985
|
+
mode: opencodeAgentMode,
|
|
1986
|
+
resumeSessionId,
|
|
1987
|
+
sessionId: randomUUID(),
|
|
1988
|
+
});
|
|
1989
|
+
sessionId = invocation.sessionId;
|
|
2071
1990
|
writeRunStatus('running', null);
|
|
2072
1991
|
if (tool === 'claude' && sessionId) {
|
|
2073
1992
|
writeRunSessionId(sessionId);
|
|
@@ -2076,6 +1995,8 @@ export const runAgent = ({ ticketId, tool, prompt, cwd, io, model, variant = nul
|
|
|
2076
1995
|
spawnTimestamp = Date.now();
|
|
2077
1996
|
}
|
|
2078
1997
|
let child;
|
|
1998
|
+
let interrupt;
|
|
1999
|
+
let placementFailure;
|
|
2079
2000
|
try {
|
|
2080
2001
|
if (buildState?.stopRequested) {
|
|
2081
2002
|
throw new AgentSpawnPermitRevokedError();
|
|
@@ -2083,28 +2004,37 @@ export const runAgent = ({ ticketId, tool, prompt, cwd, io, model, variant = nul
|
|
|
2083
2004
|
if (spawnPermitId && executionContext) {
|
|
2084
2005
|
assertAgentSpawnPermit(spawnPermitId, executionContext.runId);
|
|
2085
2006
|
}
|
|
2086
|
-
|
|
2007
|
+
const handle = executionProvider.execute({
|
|
2008
|
+
target: {
|
|
2009
|
+
kind: 'worktree',
|
|
2010
|
+
id: ticketId,
|
|
2011
|
+
workspacePath: cwd,
|
|
2012
|
+
revision: 'working-tree',
|
|
2013
|
+
},
|
|
2014
|
+
command: invocation.command,
|
|
2015
|
+
args: invocation.args,
|
|
2087
2016
|
cwd,
|
|
2088
|
-
stdio:
|
|
2017
|
+
stdio: invocation.stdio,
|
|
2089
2018
|
env: createWorkspaceProcessEnvironment(cwd, { PWD: cwd }),
|
|
2090
2019
|
detached: process.platform !== 'win32',
|
|
2091
2020
|
});
|
|
2021
|
+
child = handle.child;
|
|
2022
|
+
interrupt = handle.interrupt;
|
|
2023
|
+
placementFailure = handle.placementFailure;
|
|
2092
2024
|
}
|
|
2093
2025
|
catch (error) {
|
|
2094
|
-
const resolvedError =
|
|
2026
|
+
const resolvedError = agentProvider.classifyExecutionFailure(error);
|
|
2095
2027
|
const transcriptRuns = subagentTracker.finalize('error', resolvedError.message, null);
|
|
2096
2028
|
rejectOnce(attachTranscriptRunsToError(resolvedError, transcriptRuns));
|
|
2097
2029
|
return;
|
|
2098
2030
|
}
|
|
2099
2031
|
reviewChild = child;
|
|
2100
|
-
if (
|
|
2101
|
-
|
|
2102
|
-
? appendClaudeHeadlessPlanPrompt(prompt)
|
|
2103
|
-
: prompt;
|
|
2104
|
-
writeClaudeStdin(child, buildClaudeStdinMessage(claudePrompt, [], cwd));
|
|
2032
|
+
if (invocation.stdin !== null) {
|
|
2033
|
+
writeClaudeStdin(child, invocation.stdin);
|
|
2105
2034
|
}
|
|
2106
2035
|
if (trackBuildProcess && buildState) {
|
|
2107
2036
|
buildState.child = child;
|
|
2037
|
+
buildState.interrupt = interrupt;
|
|
2108
2038
|
activeBuildProcesses.set(ticketId, buildState);
|
|
2109
2039
|
}
|
|
2110
2040
|
if (reviewProcessState) {
|
|
@@ -2113,6 +2043,14 @@ export const runAgent = ({ ticketId, tool, prompt, cwd, io, model, variant = nul
|
|
|
2113
2043
|
killProcess(child, 'SIGTERM');
|
|
2114
2044
|
}
|
|
2115
2045
|
}
|
|
2046
|
+
void placementFailure.then(failure => {
|
|
2047
|
+
if (!failure || settled) {
|
|
2048
|
+
return;
|
|
2049
|
+
}
|
|
2050
|
+
const resolvedError = agentProvider.classifyExecutionFailure(failure);
|
|
2051
|
+
const transcriptRuns = finalizeRun('error', getErrorMessage(resolvedError), null);
|
|
2052
|
+
rejectOnce(attachTranscriptRunsToError(resolvedError, transcriptRuns));
|
|
2053
|
+
});
|
|
2116
2054
|
startTime = Date.now();
|
|
2117
2055
|
if (tool === 'opencode' && spawnTimestamp !== null) {
|
|
2118
2056
|
opencodeSubagentMonitor = createOpencodeSubagentMonitor({
|
|
@@ -2207,8 +2145,9 @@ export const runAgent = ({ ticketId, tool, prompt, cwd, io, model, variant = nul
|
|
|
2207
2145
|
resolveOnce(withTranscriptRuns({ response: null, responseTiming: null, usageSnapshot }, transcriptRuns));
|
|
2208
2146
|
return;
|
|
2209
2147
|
}
|
|
2210
|
-
const
|
|
2211
|
-
|
|
2148
|
+
const resolvedError = agentProvider.classifyExecutionFailure(err);
|
|
2149
|
+
const transcriptRuns = finalizeRun('error', getErrorMessage(resolvedError), null);
|
|
2150
|
+
rejectOnce(attachTranscriptRunsToError(resolvedError, transcriptRuns));
|
|
2212
2151
|
});
|
|
2213
2152
|
return;
|
|
2214
2153
|
}
|
|
@@ -2307,6 +2246,13 @@ export const runAgent = ({ ticketId, tool, prompt, cwd, io, model, variant = nul
|
|
|
2307
2246
|
const error = resumeSessionId && !executionCapableEventObserved && isTypedPreExecutionContinuationRejection(failureMessage)
|
|
2308
2247
|
? new AgentContinuationRejectedError(failureMessage)
|
|
2309
2248
|
: createOpencodeExitError(failureMessage, stderrModelError, recoveryTracker);
|
|
2249
|
+
const retryableError = !executionCapableEventObserved && !sessionId
|
|
2250
|
+
? markRetryableOpenCodeDatabaseLock(error)
|
|
2251
|
+
: error;
|
|
2252
|
+
if (retryableError instanceof RetryableOpenCodeDatabaseLockError) {
|
|
2253
|
+
rejectOnce(retryableError);
|
|
2254
|
+
return;
|
|
2255
|
+
}
|
|
2310
2256
|
const transcriptRuns = finalizeRun('error', log, finalResponse);
|
|
2311
2257
|
rejectOnce(attachTranscriptRunsToError(error, transcriptRuns));
|
|
2312
2258
|
});
|
|
@@ -2321,12 +2267,17 @@ export const runAgent = ({ ticketId, tool, prompt, cwd, io, model, variant = nul
|
|
|
2321
2267
|
resolveOnce(withTranscriptRuns({ response: null, responseTiming: null }, transcriptRuns));
|
|
2322
2268
|
return;
|
|
2323
2269
|
}
|
|
2324
|
-
const
|
|
2325
|
-
|
|
2270
|
+
const resolvedError = agentProvider.classifyExecutionFailure(err);
|
|
2271
|
+
const transcriptRuns = finalizeRun('error', getErrorMessage(resolvedError), null);
|
|
2272
|
+
rejectOnce(attachTranscriptRunsToError(resolvedError, transcriptRuns));
|
|
2326
2273
|
});
|
|
2327
2274
|
});
|
|
2328
2275
|
};
|
|
2329
|
-
export const
|
|
2276
|
+
export const runAgent = (options) => {
|
|
2277
|
+
const run = () => runAgentOnce(options);
|
|
2278
|
+
return options.tool === 'opencode' ? retryOpenCodeDatabaseLocks(run) : run();
|
|
2279
|
+
};
|
|
2280
|
+
const runChatAgentOnce = ({ chatSessionId, tool, prompt, files = [], cwd, io, model, variant = null, resumeSessionId = null, opencodeAgentMode = 'build', executionContext, mainRun, spawnPermitId, }) => {
|
|
2330
2281
|
const resolvedModel = resolveAgentModel(tool, model);
|
|
2331
2282
|
const subagentTracker = createSubagentRunTracker({
|
|
2332
2283
|
parentType: 'chat',
|
|
@@ -2693,11 +2644,18 @@ export const runChatAgent = ({ chatSessionId, tool, prompt, files = [], cwd, io,
|
|
|
2693
2644
|
}, transcriptRuns));
|
|
2694
2645
|
return;
|
|
2695
2646
|
}
|
|
2696
|
-
updateChatSessionStatus(chatSessionId, 'error', log, io, finalResponse);
|
|
2697
2647
|
const failureMessage = `Agent exited with code ${code ?? 'null'}\n${log}${stderrOutput !== log && stderrOutput ? '\n\n' + stderrOutput : ''}`;
|
|
2698
2648
|
const error = resumeSessionId && !executionCapableEventObserved && isTypedPreExecutionContinuationRejection(failureMessage)
|
|
2699
2649
|
? new AgentContinuationRejectedError(failureMessage)
|
|
2700
2650
|
: createOpencodeExitError(failureMessage, stderrModelError, recoveryTracker);
|
|
2651
|
+
const retryableError = !executionCapableEventObserved
|
|
2652
|
+
? markRetryableOpenCodeDatabaseLock(error)
|
|
2653
|
+
: error;
|
|
2654
|
+
if (retryableError instanceof RetryableOpenCodeDatabaseLockError) {
|
|
2655
|
+
rejectOnce(retryableError);
|
|
2656
|
+
return;
|
|
2657
|
+
}
|
|
2658
|
+
updateChatSessionStatus(chatSessionId, 'error', log, io, finalResponse);
|
|
2701
2659
|
const transcriptRuns = subagentTracker.finalize('error', log, finalResponse);
|
|
2702
2660
|
rejectOnce(attachTranscriptRunsToError(error, transcriptRuns));
|
|
2703
2661
|
});
|
|
@@ -2727,7 +2685,11 @@ export const runChatAgent = ({ chatSessionId, tool, prompt, files = [], cwd, io,
|
|
|
2727
2685
|
});
|
|
2728
2686
|
});
|
|
2729
2687
|
};
|
|
2730
|
-
export const
|
|
2688
|
+
export const runChatAgent = (options) => {
|
|
2689
|
+
const run = () => runChatAgentOnce(options);
|
|
2690
|
+
return options.tool === 'opencode' ? retryOpenCodeDatabaseLocks(run) : run();
|
|
2691
|
+
};
|
|
2692
|
+
const runSpecAgentOnce = ({ specSessionId, tool, prompt, files = [], cwd, io, model, variant = null, resumeSessionId = null, executionContext, mainRun, }) => {
|
|
2731
2693
|
const resolvedModel = resolveAgentModel(tool, model);
|
|
2732
2694
|
const subagentTracker = createSubagentRunTracker({
|
|
2733
2695
|
parentType: 'spec',
|
|
@@ -3067,10 +3029,18 @@ export const runSpecAgent = ({ specSessionId, tool, prompt, files = [], cwd, io,
|
|
|
3067
3029
|
}, transcriptRuns));
|
|
3068
3030
|
return;
|
|
3069
3031
|
}
|
|
3032
|
+
const failureMessage = `Agent exited with code ${code ?? 'null'}\n${log}${stderrOutput !== log && stderrOutput ? '\n\n' + stderrOutput : ''}`;
|
|
3033
|
+
const error = createOpencodeExitError(failureMessage, stderrModelError, recoveryTracker);
|
|
3034
|
+
const retryableError = logLines.length === 0 && responseLines.length === 0
|
|
3035
|
+
? markRetryableOpenCodeDatabaseLock(error)
|
|
3036
|
+
: error;
|
|
3037
|
+
if (retryableError instanceof RetryableOpenCodeDatabaseLockError) {
|
|
3038
|
+
rejectOnce(retryableError);
|
|
3039
|
+
return;
|
|
3040
|
+
}
|
|
3070
3041
|
updateSpecSessionStatusLocal(specSessionId, 'error', log, io, finalResponse);
|
|
3071
3042
|
const transcriptRuns = subagentTracker.finalize('error', log, finalResponse);
|
|
3072
|
-
|
|
3073
|
-
rejectOnce(attachTranscriptRunsToError(createOpencodeExitError(failureMessage, stderrModelError, recoveryTracker), transcriptRuns));
|
|
3043
|
+
rejectOnce(attachTranscriptRunsToError(error, transcriptRuns));
|
|
3074
3044
|
});
|
|
3075
3045
|
child.on('error', err => {
|
|
3076
3046
|
progressUpdater.cancel();
|
|
@@ -3098,6 +3068,10 @@ export const runSpecAgent = ({ specSessionId, tool, prompt, files = [], cwd, io,
|
|
|
3098
3068
|
});
|
|
3099
3069
|
});
|
|
3100
3070
|
};
|
|
3071
|
+
export const runSpecAgent = (options) => {
|
|
3072
|
+
const run = () => runSpecAgentOnce(options);
|
|
3073
|
+
return options.tool === 'opencode' ? retryOpenCodeDatabaseLocks(run) : run();
|
|
3074
|
+
};
|
|
3101
3075
|
/**
|
|
3102
3076
|
* Focused, decoupled runner for a one-shot Spec Review.
|
|
3103
3077
|
*
|
|
@@ -3108,7 +3082,7 @@ export const runSpecAgent = ({ specSessionId, tool, prompt, files = [], cwd, io,
|
|
|
3108
3082
|
* updateSpecReviewRunStatus / updateSpecReviewRunStreaming / updateSpecReviewRunUsage —
|
|
3109
3083
|
* the spec_sessions row is never touched by this path.
|
|
3110
3084
|
*/
|
|
3111
|
-
|
|
3085
|
+
const runSpecReviewAgentOnce = ({ specReviewRunId, specSessionId, tool, prompt, files = [], cwd, io, model, variant = null, executionContext, }) => {
|
|
3112
3086
|
const registeredState = activeSpecReviewProcesses.get(specReviewRunId);
|
|
3113
3087
|
if (registeredState?.stopRequested) {
|
|
3114
3088
|
return Promise.resolve({
|
|
@@ -3127,6 +3101,7 @@ export const runSpecReviewAgent = ({ specReviewRunId, specSessionId, tool, promp
|
|
|
3127
3101
|
});
|
|
3128
3102
|
return new Promise((resolve, reject) => {
|
|
3129
3103
|
let spawnTimestamp = null;
|
|
3104
|
+
let opencodeSessionId = null;
|
|
3130
3105
|
let opencodeSubagentMonitor = null;
|
|
3131
3106
|
let settled = false;
|
|
3132
3107
|
let startTime = null;
|
|
@@ -3134,6 +3109,7 @@ export const runSpecReviewAgent = ({ specReviewRunId, specSessionId, tool, promp
|
|
|
3134
3109
|
let usageSnapshot = EMPTY_CHAT_USAGE_SNAPSHOT;
|
|
3135
3110
|
const reviewState = registeredState ?? {
|
|
3136
3111
|
child: null,
|
|
3112
|
+
interrupt: null,
|
|
3137
3113
|
researchChildren: new Set(),
|
|
3138
3114
|
stopRequested: false,
|
|
3139
3115
|
metadataRefresher: null,
|
|
@@ -3149,21 +3125,56 @@ export const runSpecReviewAgent = ({ specReviewRunId, specSessionId, tool, promp
|
|
|
3149
3125
|
};
|
|
3150
3126
|
const resolveOnce = settle(resolve);
|
|
3151
3127
|
const rejectOnce = settle(reject);
|
|
3152
|
-
const [cmd, args] =
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
]
|
|
3157
|
-
: ['opencode', ['run', '--agent', 'plan', '--format', 'json', '--thinking', ...(resolvedModel ? ['--model', resolvedModel] : []), ...(variant ? ['--variant', variant] : []), ...files.flatMap(filePath => ['--file', filePath]), ...(files.length > 0 ? ['--'] : []), prompt]];
|
|
3128
|
+
const [cmd, args] = [
|
|
3129
|
+
'claude',
|
|
3130
|
+
buildClaudeArgs('plan', resolvedModel, null, randomUUID()),
|
|
3131
|
+
];
|
|
3158
3132
|
updateSpecReviewRunStreaming(specReviewRunId, null);
|
|
3159
|
-
const
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
|
|
3133
|
+
const env = createWorkspaceProcessEnvironment(cwd, { PWD: cwd });
|
|
3134
|
+
let child;
|
|
3135
|
+
if (tool === 'opencode') {
|
|
3136
|
+
const invocation = buildOpencodeSpecReviewInvocation({ prompt, files, model: resolvedModel, variant });
|
|
3137
|
+
try {
|
|
3138
|
+
const handle = hostExecutionEnvironment.execute({
|
|
3139
|
+
target: {
|
|
3140
|
+
kind: 'repository',
|
|
3141
|
+
id: cwd,
|
|
3142
|
+
workspacePath: cwd,
|
|
3143
|
+
revision: 'working-tree',
|
|
3144
|
+
},
|
|
3145
|
+
command: invocation.command,
|
|
3146
|
+
args: invocation.args,
|
|
3147
|
+
cwd,
|
|
3148
|
+
stdio: invocation.stdio,
|
|
3149
|
+
env,
|
|
3150
|
+
detached: process.platform !== 'win32',
|
|
3151
|
+
});
|
|
3152
|
+
child = handle.child;
|
|
3153
|
+
reviewState.interrupt = handle.interrupt;
|
|
3154
|
+
void handle.placementFailure.then(failure => {
|
|
3155
|
+
if (failure) {
|
|
3156
|
+
rejectOnce(opencodeAgentProvider.classifyExecutionFailure(failure));
|
|
3157
|
+
}
|
|
3158
|
+
});
|
|
3159
|
+
}
|
|
3160
|
+
catch (error) {
|
|
3161
|
+
rejectOnce(opencodeAgentProvider.classifyExecutionFailure(error));
|
|
3162
|
+
return;
|
|
3163
|
+
}
|
|
3164
|
+
}
|
|
3165
|
+
else {
|
|
3166
|
+
child = spawn(cmd, args, {
|
|
3167
|
+
cwd,
|
|
3168
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
3169
|
+
env,
|
|
3170
|
+
detached: process.platform !== 'win32',
|
|
3171
|
+
});
|
|
3172
|
+
}
|
|
3165
3173
|
reviewState.child = child;
|
|
3166
3174
|
activeSpecReviewProcesses.set(specReviewRunId, reviewState);
|
|
3175
|
+
if (reviewState.stopRequested) {
|
|
3176
|
+
interruptProcess(reviewState, 'SIGTERM');
|
|
3177
|
+
}
|
|
3167
3178
|
if (tool === 'claude') {
|
|
3168
3179
|
writeClaudeStdin(child, buildClaudeStdinMessage(appendClaudeHeadlessPlanPrompt(prompt), files, cwd));
|
|
3169
3180
|
}
|
|
@@ -3175,6 +3186,7 @@ export const runSpecReviewAgent = ({ specReviewRunId, specSessionId, tool, promp
|
|
|
3175
3186
|
spawnTimestamp,
|
|
3176
3187
|
cwd,
|
|
3177
3188
|
tracker: subagentTracker,
|
|
3189
|
+
parentDiscoveryDelayMs: OPENCODE_SESSION_METADATA_LOOKUP_DELAY_MS,
|
|
3178
3190
|
});
|
|
3179
3191
|
opencodeSubagentMonitor.start();
|
|
3180
3192
|
}
|
|
@@ -3287,6 +3299,7 @@ export const runSpecReviewAgent = ({ specReviewRunId, specSessionId, tool, promp
|
|
|
3287
3299
|
const parsed = parseOpencodeJsonLine(line, cwd);
|
|
3288
3300
|
const { logFragment, responseFragment, subagentEvents, providerSessionId } = parsed;
|
|
3289
3301
|
opencodeSubagentMonitor?.setParentSessionId(providerSessionId);
|
|
3302
|
+
opencodeSessionId = providerSessionId ?? opencodeSessionId;
|
|
3290
3303
|
subagentTracker.handle(subagentEvents);
|
|
3291
3304
|
if (appendOpencodeTranscriptLine({ parsed, logLines, responseLines, recoveryTracker })) {
|
|
3292
3305
|
firstFinalAnswerTime = null;
|
|
@@ -3304,6 +3317,7 @@ export const runSpecReviewAgent = ({ specReviewRunId, specSessionId, tool, promp
|
|
|
3304
3317
|
usageSnapshot = mergeUsageFromJsonLine(usageSnapshot, remainder);
|
|
3305
3318
|
const parsed = parseOpencodeJsonLine(remainder, cwd);
|
|
3306
3319
|
opencodeSubagentMonitor?.setParentSessionId(parsed.providerSessionId);
|
|
3320
|
+
opencodeSessionId = parsed.providerSessionId ?? opencodeSessionId;
|
|
3307
3321
|
subagentTracker.handle(parsed.subagentEvents);
|
|
3308
3322
|
if (appendOpencodeTranscriptLine({ parsed, logLines, responseLines, recoveryTracker })) {
|
|
3309
3323
|
firstFinalAnswerTime = null;
|
|
@@ -3315,6 +3329,13 @@ export const runSpecReviewAgent = ({ specReviewRunId, specSessionId, tool, promp
|
|
|
3315
3329
|
const finalResponse = recoveryTracker.hasRecoveredFinalAnswer()
|
|
3316
3330
|
? recoveredResponse
|
|
3317
3331
|
: responseLines.join('\n\n') || stderrOutput;
|
|
3332
|
+
if (opencodeSessionId && spawnTimestamp !== null) {
|
|
3333
|
+
usageSnapshot = resolveOpencodeUsageSnapshot({
|
|
3334
|
+
current: usageSnapshot,
|
|
3335
|
+
sessionId: opencodeSessionId,
|
|
3336
|
+
spawnTimestamp,
|
|
3337
|
+
});
|
|
3338
|
+
}
|
|
3318
3339
|
if (reviewState.stopRequested) {
|
|
3319
3340
|
updateSpecReviewRunStreaming(specReviewRunId, finalResponse || null);
|
|
3320
3341
|
subagentTracker.finalize('stopped', log || null, finalResponse || null);
|
|
@@ -3338,15 +3359,44 @@ export const runSpecReviewAgent = ({ specReviewRunId, specSessionId, tool, promp
|
|
|
3338
3359
|
});
|
|
3339
3360
|
return;
|
|
3340
3361
|
}
|
|
3341
|
-
subagentTracker.finalize('error', log || null, finalResponse || null);
|
|
3342
3362
|
const failureMessage = `Agent exited with code ${code ?? 'null'}\n${log}${stderrOutput !== log && stderrOutput ? '\n\n' + stderrOutput : ''}`;
|
|
3343
|
-
|
|
3363
|
+
const error = createOpencodeExitError(failureMessage, stderrModelError, recoveryTracker);
|
|
3364
|
+
const retryableError = logLines.length === 0 && responseLines.length === 0 && !opencodeSessionId
|
|
3365
|
+
? markRetryableOpenCodeDatabaseLock(error)
|
|
3366
|
+
: error;
|
|
3367
|
+
if (retryableError instanceof RetryableOpenCodeDatabaseLockError) {
|
|
3368
|
+
rejectOnce(retryableError);
|
|
3369
|
+
return;
|
|
3370
|
+
}
|
|
3371
|
+
subagentTracker.finalize('error', log || null, finalResponse || null);
|
|
3372
|
+
rejectOnce(error);
|
|
3344
3373
|
});
|
|
3345
3374
|
child.on('error', err => {
|
|
3346
3375
|
progressUpdater.cancel();
|
|
3347
|
-
|
|
3348
|
-
|
|
3376
|
+
if (reviewState.stopRequested) {
|
|
3377
|
+
const processError = getErrorMessage(err);
|
|
3378
|
+
const finalLog = logLines.join('\n\n') || processError;
|
|
3379
|
+
const finalResponse = recoveryTracker.hasRecoveredFinalAnswer()
|
|
3380
|
+
? recoveryTracker.getRecoveredResponse()
|
|
3381
|
+
: responseLines.join('\n\n') || null;
|
|
3382
|
+
updateSpecReviewRunStreaming(specReviewRunId, finalResponse);
|
|
3383
|
+
subagentTracker.finalize('stopped', finalLog || null, finalResponse);
|
|
3384
|
+
resolveOnce({
|
|
3385
|
+
completionStatus: 'stopped',
|
|
3386
|
+
finalResponse,
|
|
3387
|
+
usageSnapshot,
|
|
3388
|
+
responseTiming: buildResponseTiming({ startTime, firstFinalAnswerTime, isStopped: true }),
|
|
3389
|
+
});
|
|
3390
|
+
return;
|
|
3391
|
+
}
|
|
3392
|
+
const failure = opencodeAgentProvider.classifyExecutionFailure(err);
|
|
3393
|
+
subagentTracker.finalize('error', failure.message, null);
|
|
3394
|
+
rejectOnce(failure);
|
|
3349
3395
|
});
|
|
3350
3396
|
});
|
|
3351
3397
|
};
|
|
3398
|
+
export const runSpecReviewAgent = (options) => {
|
|
3399
|
+
const run = () => runSpecReviewAgentOnce(options);
|
|
3400
|
+
return options.tool === 'opencode' ? retryOpenCodeDatabaseLocks(run) : run();
|
|
3401
|
+
};
|
|
3352
3402
|
//# sourceMappingURL=agent.js.map
|