@gh-symphony/cli 0.0.15 → 0.0.17
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/{chunk-RNWX7DQU.js → chunk-EFMFGOWM.js} +123 -1165
- package/dist/{chunk-M7OSMUTN.js → chunk-MHIWAIVD.js} +5 -3
- package/dist/chunk-TF3QNWNC.js +1121 -0
- package/dist/index.js +5 -5
- package/dist/{project-3ELXQ35D.js → project-557FE2GD.js} +3 -2
- package/dist/{recover-T6ME6C56.js → recover-LVBI2TGH.js} +2 -1
- package/dist/{run-DYINRZHK.js → run-WITYAYFZ.js} +2 -1
- package/dist/{start-PIFQMIC2.js → start-JUFKNL3N.js} +3 -2
- package/dist/version-YVM2A25J.js +16 -0
- package/dist/worker-entry.js +1828 -0
- package/package.json +4 -4
- package/dist/version-VBB62JWI.js +0 -30
|
@@ -1,1086 +1,46 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
DEFAULT_WORKFLOW_LIFECYCLE,
|
|
4
|
+
WorkflowConfigStore,
|
|
5
|
+
buildHookEnv,
|
|
6
|
+
buildProjectSnapshot,
|
|
7
|
+
buildPromptVariables,
|
|
8
|
+
createDefaultWorkflowResolution,
|
|
9
|
+
createInvalidWorkflowResolution,
|
|
10
|
+
deriveIssueWorkspaceKey,
|
|
11
|
+
deriveIssueWorkspaceKeyFromIdentifier,
|
|
12
|
+
deriveLegacyIssueWorkspaceKey,
|
|
13
|
+
executeWorkspaceHook,
|
|
14
|
+
isFileMissing,
|
|
15
|
+
isMatchingIssueRun,
|
|
16
|
+
isOrchestratorChannelEvent,
|
|
17
|
+
isStateActive,
|
|
18
|
+
isStateTerminal,
|
|
19
|
+
mapIssueOrchestrationStateToStatus,
|
|
20
|
+
matchesWorkflowState,
|
|
21
|
+
parseRecentEvents,
|
|
22
|
+
readEnvFile,
|
|
23
|
+
readJsonFile,
|
|
24
|
+
renderPrompt,
|
|
25
|
+
resolveIssueWorkspaceDirectory,
|
|
26
|
+
safeReadDir,
|
|
27
|
+
scheduleRetryAt
|
|
28
|
+
} from "./chunk-TF3QNWNC.js";
|
|
2
29
|
|
|
3
30
|
// ../orchestrator/dist/service.js
|
|
4
|
-
import { mkdir as mkdir3, readFile as
|
|
31
|
+
import { mkdir as mkdir3, readFile as readFile3, rm as rm3, writeFile as writeFile3 } from "fs/promises";
|
|
5
32
|
import { createWriteStream, mkdirSync } from "fs";
|
|
6
|
-
import { spawn as
|
|
7
|
-
import { join as
|
|
33
|
+
import { spawn as spawn2 } from "child_process";
|
|
34
|
+
import { join as join3 } from "path";
|
|
8
35
|
import { StringDecoder } from "string_decoder";
|
|
9
36
|
import { fileURLToPath } from "url";
|
|
10
37
|
|
|
11
|
-
// ../core/dist/contracts/status-surface.js
|
|
12
|
-
var WORKFLOW_EXECUTION_PHASES = [
|
|
13
|
-
"planning",
|
|
14
|
-
"human-review",
|
|
15
|
-
"implementation",
|
|
16
|
-
"awaiting-merge",
|
|
17
|
-
"completed"
|
|
18
|
-
];
|
|
19
|
-
function isWorkflowExecutionPhase(value) {
|
|
20
|
-
return typeof value === "string" && WORKFLOW_EXECUTION_PHASES.includes(value);
|
|
21
|
-
}
|
|
22
|
-
var SESSION_EXIT_CLASSIFICATIONS = [
|
|
23
|
-
"completed",
|
|
24
|
-
"budget-exceeded",
|
|
25
|
-
"convergence-detected",
|
|
26
|
-
"max-turns-reached",
|
|
27
|
-
"user-input-required",
|
|
28
|
-
"timeout",
|
|
29
|
-
"error"
|
|
30
|
-
];
|
|
31
|
-
function isSessionExitClassification(value) {
|
|
32
|
-
return typeof value === "string" && SESSION_EXIT_CLASSIFICATIONS.includes(value);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// ../core/dist/contracts/run-attempt-phase.js
|
|
36
|
-
var RUN_ATTEMPT_PHASES = [
|
|
37
|
-
"preparing_workspace",
|
|
38
|
-
"building_prompt",
|
|
39
|
-
"launching_agent",
|
|
40
|
-
"initializing_session",
|
|
41
|
-
"streaming_turn",
|
|
42
|
-
"finishing",
|
|
43
|
-
"succeeded",
|
|
44
|
-
"failed",
|
|
45
|
-
"timed_out",
|
|
46
|
-
"stalled",
|
|
47
|
-
"canceled_by_reconciliation"
|
|
48
|
-
];
|
|
49
|
-
function isRunAttemptPhase(value) {
|
|
50
|
-
return typeof value === "string" && RUN_ATTEMPT_PHASES.includes(value);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// ../core/dist/contracts/orchestrator-channel.js
|
|
54
|
-
function isRecord(value) {
|
|
55
|
-
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
56
|
-
}
|
|
57
|
-
function isTokenUsage(value) {
|
|
58
|
-
if (!isRecord(value)) {
|
|
59
|
-
return false;
|
|
60
|
-
}
|
|
61
|
-
return typeof value.inputTokens === "number" && typeof value.outputTokens === "number" && typeof value.totalTokens === "number";
|
|
62
|
-
}
|
|
63
|
-
function isSessionInfo(value) {
|
|
64
|
-
if (!isRecord(value)) {
|
|
65
|
-
return false;
|
|
66
|
-
}
|
|
67
|
-
return (typeof value.threadId === "string" || value.threadId === null) && (typeof value.turnId === "string" || value.turnId === null) && typeof value.turnCount === "number" && (typeof value.sessionId === "string" || value.sessionId === null) && (!("exitClassification" in value) || value.exitClassification === void 0 || value.exitClassification === null || isSessionExitClassification(value.exitClassification));
|
|
68
|
-
}
|
|
69
|
-
function isNullableString(value) {
|
|
70
|
-
return typeof value === "string" || value === null;
|
|
71
|
-
}
|
|
72
|
-
function isTurnEventBase(value) {
|
|
73
|
-
return typeof value.startedAt === "string" && isNullableString(value.threadId) && isNullableString(value.turnId) && typeof value.turnCount === "number" && isNullableString(value.sessionId);
|
|
74
|
-
}
|
|
75
|
-
function isOrchestratorChannelEvent(value) {
|
|
76
|
-
if (!isRecord(value)) {
|
|
77
|
-
return false;
|
|
78
|
-
}
|
|
79
|
-
if (typeof value.issueId !== "string") {
|
|
80
|
-
return false;
|
|
81
|
-
}
|
|
82
|
-
if (value.type === "codex_update") {
|
|
83
|
-
if (typeof value.lastEventAt !== "string") {
|
|
84
|
-
return false;
|
|
85
|
-
}
|
|
86
|
-
if ("event" in value && value.event !== void 0 && typeof value.event !== "string") {
|
|
87
|
-
return false;
|
|
88
|
-
}
|
|
89
|
-
if ("tokenUsage" in value && value.tokenUsage !== void 0 && !isTokenUsage(value.tokenUsage)) {
|
|
90
|
-
return false;
|
|
91
|
-
}
|
|
92
|
-
if ("rateLimits" in value && value.rateLimits !== void 0 && !isRecord(value.rateLimits)) {
|
|
93
|
-
return false;
|
|
94
|
-
}
|
|
95
|
-
if ("sessionInfo" in value && value.sessionInfo !== void 0 && !isSessionInfo(value.sessionInfo)) {
|
|
96
|
-
return false;
|
|
97
|
-
}
|
|
98
|
-
if ("executionPhase" in value && value.executionPhase !== void 0 && value.executionPhase !== null && !isWorkflowExecutionPhase(value.executionPhase)) {
|
|
99
|
-
return false;
|
|
100
|
-
}
|
|
101
|
-
if ("runPhase" in value && value.runPhase !== void 0 && value.runPhase !== null && !isRunAttemptPhase(value.runPhase)) {
|
|
102
|
-
return false;
|
|
103
|
-
}
|
|
104
|
-
if ("lastError" in value && value.lastError !== void 0 && value.lastError !== null && typeof value.lastError !== "string") {
|
|
105
|
-
return false;
|
|
106
|
-
}
|
|
107
|
-
return true;
|
|
108
|
-
}
|
|
109
|
-
if (value.type === "heartbeat") {
|
|
110
|
-
if (value.lastEventAt !== null && typeof value.lastEventAt !== "string") {
|
|
111
|
-
return false;
|
|
112
|
-
}
|
|
113
|
-
if (!isTokenUsage(value.tokenUsage)) {
|
|
114
|
-
return false;
|
|
115
|
-
}
|
|
116
|
-
if (value.rateLimits !== null && !isRecord(value.rateLimits)) {
|
|
117
|
-
return false;
|
|
118
|
-
}
|
|
119
|
-
if (value.sessionInfo !== null && !isSessionInfo(value.sessionInfo)) {
|
|
120
|
-
return false;
|
|
121
|
-
}
|
|
122
|
-
if (value.executionPhase !== null && !isWorkflowExecutionPhase(value.executionPhase)) {
|
|
123
|
-
return false;
|
|
124
|
-
}
|
|
125
|
-
if (value.runPhase !== null && !isRunAttemptPhase(value.runPhase)) {
|
|
126
|
-
return false;
|
|
127
|
-
}
|
|
128
|
-
if (value.lastError !== null && typeof value.lastError !== "string") {
|
|
129
|
-
return false;
|
|
130
|
-
}
|
|
131
|
-
return true;
|
|
132
|
-
}
|
|
133
|
-
if (value.type === "turn_started") {
|
|
134
|
-
return isTurnEventBase(value);
|
|
135
|
-
}
|
|
136
|
-
if (value.type === "turn_completed") {
|
|
137
|
-
return isTurnEventBase(value) && typeof value.completedAt === "string" && typeof value.durationMs === "number" && isTokenUsage(value.tokenUsage);
|
|
138
|
-
}
|
|
139
|
-
if (value.type === "turn_failed") {
|
|
140
|
-
return isTurnEventBase(value) && typeof value.failedAt === "string" && typeof value.durationMs === "number" && isTokenUsage(value.tokenUsage) && isNullableString(value.error);
|
|
141
|
-
}
|
|
142
|
-
return false;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
// ../core/dist/workflow/lifecycle.js
|
|
146
|
-
var DEFAULT_WORKFLOW_LIFECYCLE = {
|
|
147
|
-
stateFieldName: "Status",
|
|
148
|
-
activeStates: ["Todo", "In Progress"],
|
|
149
|
-
terminalStates: ["Done"],
|
|
150
|
-
blockerCheckStates: ["Todo"]
|
|
151
|
-
};
|
|
152
|
-
function isStateActive(state, lifecycle) {
|
|
153
|
-
return matchesWorkflowState(state, lifecycle.activeStates);
|
|
154
|
-
}
|
|
155
|
-
function isStateTerminal(state, lifecycle) {
|
|
156
|
-
return matchesWorkflowState(state, lifecycle.terminalStates);
|
|
157
|
-
}
|
|
158
|
-
function matchesWorkflowState(state, candidates) {
|
|
159
|
-
const normalizedState = normalizeWorkflowState(state);
|
|
160
|
-
return candidates.some((candidate) => normalizeWorkflowState(candidate) === normalizedState);
|
|
161
|
-
}
|
|
162
|
-
function normalizeWorkflowState(state) {
|
|
163
|
-
return state.trim().toLowerCase();
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
// ../core/dist/workflow/config.js
|
|
167
|
-
var DEFAULT_CODEX_COMMAND = "codex app-server";
|
|
168
|
-
var DEFAULT_AGENT_COMMAND = DEFAULT_CODEX_COMMAND;
|
|
169
|
-
var DEFAULT_HOOK_TIMEOUT_MS = 6e4;
|
|
170
|
-
var DEFAULT_POLL_INTERVAL_MS = 3e4;
|
|
171
|
-
var DEFAULT_MAX_RETRY_BACKOFF_MS = 3e5;
|
|
172
|
-
var DEFAULT_MAX_DELAY_MS = DEFAULT_MAX_RETRY_BACKOFF_MS;
|
|
173
|
-
var DEFAULT_BASE_DELAY_MS = 1e4;
|
|
174
|
-
var DEFAULT_MAX_TURNS = 20;
|
|
175
|
-
var DEFAULT_READ_TIMEOUT_MS = 5e3;
|
|
176
|
-
var DEFAULT_TURN_TIMEOUT_MS = 36e5;
|
|
177
|
-
var DEFAULT_STALL_TIMEOUT_MS = 3e5;
|
|
178
|
-
var DEFAULT_MAX_CONCURRENT_AGENTS = 10;
|
|
179
|
-
var DEFAULT_WORKFLOW_HOOKS = {
|
|
180
|
-
afterCreate: null,
|
|
181
|
-
beforeRun: null,
|
|
182
|
-
afterRun: null,
|
|
183
|
-
beforeRemove: null,
|
|
184
|
-
timeoutMs: DEFAULT_HOOK_TIMEOUT_MS
|
|
185
|
-
};
|
|
186
|
-
var DEFAULT_WORKFLOW_TRACKER = {
|
|
187
|
-
kind: null,
|
|
188
|
-
endpoint: null,
|
|
189
|
-
apiKey: null,
|
|
190
|
-
projectSlug: null,
|
|
191
|
-
activeStates: DEFAULT_WORKFLOW_LIFECYCLE.activeStates,
|
|
192
|
-
terminalStates: DEFAULT_WORKFLOW_LIFECYCLE.terminalStates,
|
|
193
|
-
projectId: null,
|
|
194
|
-
stateFieldName: DEFAULT_WORKFLOW_LIFECYCLE.stateFieldName,
|
|
195
|
-
priorityFieldName: null,
|
|
196
|
-
blockerCheckStates: DEFAULT_WORKFLOW_LIFECYCLE.blockerCheckStates
|
|
197
|
-
};
|
|
198
|
-
var DEFAULT_WORKFLOW_WORKSPACE = {
|
|
199
|
-
root: null
|
|
200
|
-
};
|
|
201
|
-
var DEFAULT_WORKFLOW_AGENT = {
|
|
202
|
-
maxConcurrentAgents: DEFAULT_MAX_CONCURRENT_AGENTS,
|
|
203
|
-
maxRetryBackoffMs: DEFAULT_MAX_RETRY_BACKOFF_MS,
|
|
204
|
-
maxConcurrentAgentsByState: {},
|
|
205
|
-
maxTurns: DEFAULT_MAX_TURNS,
|
|
206
|
-
retryBaseDelayMs: DEFAULT_BASE_DELAY_MS
|
|
207
|
-
};
|
|
208
|
-
var DEFAULT_WORKFLOW_CODEX = {
|
|
209
|
-
command: DEFAULT_CODEX_COMMAND,
|
|
210
|
-
approvalPolicy: null,
|
|
211
|
-
threadSandbox: null,
|
|
212
|
-
turnSandboxPolicy: null,
|
|
213
|
-
turnTimeoutMs: DEFAULT_TURN_TIMEOUT_MS,
|
|
214
|
-
readTimeoutMs: DEFAULT_READ_TIMEOUT_MS,
|
|
215
|
-
stallTimeoutMs: DEFAULT_STALL_TIMEOUT_MS
|
|
216
|
-
};
|
|
217
|
-
var DEFAULT_WORKFLOW_DEFINITION = {
|
|
218
|
-
promptTemplate: "",
|
|
219
|
-
continuationGuidance: null,
|
|
220
|
-
tracker: DEFAULT_WORKFLOW_TRACKER,
|
|
221
|
-
polling: {
|
|
222
|
-
intervalMs: DEFAULT_POLL_INTERVAL_MS
|
|
223
|
-
},
|
|
224
|
-
workspace: DEFAULT_WORKFLOW_WORKSPACE,
|
|
225
|
-
hooks: DEFAULT_WORKFLOW_HOOKS,
|
|
226
|
-
agent: DEFAULT_WORKFLOW_AGENT,
|
|
227
|
-
codex: DEFAULT_WORKFLOW_CODEX,
|
|
228
|
-
lifecycle: DEFAULT_WORKFLOW_LIFECYCLE,
|
|
229
|
-
format: "default",
|
|
230
|
-
githubProjectId: null,
|
|
231
|
-
agentCommand: DEFAULT_CODEX_COMMAND,
|
|
232
|
-
hookPath: null,
|
|
233
|
-
maxConcurrentByState: {}
|
|
234
|
-
};
|
|
235
|
-
|
|
236
|
-
// ../core/dist/workflow/parser.js
|
|
237
|
-
function parseWorkflowMarkdown(markdown, env = process.env, options = {}) {
|
|
238
|
-
const compatibilityMode = options.compatibilityMode ?? "strict";
|
|
239
|
-
const frontMatterMatch = markdown.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
|
|
240
|
-
if (!frontMatterMatch) {
|
|
241
|
-
if (compatibilityMode === "legacy") {
|
|
242
|
-
return parseLegacyWorkflowMarkdown(markdown);
|
|
243
|
-
}
|
|
244
|
-
throw new Error("WORKFLOW.md must use YAML front matter.");
|
|
245
|
-
}
|
|
246
|
-
const [, rawFrontMatter, rawPromptTemplate = ""] = frontMatterMatch;
|
|
247
|
-
const frontMatter = parseFrontMatter(rawFrontMatter);
|
|
248
|
-
const promptTemplate = rawPromptTemplate.trim();
|
|
249
|
-
const tracker = readRequiredObject(frontMatter, "tracker");
|
|
250
|
-
const polling = readObject(frontMatter, "polling");
|
|
251
|
-
const workspace = readObject(frontMatter, "workspace");
|
|
252
|
-
const hooks = readObject(frontMatter, "hooks");
|
|
253
|
-
const agent = readObject(frontMatter, "agent");
|
|
254
|
-
const codex = readRequiredObject(frontMatter, "codex");
|
|
255
|
-
const trackerKind = readRequiredString(tracker, "kind", env);
|
|
256
|
-
const activeStates = readStringList(tracker, "active_states") ?? DEFAULT_WORKFLOW_TRACKER.activeStates;
|
|
257
|
-
const terminalStates = readStringList(tracker, "terminal_states") ?? DEFAULT_WORKFLOW_TRACKER.terminalStates;
|
|
258
|
-
const blockerCheckStates = readStringList(tracker, "blocker_check_states") ?? DEFAULT_WORKFLOW_TRACKER.blockerCheckStates;
|
|
259
|
-
const maxConcurrentAgentsByState = readNumberMap(agent, "max_concurrent_agents_by_state");
|
|
260
|
-
const command = readOptionalString(codex, "command", env) ?? DEFAULT_AGENT_COMMAND;
|
|
261
|
-
const parsed = {
|
|
262
|
-
promptTemplate,
|
|
263
|
-
continuationGuidance: readOptionalWorkflowString(frontMatter, "continuationGuidance", "continuation_guidance", env),
|
|
264
|
-
tracker: {
|
|
265
|
-
kind: trackerKind,
|
|
266
|
-
endpoint: readOptionalString(tracker, "endpoint", env),
|
|
267
|
-
apiKey: readOptionalString(tracker, "api_key", env),
|
|
268
|
-
projectSlug: readOptionalString(tracker, "project_slug", env),
|
|
269
|
-
activeStates,
|
|
270
|
-
terminalStates,
|
|
271
|
-
projectId: readOptionalString(tracker, "project_id", env),
|
|
272
|
-
stateFieldName: readOptionalString(tracker, "state_field", env) ?? DEFAULT_WORKFLOW_TRACKER.stateFieldName,
|
|
273
|
-
priorityFieldName: readOptionalString(tracker, "priority_field", env),
|
|
274
|
-
blockerCheckStates
|
|
275
|
-
},
|
|
276
|
-
polling: {
|
|
277
|
-
intervalMs: readOptionalIntegerLike(polling, "interval_ms") ?? DEFAULT_POLL_INTERVAL_MS
|
|
278
|
-
},
|
|
279
|
-
workspace: {
|
|
280
|
-
root: readOptionalString(workspace, "root", env)
|
|
281
|
-
},
|
|
282
|
-
hooks: {
|
|
283
|
-
afterCreate: readOptionalString(hooks, "after_create", env),
|
|
284
|
-
beforeRun: readOptionalString(hooks, "before_run", env),
|
|
285
|
-
afterRun: readOptionalString(hooks, "after_run", env),
|
|
286
|
-
beforeRemove: readOptionalString(hooks, "before_remove", env),
|
|
287
|
-
timeoutMs: readOptionalIntegerLike(hooks, "timeout_ms") ?? DEFAULT_HOOK_TIMEOUT_MS
|
|
288
|
-
},
|
|
289
|
-
agent: {
|
|
290
|
-
maxConcurrentAgents: readOptionalIntegerLike(agent, "max_concurrent_agents") ?? DEFAULT_MAX_CONCURRENT_AGENTS,
|
|
291
|
-
maxRetryBackoffMs: readOptionalIntegerLike(agent, "max_retry_backoff_ms") ?? DEFAULT_MAX_RETRY_BACKOFF_MS,
|
|
292
|
-
maxConcurrentAgentsByState,
|
|
293
|
-
maxTurns: readOptionalIntegerLike(agent, "max_turns") ?? DEFAULT_MAX_TURNS,
|
|
294
|
-
retryBaseDelayMs: readOptionalIntegerLike(agent, "retry_base_delay_ms") ?? DEFAULT_BASE_DELAY_MS
|
|
295
|
-
},
|
|
296
|
-
codex: {
|
|
297
|
-
command,
|
|
298
|
-
approvalPolicy: readOptionalString(codex, "approval_policy", env),
|
|
299
|
-
threadSandbox: readOptionalString(codex, "thread_sandbox", env),
|
|
300
|
-
turnSandboxPolicy: readOptionalString(codex, "turn_sandbox_policy", env),
|
|
301
|
-
turnTimeoutMs: readOptionalIntegerLike(codex, "turn_timeout_ms") ?? DEFAULT_TURN_TIMEOUT_MS,
|
|
302
|
-
readTimeoutMs: readOptionalIntegerLike(codex, "read_timeout_ms") ?? DEFAULT_READ_TIMEOUT_MS,
|
|
303
|
-
stallTimeoutMs: readOptionalIntegerLike(codex, "stall_timeout_ms") ?? DEFAULT_STALL_TIMEOUT_MS
|
|
304
|
-
},
|
|
305
|
-
lifecycle: {
|
|
306
|
-
stateFieldName: readOptionalString(tracker, "state_field", env) ?? DEFAULT_WORKFLOW_TRACKER.stateFieldName,
|
|
307
|
-
activeStates,
|
|
308
|
-
terminalStates,
|
|
309
|
-
blockerCheckStates
|
|
310
|
-
},
|
|
311
|
-
format: "front-matter",
|
|
312
|
-
githubProjectId: readOptionalString(tracker, "project_id", env),
|
|
313
|
-
agentCommand: command,
|
|
314
|
-
hookPath: readOptionalString(hooks, "after_create", env),
|
|
315
|
-
maxConcurrentByState: maxConcurrentAgentsByState
|
|
316
|
-
};
|
|
317
|
-
return parsed;
|
|
318
|
-
}
|
|
319
|
-
function parseLegacyWorkflowMarkdown(markdown) {
|
|
320
|
-
const promptGuidelines = matchOptionalSection(markdown, "Prompt Guidelines") ?? "";
|
|
321
|
-
return {
|
|
322
|
-
...DEFAULT_WORKFLOW_DEFINITION,
|
|
323
|
-
promptTemplate: promptGuidelines,
|
|
324
|
-
format: "legacy-sectioned"
|
|
325
|
-
};
|
|
326
|
-
}
|
|
327
|
-
function parseFrontMatter(frontMatter) {
|
|
328
|
-
const lines = frontMatter.replace(/\r\n/g, "\n").split("\n");
|
|
329
|
-
const [value] = parseBlock(lines, 0, 0);
|
|
330
|
-
if (!value || Array.isArray(value) || typeof value !== "object") {
|
|
331
|
-
throw new Error("Workflow front matter must be a YAML object.");
|
|
332
|
-
}
|
|
333
|
-
return value;
|
|
334
|
-
}
|
|
335
|
-
function parseBlock(lines, startIndex, indent) {
|
|
336
|
-
let index = startIndex;
|
|
337
|
-
let collectionType = null;
|
|
338
|
-
const arrayValues = [];
|
|
339
|
-
const objectValues = {};
|
|
340
|
-
while (index < lines.length) {
|
|
341
|
-
const line = lines[index] ?? "";
|
|
342
|
-
if (!line.trim()) {
|
|
343
|
-
index += 1;
|
|
344
|
-
continue;
|
|
345
|
-
}
|
|
346
|
-
const lineIndent = countIndent(line);
|
|
347
|
-
if (lineIndent < indent) {
|
|
348
|
-
break;
|
|
349
|
-
}
|
|
350
|
-
if (lineIndent > indent) {
|
|
351
|
-
throw new Error(`Invalid workflow front matter indentation near "${line.trim()}".`);
|
|
352
|
-
}
|
|
353
|
-
const trimmed = line.trim();
|
|
354
|
-
if (trimmed.startsWith("- ")) {
|
|
355
|
-
if (collectionType === "object") {
|
|
356
|
-
throw new Error("Cannot mix array and object values in workflow front matter.");
|
|
357
|
-
}
|
|
358
|
-
collectionType = "array";
|
|
359
|
-
const itemText = trimmed.slice(2).trim();
|
|
360
|
-
if (itemText === "|" || itemText === "|-") {
|
|
361
|
-
const [multiline, nextIndex3] = parseMultilineScalar(lines, index + 1, indent + 2);
|
|
362
|
-
arrayValues.push(multiline);
|
|
363
|
-
index = nextIndex3;
|
|
364
|
-
continue;
|
|
365
|
-
}
|
|
366
|
-
if (itemText) {
|
|
367
|
-
arrayValues.push(parseScalar(itemText));
|
|
368
|
-
index += 1;
|
|
369
|
-
continue;
|
|
370
|
-
}
|
|
371
|
-
const [child2, nextIndex2] = parseBlock(lines, index + 1, indent + 2);
|
|
372
|
-
arrayValues.push(child2);
|
|
373
|
-
index = nextIndex2;
|
|
374
|
-
continue;
|
|
375
|
-
}
|
|
376
|
-
if (collectionType === "array") {
|
|
377
|
-
throw new Error("Cannot mix object and array values in workflow front matter.");
|
|
378
|
-
}
|
|
379
|
-
collectionType = "object";
|
|
380
|
-
const separatorIndex = trimmed.indexOf(":");
|
|
381
|
-
if (separatorIndex < 0) {
|
|
382
|
-
throw new Error(`Invalid workflow front matter line "${trimmed}".`);
|
|
383
|
-
}
|
|
384
|
-
const key = trimmed.slice(0, separatorIndex).trim();
|
|
385
|
-
const remainder = trimmed.slice(separatorIndex + 1).trim();
|
|
386
|
-
if (remainder === "|" || remainder === "|-") {
|
|
387
|
-
const [multiline, nextIndex2] = parseMultilineScalar(lines, index + 1, indent + 2);
|
|
388
|
-
objectValues[key] = multiline;
|
|
389
|
-
index = nextIndex2;
|
|
390
|
-
continue;
|
|
391
|
-
}
|
|
392
|
-
if (remainder) {
|
|
393
|
-
objectValues[key] = parseScalar(remainder);
|
|
394
|
-
index += 1;
|
|
395
|
-
continue;
|
|
396
|
-
}
|
|
397
|
-
const [child, nextIndex] = parseBlock(lines, index + 1, indent + 2);
|
|
398
|
-
objectValues[key] = child;
|
|
399
|
-
index = nextIndex;
|
|
400
|
-
}
|
|
401
|
-
return [collectionType === "array" ? arrayValues : objectValues, index];
|
|
402
|
-
}
|
|
403
|
-
function parseMultilineScalar(lines, startIndex, indent) {
|
|
404
|
-
let index = startIndex;
|
|
405
|
-
const collected = [];
|
|
406
|
-
while (index < lines.length) {
|
|
407
|
-
const line = lines[index] ?? "";
|
|
408
|
-
if (!line.trim()) {
|
|
409
|
-
collected.push("");
|
|
410
|
-
index += 1;
|
|
411
|
-
continue;
|
|
412
|
-
}
|
|
413
|
-
const lineIndent = countIndent(line);
|
|
414
|
-
if (lineIndent < indent) {
|
|
415
|
-
break;
|
|
416
|
-
}
|
|
417
|
-
collected.push(line.slice(indent));
|
|
418
|
-
index += 1;
|
|
419
|
-
}
|
|
420
|
-
return [collected.join("\n").trimEnd(), index];
|
|
421
|
-
}
|
|
422
|
-
function countIndent(line) {
|
|
423
|
-
return line.match(/^ */)?.[0].length ?? 0;
|
|
424
|
-
}
|
|
425
|
-
function parseScalar(value) {
|
|
426
|
-
if (value === "null")
|
|
427
|
-
return null;
|
|
428
|
-
if (value === "true")
|
|
429
|
-
return true;
|
|
430
|
-
if (value === "false")
|
|
431
|
-
return false;
|
|
432
|
-
if (/^-?\d+$/.test(value))
|
|
433
|
-
return Number.parseInt(value, 10);
|
|
434
|
-
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
|
435
|
-
return value.slice(1, -1);
|
|
436
|
-
}
|
|
437
|
-
return value;
|
|
438
|
-
}
|
|
439
|
-
function readObject(input, key) {
|
|
440
|
-
const value = input[key];
|
|
441
|
-
if (value === void 0 || value === null) {
|
|
442
|
-
return {};
|
|
443
|
-
}
|
|
444
|
-
if (typeof value !== "object" || Array.isArray(value)) {
|
|
445
|
-
throw new Error(`Workflow front matter field "${key}" must be an object.`);
|
|
446
|
-
}
|
|
447
|
-
return value;
|
|
448
|
-
}
|
|
449
|
-
function readRequiredObject(input, key) {
|
|
450
|
-
if (!(key in input)) {
|
|
451
|
-
throw new Error(`Workflow front matter field "${key}" is required.`);
|
|
452
|
-
}
|
|
453
|
-
return readObject(input, key);
|
|
454
|
-
}
|
|
455
|
-
function readOptionalString(input, key, env) {
|
|
456
|
-
const value = input[key];
|
|
457
|
-
if (value === void 0 || value === null) {
|
|
458
|
-
return null;
|
|
459
|
-
}
|
|
460
|
-
if (typeof value !== "string") {
|
|
461
|
-
throw new Error(`Workflow front matter field "${key}" must be a string.`);
|
|
462
|
-
}
|
|
463
|
-
return resolveEnvironmentValue(value, env);
|
|
464
|
-
}
|
|
465
|
-
function readOptionalWorkflowString(input, primaryKey, fallbackKey, env) {
|
|
466
|
-
return readOptionalString(input, primaryKey, env) ?? readOptionalString(input, fallbackKey, env);
|
|
467
|
-
}
|
|
468
|
-
function readRequiredString(input, key, env) {
|
|
469
|
-
const value = readOptionalString(input, key, env);
|
|
470
|
-
if (!value) {
|
|
471
|
-
throw new Error(`Workflow front matter field "${key}" is required.`);
|
|
472
|
-
}
|
|
473
|
-
return value;
|
|
474
|
-
}
|
|
475
|
-
function readStringList(input, key) {
|
|
476
|
-
const value = input[key];
|
|
477
|
-
if (value === void 0 || value === null) {
|
|
478
|
-
return void 0;
|
|
479
|
-
}
|
|
480
|
-
if (typeof value === "string") {
|
|
481
|
-
return value.split(",").map((entry) => entry.trim()).filter(Boolean);
|
|
482
|
-
}
|
|
483
|
-
if (!Array.isArray(value) || value.some((entry) => typeof entry !== "string")) {
|
|
484
|
-
throw new Error(`Workflow front matter field "${key}" must be an array of strings or comma-separated string.`);
|
|
485
|
-
}
|
|
486
|
-
return value;
|
|
487
|
-
}
|
|
488
|
-
function readOptionalIntegerLike(input, key) {
|
|
489
|
-
const value = input[key];
|
|
490
|
-
if (value === void 0 || value === null) {
|
|
491
|
-
return null;
|
|
492
|
-
}
|
|
493
|
-
if (typeof value === "number") {
|
|
494
|
-
return value;
|
|
495
|
-
}
|
|
496
|
-
if (typeof value === "string" && /^-?\d+$/.test(value)) {
|
|
497
|
-
return Number.parseInt(value, 10);
|
|
498
|
-
}
|
|
499
|
-
throw new Error(`Workflow front matter field "${key}" must be an integer.`);
|
|
500
|
-
}
|
|
501
|
-
function readNumberMap(input, key) {
|
|
502
|
-
const value = input[key];
|
|
503
|
-
if (value === void 0 || value === null) {
|
|
504
|
-
return {};
|
|
505
|
-
}
|
|
506
|
-
if (typeof value !== "object" || Array.isArray(value)) {
|
|
507
|
-
throw new Error(`Workflow front matter field "${key}" must be an object.`);
|
|
508
|
-
}
|
|
509
|
-
const result = {};
|
|
510
|
-
for (const [entryKey, entryValue] of Object.entries(value)) {
|
|
511
|
-
if (typeof entryValue === "number") {
|
|
512
|
-
result[entryKey] = entryValue;
|
|
513
|
-
continue;
|
|
514
|
-
}
|
|
515
|
-
if (typeof entryValue === "string" && /^\d+$/.test(entryValue)) {
|
|
516
|
-
result[entryKey] = Number.parseInt(entryValue, 10);
|
|
517
|
-
continue;
|
|
518
|
-
}
|
|
519
|
-
throw new Error(`Workflow front matter field "${key}.${entryKey}" must be an integer.`);
|
|
520
|
-
}
|
|
521
|
-
return result;
|
|
522
|
-
}
|
|
523
|
-
function resolveEnvironmentValue(value, env) {
|
|
524
|
-
const envTokenMatch = value.match(/^(?:env:)?([A-Z0-9_]+)$/);
|
|
525
|
-
if (value.startsWith("env:") && envTokenMatch) {
|
|
526
|
-
const resolved = env[envTokenMatch[1]];
|
|
527
|
-
if (!resolved) {
|
|
528
|
-
throw new Error(`Workflow front matter requires environment variable ${envTokenMatch[1]}.`);
|
|
529
|
-
}
|
|
530
|
-
return resolved;
|
|
531
|
-
}
|
|
532
|
-
return value.replace(/\$\{([A-Z0-9_]+)\}/g, (_, name) => {
|
|
533
|
-
const resolved = env[name];
|
|
534
|
-
if (!resolved) {
|
|
535
|
-
throw new Error(`Workflow front matter requires environment variable ${name}.`);
|
|
536
|
-
}
|
|
537
|
-
return resolved;
|
|
538
|
-
});
|
|
539
|
-
}
|
|
540
|
-
function matchOptionalSection(markdown, heading) {
|
|
541
|
-
const escapedHeading = heading.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
542
|
-
const pattern = new RegExp(`## ${escapedHeading}\\n\\n([\\s\\S]*?)(?=\\n## |$)`);
|
|
543
|
-
const match = markdown.match(pattern);
|
|
544
|
-
return match?.[1]?.trim() ?? null;
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
// ../core/dist/workflow/loader.js
|
|
548
|
-
import { createHash } from "crypto";
|
|
549
|
-
import { access, readFile, stat } from "fs/promises";
|
|
550
|
-
import { constants } from "fs";
|
|
551
|
-
var WorkflowConfigStore = class {
|
|
552
|
-
cache = /* @__PURE__ */ new Map();
|
|
553
|
-
async load(workflowPath, env = process.env) {
|
|
554
|
-
await access(workflowPath, constants.R_OK);
|
|
555
|
-
const fileStat = await stat(workflowPath);
|
|
556
|
-
const fingerprint = `${fileStat.mtimeMs}:${fileStat.size}`;
|
|
557
|
-
const cached = this.cache.get(workflowPath);
|
|
558
|
-
if (cached && cached.fingerprint === fingerprint) {
|
|
559
|
-
return toWorkflowResolution(workflowPath, cached.workflow, {
|
|
560
|
-
isValid: true,
|
|
561
|
-
usedLastKnownGood: false,
|
|
562
|
-
validationError: null
|
|
563
|
-
});
|
|
564
|
-
}
|
|
565
|
-
const markdown = await readFile(workflowPath, "utf8");
|
|
566
|
-
try {
|
|
567
|
-
const workflow = parseWorkflowMarkdown(markdown, env);
|
|
568
|
-
this.cache.set(workflowPath, {
|
|
569
|
-
fingerprint,
|
|
570
|
-
workflow,
|
|
571
|
-
loadedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
572
|
-
});
|
|
573
|
-
return toWorkflowResolution(workflowPath, workflow, {
|
|
574
|
-
isValid: true,
|
|
575
|
-
usedLastKnownGood: false,
|
|
576
|
-
validationError: null
|
|
577
|
-
});
|
|
578
|
-
} catch (error) {
|
|
579
|
-
if (cached) {
|
|
580
|
-
return toWorkflowResolution(workflowPath, cached.workflow, {
|
|
581
|
-
isValid: false,
|
|
582
|
-
usedLastKnownGood: true,
|
|
583
|
-
validationError: error instanceof Error ? error.message : "Invalid workflow definition."
|
|
584
|
-
});
|
|
585
|
-
}
|
|
586
|
-
throw error;
|
|
587
|
-
}
|
|
588
|
-
}
|
|
589
|
-
};
|
|
590
|
-
function createDefaultWorkflowResolution() {
|
|
591
|
-
return createInvalidWorkflowResolution(null, "missing_workflow_file");
|
|
592
|
-
}
|
|
593
|
-
function createInvalidWorkflowResolution(workflowPath, validationError) {
|
|
594
|
-
return toWorkflowResolution(workflowPath, DEFAULT_WORKFLOW_DEFINITION, {
|
|
595
|
-
isValid: false,
|
|
596
|
-
usedLastKnownGood: false,
|
|
597
|
-
validationError
|
|
598
|
-
});
|
|
599
|
-
}
|
|
600
|
-
function toWorkflowResolution(workflowPath, workflow, metadata) {
|
|
601
|
-
return {
|
|
602
|
-
workflowPath,
|
|
603
|
-
workflow,
|
|
604
|
-
lifecycle: workflow.lifecycle,
|
|
605
|
-
promptTemplate: workflow.promptTemplate,
|
|
606
|
-
agentCommand: workflow.agentCommand,
|
|
607
|
-
hookPath: workflow.hookPath ?? "",
|
|
608
|
-
isValid: metadata.isValid,
|
|
609
|
-
usedLastKnownGood: metadata.usedLastKnownGood,
|
|
610
|
-
validationError: metadata.validationError
|
|
611
|
-
};
|
|
612
|
-
}
|
|
613
|
-
|
|
614
|
-
// ../core/dist/workflow/render.js
|
|
615
|
-
import { Liquid, ParseError, RenderError, TokenizationError, UndefinedVariableError } from "liquidjs";
|
|
616
|
-
function buildPromptVariables(issue, options) {
|
|
617
|
-
return {
|
|
618
|
-
issue: {
|
|
619
|
-
id: issue.id,
|
|
620
|
-
identifier: issue.identifier,
|
|
621
|
-
number: issue.number,
|
|
622
|
-
title: issue.title,
|
|
623
|
-
description: issue.description,
|
|
624
|
-
priority: issue.priority,
|
|
625
|
-
url: issue.url,
|
|
626
|
-
state: issue.state,
|
|
627
|
-
labels: issue.labels,
|
|
628
|
-
blocked_by: issue.blockedBy,
|
|
629
|
-
branch_name: issue.branchName,
|
|
630
|
-
created_at: issue.createdAt,
|
|
631
|
-
updated_at: issue.updatedAt,
|
|
632
|
-
repository: `${issue.repository.owner}/${issue.repository.name}`
|
|
633
|
-
},
|
|
634
|
-
attempt: options.attempt
|
|
635
|
-
};
|
|
636
|
-
}
|
|
637
|
-
var STRICT_LIQUID_ENGINE = new Liquid({
|
|
638
|
-
strictVariables: true,
|
|
639
|
-
strictFilters: true,
|
|
640
|
-
ownPropertyOnly: true
|
|
641
|
-
});
|
|
642
|
-
function renderPrompt(template, variables, options = {}) {
|
|
643
|
-
const strict = options.strict ?? true;
|
|
644
|
-
if (!strict) {
|
|
645
|
-
return renderLegacyPrompt(template, variables);
|
|
646
|
-
}
|
|
647
|
-
try {
|
|
648
|
-
return STRICT_LIQUID_ENGINE.parseAndRenderSync(template, variables);
|
|
649
|
-
} catch (error) {
|
|
650
|
-
throw normalizeTemplateError(error);
|
|
651
|
-
}
|
|
652
|
-
}
|
|
653
|
-
function normalizeTemplateError(error) {
|
|
654
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
655
|
-
if (error instanceof UndefinedVariableError || error instanceof RenderError || error instanceof ParseError && message.startsWith("undefined filter:")) {
|
|
656
|
-
return new Error(`template_render_error: ${message}`, { cause: error });
|
|
657
|
-
}
|
|
658
|
-
if (error instanceof ParseError || error instanceof TokenizationError) {
|
|
659
|
-
return new Error(`template_parse_error: ${message}`, { cause: error });
|
|
660
|
-
}
|
|
661
|
-
return new Error(`template_render_error: ${message}`, { cause: error });
|
|
662
|
-
}
|
|
663
|
-
function flattenVariables(obj, prefix = "") {
|
|
664
|
-
const result = /* @__PURE__ */ new Map();
|
|
665
|
-
for (const [key, value] of Object.entries(obj)) {
|
|
666
|
-
const fullKey = prefix ? `${prefix}.${key}` : key;
|
|
667
|
-
if (value !== null && typeof value === "object" && !Array.isArray(value)) {
|
|
668
|
-
for (const [nestedKey, nestedValue] of flattenVariables(value, fullKey)) {
|
|
669
|
-
result.set(nestedKey, nestedValue);
|
|
670
|
-
}
|
|
671
|
-
} else {
|
|
672
|
-
result.set(fullKey, value);
|
|
673
|
-
}
|
|
674
|
-
}
|
|
675
|
-
return result;
|
|
676
|
-
}
|
|
677
|
-
function renderLegacyPrompt(template, variables) {
|
|
678
|
-
const flatVars = flattenVariables(variables);
|
|
679
|
-
return template.replace(/\{\{([a-zA-Z_][a-zA-Z0-9_.]*)\}\}/g, (match, key) => {
|
|
680
|
-
const value = flatVars.get(key);
|
|
681
|
-
if (value === void 0) {
|
|
682
|
-
return match;
|
|
683
|
-
}
|
|
684
|
-
if (value === null) {
|
|
685
|
-
return "";
|
|
686
|
-
}
|
|
687
|
-
return String(value);
|
|
688
|
-
});
|
|
689
|
-
}
|
|
690
|
-
|
|
691
|
-
// ../core/dist/orchestration/retry-policy.js
|
|
692
|
-
function calculateRetryDelay(attempt, options = {}) {
|
|
693
|
-
const baseDelayMs = options.baseDelayMs ?? DEFAULT_BASE_DELAY_MS;
|
|
694
|
-
const maxDelayMs = options.maxDelayMs ?? DEFAULT_MAX_DELAY_MS;
|
|
695
|
-
const normalizedAttempt = Math.max(1, attempt);
|
|
696
|
-
const delay2 = baseDelayMs * 2 ** (normalizedAttempt - 1);
|
|
697
|
-
return Math.min(delay2, maxDelayMs);
|
|
698
|
-
}
|
|
699
|
-
function scheduleRetryAt(now, attempt, options = {}) {
|
|
700
|
-
return new Date(now.getTime() + calculateRetryDelay(attempt, options));
|
|
701
|
-
}
|
|
702
|
-
|
|
703
|
-
// ../core/dist/workspace/env-file.js
|
|
704
|
-
import { existsSync, readFileSync } from "fs";
|
|
705
|
-
function readEnvFile(path) {
|
|
706
|
-
if (!existsSync(path)) {
|
|
707
|
-
return {};
|
|
708
|
-
}
|
|
709
|
-
return readFileSync(path, "utf8").split("\n").map((line) => line.trim()).filter((line) => line && !line.startsWith("#") && line.includes("=")).reduce((result, line) => {
|
|
710
|
-
const separatorIndex = line.indexOf("=");
|
|
711
|
-
const key = line.slice(0, separatorIndex).trim();
|
|
712
|
-
const value = line.slice(separatorIndex + 1).trim();
|
|
713
|
-
if (key) {
|
|
714
|
-
result[key] = value;
|
|
715
|
-
}
|
|
716
|
-
return result;
|
|
717
|
-
}, {});
|
|
718
|
-
}
|
|
719
|
-
|
|
720
|
-
// ../core/dist/workspace/safety.js
|
|
721
|
-
import { resolve } from "path";
|
|
722
|
-
|
|
723
|
-
// ../core/dist/workspace/identity.js
|
|
724
|
-
import { resolve as resolve2, join } from "path";
|
|
725
|
-
import { createHash as createHash2 } from "crypto";
|
|
726
|
-
function deriveIssueWorkspaceKey(identity, issueIdentifier) {
|
|
727
|
-
if (issueIdentifier) {
|
|
728
|
-
return deriveIssueWorkspaceKeyFromIdentifier(issueIdentifier);
|
|
729
|
-
}
|
|
730
|
-
return deriveLegacyIssueWorkspaceKey(identity);
|
|
731
|
-
}
|
|
732
|
-
function deriveIssueWorkspaceKeyFromIdentifier(issueIdentifier) {
|
|
733
|
-
const sanitized = issueIdentifier.replace(/[^A-Za-z0-9._-]+/g, "_").replace(/^_+|_+$/g, "");
|
|
734
|
-
if (!sanitized || /^[.]+$/.test(sanitized)) {
|
|
735
|
-
return "issue";
|
|
736
|
-
}
|
|
737
|
-
return sanitized;
|
|
738
|
-
}
|
|
739
|
-
function deriveLegacyIssueWorkspaceKey(identity) {
|
|
740
|
-
const input = [
|
|
741
|
-
identity.projectId,
|
|
742
|
-
identity.adapter,
|
|
743
|
-
identity.issueSubjectId
|
|
744
|
-
].join(":");
|
|
745
|
-
return createHash2("sha256").update(input).digest("hex").slice(0, 16);
|
|
746
|
-
}
|
|
747
|
-
function resolveIssueWorkspaceDirectory(projectDirectory, workspaceKey) {
|
|
748
|
-
const normalizedProjectDirectory = resolve2(projectDirectory);
|
|
749
|
-
const candidate = resolve2(normalizedProjectDirectory, "issues", workspaceKey);
|
|
750
|
-
if (!candidate.startsWith(`${normalizedProjectDirectory}/`)) {
|
|
751
|
-
throw new Error("Issue workspace path escapes the configured project directory.");
|
|
752
|
-
}
|
|
753
|
-
return candidate;
|
|
754
|
-
}
|
|
755
|
-
|
|
756
|
-
// ../core/dist/workspace/hooks.js
|
|
757
|
-
import { spawn } from "child_process";
|
|
758
|
-
var DEFAULT_HOOK_TIMEOUT_MS2 = 6e4;
|
|
759
|
-
async function executeHook(options) {
|
|
760
|
-
const { kind, command, cwd, env, timeoutMs } = options;
|
|
761
|
-
const start = Date.now();
|
|
762
|
-
const normalizedCommand = normalizeHookCommand(command);
|
|
763
|
-
return new Promise((resolveResult) => {
|
|
764
|
-
let timedOut = false;
|
|
765
|
-
let timer = null;
|
|
766
|
-
const child = spawn("bash", ["-lc", normalizedCommand], {
|
|
767
|
-
cwd,
|
|
768
|
-
env: { ...process.env, ...env },
|
|
769
|
-
stdio: "pipe"
|
|
770
|
-
});
|
|
771
|
-
const stderrChunks = [];
|
|
772
|
-
child.stderr?.on("data", (chunk) => {
|
|
773
|
-
stderrChunks.push(chunk);
|
|
774
|
-
});
|
|
775
|
-
if (timeoutMs > 0) {
|
|
776
|
-
timer = setTimeout(() => {
|
|
777
|
-
timedOut = true;
|
|
778
|
-
child.kill("SIGTERM");
|
|
779
|
-
setTimeout(() => {
|
|
780
|
-
try {
|
|
781
|
-
child.kill("SIGKILL");
|
|
782
|
-
} catch {
|
|
783
|
-
}
|
|
784
|
-
}, 5e3);
|
|
785
|
-
}, timeoutMs);
|
|
786
|
-
}
|
|
787
|
-
child.on("close", (code) => {
|
|
788
|
-
if (timer) {
|
|
789
|
-
clearTimeout(timer);
|
|
790
|
-
}
|
|
791
|
-
const durationMs = Date.now() - start;
|
|
792
|
-
const stderr = Buffer.concat(stderrChunks).toString("utf8").trim();
|
|
793
|
-
if (timedOut) {
|
|
794
|
-
resolveResult({
|
|
795
|
-
kind,
|
|
796
|
-
outcome: "timeout",
|
|
797
|
-
exitCode: code,
|
|
798
|
-
durationMs,
|
|
799
|
-
error: `Hook "${kind}" timed out after ${timeoutMs}ms`
|
|
800
|
-
});
|
|
801
|
-
return;
|
|
802
|
-
}
|
|
803
|
-
if (code !== 0) {
|
|
804
|
-
resolveResult({
|
|
805
|
-
kind,
|
|
806
|
-
outcome: "failure",
|
|
807
|
-
exitCode: code,
|
|
808
|
-
durationMs,
|
|
809
|
-
error: stderr || `Hook "${kind}" exited with code ${code}`
|
|
810
|
-
});
|
|
811
|
-
return;
|
|
812
|
-
}
|
|
813
|
-
resolveResult({
|
|
814
|
-
kind,
|
|
815
|
-
outcome: "success",
|
|
816
|
-
exitCode: 0,
|
|
817
|
-
durationMs,
|
|
818
|
-
error: null
|
|
819
|
-
});
|
|
820
|
-
});
|
|
821
|
-
child.on("error", (err) => {
|
|
822
|
-
if (timer) {
|
|
823
|
-
clearTimeout(timer);
|
|
824
|
-
}
|
|
825
|
-
resolveResult({
|
|
826
|
-
kind,
|
|
827
|
-
outcome: "failure",
|
|
828
|
-
exitCode: null,
|
|
829
|
-
durationMs: Date.now() - start,
|
|
830
|
-
error: err.message
|
|
831
|
-
});
|
|
832
|
-
});
|
|
833
|
-
});
|
|
834
|
-
}
|
|
835
|
-
function buildHookEnv(context) {
|
|
836
|
-
const env = {
|
|
837
|
-
SYMPHONY_PROJECT_ID: context.projectId,
|
|
838
|
-
SYMPHONY_ISSUE_WORKSPACE_KEY: context.workspaceKey,
|
|
839
|
-
SYMPHONY_ISSUE_SUBJECT_ID: context.issueSubjectId,
|
|
840
|
-
SYMPHONY_ISSUE_IDENTIFIER: context.issueIdentifier,
|
|
841
|
-
SYMPHONY_WORKSPACE_PATH: context.workspacePath,
|
|
842
|
-
SYMPHONY_REPOSITORY_PATH: context.repositoryPath
|
|
843
|
-
};
|
|
844
|
-
if (context.runId) {
|
|
845
|
-
env.SYMPHONY_RUN_ID = context.runId;
|
|
846
|
-
}
|
|
847
|
-
if (context.state) {
|
|
848
|
-
env.SYMPHONY_ISSUE_STATE = context.state;
|
|
849
|
-
}
|
|
850
|
-
return env;
|
|
851
|
-
}
|
|
852
|
-
function resolveHookCommand(hooks, kind) {
|
|
853
|
-
switch (kind) {
|
|
854
|
-
case "after_create":
|
|
855
|
-
return hooks.afterCreate;
|
|
856
|
-
case "before_run":
|
|
857
|
-
return hooks.beforeRun;
|
|
858
|
-
case "after_run":
|
|
859
|
-
return hooks.afterRun;
|
|
860
|
-
case "before_remove":
|
|
861
|
-
return hooks.beforeRemove;
|
|
862
|
-
}
|
|
863
|
-
}
|
|
864
|
-
async function executeWorkspaceHook(options) {
|
|
865
|
-
const hookCommand = resolveHookCommand(options.hooks, options.kind);
|
|
866
|
-
if (!hookCommand) {
|
|
867
|
-
return {
|
|
868
|
-
kind: options.kind,
|
|
869
|
-
outcome: "skipped",
|
|
870
|
-
exitCode: null,
|
|
871
|
-
durationMs: 0,
|
|
872
|
-
error: null
|
|
873
|
-
};
|
|
874
|
-
}
|
|
875
|
-
return executeHook({
|
|
876
|
-
kind: options.kind,
|
|
877
|
-
command: hookCommand,
|
|
878
|
-
cwd: options.repositoryPath,
|
|
879
|
-
env: options.env,
|
|
880
|
-
timeoutMs: options.timeoutMs ?? DEFAULT_HOOK_TIMEOUT_MS2
|
|
881
|
-
});
|
|
882
|
-
}
|
|
883
|
-
function normalizeHookCommand(command) {
|
|
884
|
-
const trimmed = command.trim();
|
|
885
|
-
if (trimmed.includes("/") && !trimmed.startsWith("/") && !trimmed.startsWith("./") && !trimmed.startsWith("../") && !/\s/.test(trimmed)) {
|
|
886
|
-
return `bash ./${trimmed}`;
|
|
887
|
-
}
|
|
888
|
-
return command;
|
|
889
|
-
}
|
|
890
|
-
|
|
891
|
-
// ../core/dist/observability/snapshot-builder.js
|
|
892
|
-
function buildProjectSnapshot(input) {
|
|
893
|
-
const { project, activeRuns, allRuns, summary, lastTickAt, lastError, rateLimits } = input;
|
|
894
|
-
return {
|
|
895
|
-
projectId: project.projectId,
|
|
896
|
-
slug: project.slug,
|
|
897
|
-
tracker: {
|
|
898
|
-
adapter: project.tracker.adapter,
|
|
899
|
-
bindingId: project.tracker.bindingId
|
|
900
|
-
},
|
|
901
|
-
lastTickAt,
|
|
902
|
-
health: lastError ? "degraded" : activeRuns.length > 0 ? "running" : "idle",
|
|
903
|
-
summary: {
|
|
904
|
-
dispatched: summary.dispatched,
|
|
905
|
-
suppressed: summary.suppressed,
|
|
906
|
-
recovered: summary.recovered,
|
|
907
|
-
activeRuns: activeRuns.length
|
|
908
|
-
},
|
|
909
|
-
activeRuns: activeRuns.map((run) => ({
|
|
910
|
-
runId: run.runId,
|
|
911
|
-
issueIdentifier: run.issueIdentifier,
|
|
912
|
-
issueState: run.issueState,
|
|
913
|
-
status: run.status,
|
|
914
|
-
retryKind: run.retryKind,
|
|
915
|
-
port: run.port,
|
|
916
|
-
runtimeSession: run.runtimeSession ?? null,
|
|
917
|
-
// New fields from live worker data
|
|
918
|
-
processId: run.processId ?? null,
|
|
919
|
-
turnCount: run.turnCount,
|
|
920
|
-
startedAt: run.startedAt ?? null,
|
|
921
|
-
lastEvent: run.lastEvent ?? null,
|
|
922
|
-
lastEventAt: run.lastEventAt ?? null,
|
|
923
|
-
executionPhase: run.executionPhase ?? null,
|
|
924
|
-
runPhase: run.runPhase ?? null,
|
|
925
|
-
tokenUsage: run.tokenUsage
|
|
926
|
-
})),
|
|
927
|
-
retryQueue: activeRuns.filter((run) => run.status === "retrying" && run.retryKind).map((run) => ({
|
|
928
|
-
runId: run.runId,
|
|
929
|
-
issueIdentifier: run.issueIdentifier,
|
|
930
|
-
retryKind: run.retryKind ?? "failure",
|
|
931
|
-
nextRetryAt: run.nextRetryAt
|
|
932
|
-
})),
|
|
933
|
-
lastError,
|
|
934
|
-
codexTotals: aggregateTokenUsage(allRuns ?? activeRuns, lastTickAt),
|
|
935
|
-
rateLimits: rateLimits ?? null
|
|
936
|
-
};
|
|
937
|
-
}
|
|
938
|
-
function aggregateTokenUsage(runs, lastTickAt) {
|
|
939
|
-
let inputTokens = 0;
|
|
940
|
-
let outputTokens = 0;
|
|
941
|
-
let totalTokens = 0;
|
|
942
|
-
let earliestStart = null;
|
|
943
|
-
let latestEnd = null;
|
|
944
|
-
for (const run of runs) {
|
|
945
|
-
if (run.tokenUsage) {
|
|
946
|
-
inputTokens += run.tokenUsage.inputTokens;
|
|
947
|
-
outputTokens += run.tokenUsage.outputTokens;
|
|
948
|
-
totalTokens += run.tokenUsage.totalTokens;
|
|
949
|
-
}
|
|
950
|
-
if (run.startedAt) {
|
|
951
|
-
const start = new Date(run.startedAt).getTime();
|
|
952
|
-
if (earliestStart === null || start < earliestStart) {
|
|
953
|
-
earliestStart = start;
|
|
954
|
-
}
|
|
955
|
-
}
|
|
956
|
-
const end = run.completedAt ? new Date(run.completedAt).getTime() : new Date(lastTickAt).getTime();
|
|
957
|
-
if (latestEnd === null || end > latestEnd) {
|
|
958
|
-
latestEnd = end;
|
|
959
|
-
}
|
|
960
|
-
}
|
|
961
|
-
const secondsRunning = earliestStart !== null && latestEnd !== null ? Math.max(0, Math.round((latestEnd - earliestStart) / 1e3)) : 0;
|
|
962
|
-
return { inputTokens, outputTokens, totalTokens, secondsRunning };
|
|
963
|
-
}
|
|
964
|
-
|
|
965
|
-
// ../core/dist/observability/fs-reader.js
|
|
966
|
-
import { readFile as readFile2, readdir } from "fs/promises";
|
|
967
|
-
async function readJsonFile(path) {
|
|
968
|
-
try {
|
|
969
|
-
const raw = await readFile2(path, "utf8");
|
|
970
|
-
return JSON.parse(raw);
|
|
971
|
-
} catch (error) {
|
|
972
|
-
if (isFileMissing(error)) {
|
|
973
|
-
return null;
|
|
974
|
-
}
|
|
975
|
-
throw error;
|
|
976
|
-
}
|
|
977
|
-
}
|
|
978
|
-
async function safeReadDir(path) {
|
|
979
|
-
try {
|
|
980
|
-
return await readdir(path);
|
|
981
|
-
} catch (error) {
|
|
982
|
-
if (isFileMissing(error)) {
|
|
983
|
-
return [];
|
|
984
|
-
}
|
|
985
|
-
throw error;
|
|
986
|
-
}
|
|
987
|
-
}
|
|
988
|
-
function isFileMissing(error) {
|
|
989
|
-
return Boolean(error && typeof error === "object" && "code" in error && (error.code === "ENOENT" || error.code === "ENOTDIR"));
|
|
990
|
-
}
|
|
991
|
-
|
|
992
|
-
// ../core/dist/observability/event-formatter.js
|
|
993
|
-
function formatEventMessage(event) {
|
|
994
|
-
switch (event.event) {
|
|
995
|
-
case "run-dispatched":
|
|
996
|
-
return event.issueState ? `Dispatched from ${event.issueState}` : "Dispatched";
|
|
997
|
-
case "run-recovered":
|
|
998
|
-
return "Recovered existing run";
|
|
999
|
-
case "run-retried":
|
|
1000
|
-
return `Retry ${event.attempt} scheduled (${event.retryKind})`;
|
|
1001
|
-
case "run-failed":
|
|
1002
|
-
return event.lastError;
|
|
1003
|
-
case "run-suppressed":
|
|
1004
|
-
return event.reason;
|
|
1005
|
-
case "hook-executed":
|
|
1006
|
-
return `${event.hook}: ${event.outcome}`;
|
|
1007
|
-
case "hook-failed":
|
|
1008
|
-
return event.error;
|
|
1009
|
-
case "workspace-cleanup":
|
|
1010
|
-
return event.error ? `${event.outcome}: ${event.error}` : event.outcome;
|
|
1011
|
-
case "worker-error":
|
|
1012
|
-
return event.error;
|
|
1013
|
-
case "turn_started":
|
|
1014
|
-
return `Turn ${event.turnCount} started`;
|
|
1015
|
-
case "turn_completed":
|
|
1016
|
-
return `Turn ${event.turnCount} completed in ${event.durationMs}ms`;
|
|
1017
|
-
case "turn_failed":
|
|
1018
|
-
return event.error ?? `Turn ${event.turnCount} failed`;
|
|
1019
|
-
default:
|
|
1020
|
-
return null;
|
|
1021
|
-
}
|
|
1022
|
-
}
|
|
1023
|
-
function parseRecentEvents(raw, limit, options) {
|
|
1024
|
-
const lines = raw.split("\n");
|
|
1025
|
-
if (options.allowPartialFirstLine) {
|
|
1026
|
-
lines.shift();
|
|
1027
|
-
}
|
|
1028
|
-
const events = [];
|
|
1029
|
-
for (let index = lines.length - 1; index >= 0; index -= 1) {
|
|
1030
|
-
const line = lines[index]?.trim();
|
|
1031
|
-
if (!line) {
|
|
1032
|
-
continue;
|
|
1033
|
-
}
|
|
1034
|
-
const event = parseRunEventLine(line);
|
|
1035
|
-
if (!event) {
|
|
1036
|
-
continue;
|
|
1037
|
-
}
|
|
1038
|
-
events.push({
|
|
1039
|
-
at: event.at,
|
|
1040
|
-
event: event.event,
|
|
1041
|
-
message: formatEventMessage(event)
|
|
1042
|
-
});
|
|
1043
|
-
if (events.length === limit) {
|
|
1044
|
-
break;
|
|
1045
|
-
}
|
|
1046
|
-
}
|
|
1047
|
-
return events.reverse();
|
|
1048
|
-
}
|
|
1049
|
-
function parseRunEventLine(line) {
|
|
1050
|
-
try {
|
|
1051
|
-
return JSON.parse(line);
|
|
1052
|
-
} catch {
|
|
1053
|
-
return null;
|
|
1054
|
-
}
|
|
1055
|
-
}
|
|
1056
|
-
|
|
1057
|
-
// ../core/dist/observability/status-assembler.js
|
|
1058
|
-
function isMatchingIssueRun(run, projectId, issueId, issueIdentifier) {
|
|
1059
|
-
return Boolean(run && run.projectId === projectId && (run.issueId === issueId || run.issueIdentifier === issueIdentifier));
|
|
1060
|
-
}
|
|
1061
|
-
function mapIssueOrchestrationStateToStatus(state) {
|
|
1062
|
-
switch (state) {
|
|
1063
|
-
case "claimed":
|
|
1064
|
-
return "starting";
|
|
1065
|
-
case "running":
|
|
1066
|
-
return "running";
|
|
1067
|
-
case "retry_queued":
|
|
1068
|
-
return "retrying";
|
|
1069
|
-
case "released":
|
|
1070
|
-
return "released";
|
|
1071
|
-
case "unclaimed":
|
|
1072
|
-
return "pending";
|
|
1073
|
-
default:
|
|
1074
|
-
return state;
|
|
1075
|
-
}
|
|
1076
|
-
}
|
|
1077
|
-
|
|
1078
38
|
// ../orchestrator/dist/git.js
|
|
1079
|
-
import { spawn
|
|
39
|
+
import { spawn } from "child_process";
|
|
1080
40
|
import { randomUUID } from "crypto";
|
|
1081
|
-
import { access
|
|
1082
|
-
import { constants
|
|
1083
|
-
import { join
|
|
41
|
+
import { access, mkdir, readFile, rename, rm, stat, writeFile } from "fs/promises";
|
|
42
|
+
import { constants } from "fs";
|
|
43
|
+
import { join } from "path";
|
|
1084
44
|
var workflowConfigStore = new WorkflowConfigStore();
|
|
1085
45
|
var LOCK_RETRY_MS = 100;
|
|
1086
46
|
var LOCK_STALE_MS = 30 * 60 * 1e3;
|
|
@@ -1091,12 +51,12 @@ async function cloneRepositoryForRun(input) {
|
|
|
1091
51
|
}
|
|
1092
52
|
async function syncRepositoryForRun(input) {
|
|
1093
53
|
await mkdir(input.targetDirectory, { recursive: true });
|
|
1094
|
-
const repositoryDirectory =
|
|
1095
|
-
const lockDirectory =
|
|
54
|
+
const repositoryDirectory = join(input.targetDirectory, "repository");
|
|
55
|
+
const lockDirectory = join(input.targetDirectory, "repository.lock");
|
|
1096
56
|
return withRepositoryLock(lockDirectory, async () => {
|
|
1097
57
|
let hasGit = false;
|
|
1098
58
|
try {
|
|
1099
|
-
await
|
|
59
|
+
await access(join(repositoryDirectory, ".git"), constants.R_OK);
|
|
1100
60
|
hasGit = true;
|
|
1101
61
|
} catch {
|
|
1102
62
|
}
|
|
@@ -1120,7 +80,7 @@ async function syncRepositoryForRun(input) {
|
|
|
1120
80
|
} else {
|
|
1121
81
|
await rm(repositoryDirectory, { recursive: true, force: true });
|
|
1122
82
|
}
|
|
1123
|
-
const tempRepositoryDirectory =
|
|
83
|
+
const tempRepositoryDirectory = join(input.targetDirectory, `repository.tmp-${process.pid}-${Date.now()}`);
|
|
1124
84
|
await rm(tempRepositoryDirectory, { recursive: true, force: true });
|
|
1125
85
|
try {
|
|
1126
86
|
await runCommand("git", [
|
|
@@ -1147,7 +107,7 @@ async function ensureIssueWorkspaceRepository(input) {
|
|
|
1147
107
|
});
|
|
1148
108
|
}
|
|
1149
109
|
async function loadRepositoryWorkflow(repositoryDirectory, _repository) {
|
|
1150
|
-
const workflowPath =
|
|
110
|
+
const workflowPath = join(repositoryDirectory, "WORKFLOW.md");
|
|
1151
111
|
try {
|
|
1152
112
|
return await workflowConfigStore.load(workflowPath);
|
|
1153
113
|
} catch (error) {
|
|
@@ -1158,8 +118,8 @@ async function loadRepositoryWorkflow(repositoryDirectory, _repository) {
|
|
|
1158
118
|
}
|
|
1159
119
|
}
|
|
1160
120
|
function runCommand(command, args) {
|
|
1161
|
-
return new Promise((
|
|
1162
|
-
const child =
|
|
121
|
+
return new Promise((resolve4, reject) => {
|
|
122
|
+
const child = spawn(command, args, {
|
|
1163
123
|
stdio: "pipe"
|
|
1164
124
|
});
|
|
1165
125
|
let stderr = "";
|
|
@@ -1169,7 +129,7 @@ function runCommand(command, args) {
|
|
|
1169
129
|
child.once("error", reject);
|
|
1170
130
|
child.once("exit", (code) => {
|
|
1171
131
|
if (code === 0) {
|
|
1172
|
-
|
|
132
|
+
resolve4();
|
|
1173
133
|
return;
|
|
1174
134
|
}
|
|
1175
135
|
reject(new Error(stderr.trim() || `${command} exited with code ${code ?? "unknown"}`));
|
|
@@ -1189,8 +149,8 @@ async function readGitHead(repositoryDirectory) {
|
|
|
1189
149
|
}
|
|
1190
150
|
}
|
|
1191
151
|
function runCommandCapture(command, args) {
|
|
1192
|
-
return new Promise((
|
|
1193
|
-
const child =
|
|
152
|
+
return new Promise((resolve4, reject) => {
|
|
153
|
+
const child = spawn(command, args, {
|
|
1194
154
|
stdio: ["ignore", "pipe", "pipe"]
|
|
1195
155
|
});
|
|
1196
156
|
let stdout = "";
|
|
@@ -1204,7 +164,7 @@ function runCommandCapture(command, args) {
|
|
|
1204
164
|
child.once("error", reject);
|
|
1205
165
|
child.once("exit", (code) => {
|
|
1206
166
|
if (code === 0) {
|
|
1207
|
-
|
|
167
|
+
resolve4(stdout.trim());
|
|
1208
168
|
return;
|
|
1209
169
|
}
|
|
1210
170
|
reject(new Error(stderr.trim() || `${command} exited with code ${code ?? "unknown"}`));
|
|
@@ -1225,7 +185,7 @@ async function acquireRepositoryLock(lockDirectory) {
|
|
|
1225
185
|
for (; ; ) {
|
|
1226
186
|
try {
|
|
1227
187
|
await mkdir(lockDirectory);
|
|
1228
|
-
await writeFile(
|
|
188
|
+
await writeFile(join(lockDirectory, "owner"), `${ownerToken}
|
|
1229
189
|
${(/* @__PURE__ */ new Date()).toISOString()}
|
|
1230
190
|
`, "utf8");
|
|
1231
191
|
return ownerToken;
|
|
@@ -1261,7 +221,7 @@ async function releaseRepositoryLock(lockDirectory, ownerToken) {
|
|
|
1261
221
|
}
|
|
1262
222
|
async function isStaleLock(lockDirectory) {
|
|
1263
223
|
try {
|
|
1264
|
-
const details = await
|
|
224
|
+
const details = await stat(lockDirectory);
|
|
1265
225
|
return Date.now() - details.mtimeMs >= LOCK_STALE_MS;
|
|
1266
226
|
} catch (error) {
|
|
1267
227
|
if (isMissingFileError(error)) {
|
|
@@ -1274,13 +234,13 @@ function isAlreadyExistsError(error) {
|
|
|
1274
234
|
return Boolean(error && typeof error === "object" && "code" in error && error.code === "EEXIST");
|
|
1275
235
|
}
|
|
1276
236
|
async function readLockOwner(lockDirectory) {
|
|
1277
|
-
await
|
|
1278
|
-
const owner = await
|
|
237
|
+
await access(join(lockDirectory, "owner"), constants.R_OK);
|
|
238
|
+
const owner = await readFile(join(lockDirectory, "owner"), "utf8");
|
|
1279
239
|
return owner.split("\n", 1)[0] || null;
|
|
1280
240
|
}
|
|
1281
241
|
function wait(ms) {
|
|
1282
|
-
return new Promise((
|
|
1283
|
-
setTimeout(
|
|
242
|
+
return new Promise((resolve4) => {
|
|
243
|
+
setTimeout(resolve4, ms);
|
|
1284
244
|
});
|
|
1285
245
|
}
|
|
1286
246
|
function isMissingFileError(error) {
|
|
@@ -1288,40 +248,40 @@ function isMissingFileError(error) {
|
|
|
1288
248
|
}
|
|
1289
249
|
|
|
1290
250
|
// ../orchestrator/dist/fs-store.js
|
|
1291
|
-
import { mkdir as mkdir2, open, rename as rename2, rm as rm2, stat as
|
|
1292
|
-
import { dirname, join as
|
|
251
|
+
import { mkdir as mkdir2, open, rename as rename2, rm as rm2, stat as stat2, writeFile as writeFile2, appendFile } from "fs/promises";
|
|
252
|
+
import { dirname, join as join2, relative, resolve } from "path";
|
|
1293
253
|
var OrchestratorFsStore = class {
|
|
1294
254
|
runtimeRoot;
|
|
1295
255
|
resolvedRuntimeRoot;
|
|
1296
256
|
resolvedEventsMirrorRoot;
|
|
1297
257
|
constructor(runtimeRoot, options = {}) {
|
|
1298
258
|
this.runtimeRoot = runtimeRoot;
|
|
1299
|
-
this.resolvedRuntimeRoot =
|
|
1300
|
-
this.resolvedEventsMirrorRoot = options.eventsMirrorRoot ?
|
|
259
|
+
this.resolvedRuntimeRoot = resolve(runtimeRoot);
|
|
260
|
+
this.resolvedEventsMirrorRoot = options.eventsMirrorRoot ? resolve(options.eventsMirrorRoot) : null;
|
|
1301
261
|
}
|
|
1302
262
|
projectsRoot() {
|
|
1303
|
-
return
|
|
263
|
+
return join2(this.runtimeRoot, "projects");
|
|
1304
264
|
}
|
|
1305
265
|
projectDir(projectId) {
|
|
1306
|
-
return
|
|
266
|
+
return join2(this.projectsRoot(), projectId);
|
|
1307
267
|
}
|
|
1308
268
|
projectRunsDir(projectId) {
|
|
1309
|
-
return
|
|
269
|
+
return join2(this.projectDir(projectId), "runs");
|
|
1310
270
|
}
|
|
1311
271
|
runDir(runId, projectId) {
|
|
1312
272
|
if (!projectId) {
|
|
1313
|
-
return
|
|
273
|
+
return join2(this.runtimeRoot, "projects", "__unknown__", "runs", runId);
|
|
1314
274
|
}
|
|
1315
|
-
return
|
|
275
|
+
return join2(this.projectRunsDir(projectId), runId);
|
|
1316
276
|
}
|
|
1317
277
|
async loadProjectConfig(projectId) {
|
|
1318
|
-
return readJsonFile(
|
|
278
|
+
return readJsonFile(join2(this.projectDir(projectId), "project.json"));
|
|
1319
279
|
}
|
|
1320
280
|
async saveProjectConfig(config) {
|
|
1321
|
-
await writeJsonFile(
|
|
281
|
+
await writeJsonFile(join2(this.projectDir(config.projectId), "project.json"), config);
|
|
1322
282
|
}
|
|
1323
283
|
async loadProjectIssueOrchestrations(projectId) {
|
|
1324
|
-
const issuesPath =
|
|
284
|
+
const issuesPath = join2(this.projectDir(projectId), "issues.json");
|
|
1325
285
|
const issues = await readJsonFile(issuesPath);
|
|
1326
286
|
if (issues) {
|
|
1327
287
|
return issues.map((issue) => ({
|
|
@@ -1329,7 +289,7 @@ var OrchestratorFsStore = class {
|
|
|
1329
289
|
completedOnce: issue.completedOnce ?? false
|
|
1330
290
|
}));
|
|
1331
291
|
}
|
|
1332
|
-
const legacyLeases = await readJsonFile(
|
|
292
|
+
const legacyLeases = await readJsonFile(join2(this.projectDir(projectId), "leases.json")) ?? [];
|
|
1333
293
|
if (legacyLeases.length === 0) {
|
|
1334
294
|
return [];
|
|
1335
295
|
}
|
|
@@ -1347,20 +307,20 @@ var OrchestratorFsStore = class {
|
|
|
1347
307
|
return migratedIssues;
|
|
1348
308
|
}
|
|
1349
309
|
async saveProjectIssueOrchestrations(projectId, issues) {
|
|
1350
|
-
await writeJsonFile(
|
|
310
|
+
await writeJsonFile(join2(this.projectDir(projectId), "issues.json"), issues);
|
|
1351
311
|
}
|
|
1352
312
|
async saveProjectStatus(status) {
|
|
1353
|
-
await writeJsonFile(
|
|
313
|
+
await writeJsonFile(join2(this.projectDir(status.projectId), "status.json"), status);
|
|
1354
314
|
}
|
|
1355
315
|
async loadProjectStatus(projectId) {
|
|
1356
|
-
return await readJsonFile(
|
|
316
|
+
return await readJsonFile(join2(this.projectDir(projectId), "status.json")) ?? null;
|
|
1357
317
|
}
|
|
1358
318
|
async loadRun(runId, projectId) {
|
|
1359
319
|
const runDirectory = projectId !== void 0 ? this.runDir(runId, projectId) : await this.findRunDir(runId);
|
|
1360
320
|
if (!runDirectory) {
|
|
1361
321
|
return null;
|
|
1362
322
|
}
|
|
1363
|
-
return await readJsonFile(
|
|
323
|
+
return await readJsonFile(join2(runDirectory, "run.json")) ?? null;
|
|
1364
324
|
}
|
|
1365
325
|
async loadAllRuns() {
|
|
1366
326
|
const projectIds = await safeReadDir(this.projectsRoot());
|
|
@@ -1368,11 +328,11 @@ var OrchestratorFsStore = class {
|
|
|
1368
328
|
const entries = await safeReadDir(this.projectRunsDir(projectId));
|
|
1369
329
|
return entries.map((entry) => this.runDir(entry, projectId));
|
|
1370
330
|
}));
|
|
1371
|
-
const runs = await Promise.all(runDirectories.flat().map((directory) => readJsonFile(
|
|
331
|
+
const runs = await Promise.all(runDirectories.flat().map((directory) => readJsonFile(join2(directory, "run.json"))));
|
|
1372
332
|
return runs.filter((run) => Boolean(run));
|
|
1373
333
|
}
|
|
1374
334
|
async saveRun(run) {
|
|
1375
|
-
await writeJsonFile(
|
|
335
|
+
await writeJsonFile(join2(this.runDir(run.runId, run.projectId), "run.json"), run);
|
|
1376
336
|
}
|
|
1377
337
|
async appendRunEvent(runId, event) {
|
|
1378
338
|
const resolvedProjectId = "projectId" in event && typeof event.projectId === "string" ? event.projectId : void 0;
|
|
@@ -1380,8 +340,8 @@ var OrchestratorFsStore = class {
|
|
|
1380
340
|
if (!runDirectory) {
|
|
1381
341
|
throw new Error(`Unable to resolve run directory for event append: ${runId}`);
|
|
1382
342
|
}
|
|
1383
|
-
const path =
|
|
1384
|
-
const resolvedPath =
|
|
343
|
+
const path = join2(runDirectory, "events.ndjson");
|
|
344
|
+
const resolvedPath = resolve(path);
|
|
1385
345
|
const serializedEvent = JSON.stringify(event) + "\n";
|
|
1386
346
|
await mkdir2(dirname(path), { recursive: true });
|
|
1387
347
|
await appendFile(path, serializedEvent, {
|
|
@@ -1407,7 +367,7 @@ var OrchestratorFsStore = class {
|
|
|
1407
367
|
if (!runDirectory) {
|
|
1408
368
|
return [];
|
|
1409
369
|
}
|
|
1410
|
-
const path =
|
|
370
|
+
const path = join2(runDirectory, "events.ndjson");
|
|
1411
371
|
try {
|
|
1412
372
|
if (limit <= 0) {
|
|
1413
373
|
return [];
|
|
@@ -1444,19 +404,19 @@ var OrchestratorFsStore = class {
|
|
|
1444
404
|
}
|
|
1445
405
|
}
|
|
1446
406
|
issueWorkspaceDir(projectId, workspaceKey) {
|
|
1447
|
-
return
|
|
407
|
+
return join2(this.projectDir(projectId), "issues", workspaceKey);
|
|
1448
408
|
}
|
|
1449
409
|
async loadIssueWorkspace(projectId, workspaceKey) {
|
|
1450
|
-
return await readJsonFile(
|
|
410
|
+
return await readJsonFile(join2(this.issueWorkspaceDir(projectId, workspaceKey), "workspace.json")) ?? null;
|
|
1451
411
|
}
|
|
1452
412
|
async loadIssueWorkspaces(projectId) {
|
|
1453
|
-
const issuesDir =
|
|
413
|
+
const issuesDir = join2(this.projectDir(projectId), "issues");
|
|
1454
414
|
const entries = await safeReadDir(issuesDir);
|
|
1455
415
|
const records = await Promise.all(entries.map((entry) => this.loadIssueWorkspace(projectId, entry)));
|
|
1456
416
|
return records.filter((record) => Boolean(record));
|
|
1457
417
|
}
|
|
1458
418
|
async saveIssueWorkspace(record) {
|
|
1459
|
-
await writeJsonFile(
|
|
419
|
+
await writeJsonFile(join2(this.issueWorkspaceDir(record.projectId, record.workspaceKey), "workspace.json"), record);
|
|
1460
420
|
}
|
|
1461
421
|
async removeIssueWorkspace(projectId, workspaceKey) {
|
|
1462
422
|
const dir = this.issueWorkspaceDir(projectId, workspaceKey);
|
|
@@ -1466,8 +426,8 @@ var OrchestratorFsStore = class {
|
|
|
1466
426
|
const projectIds = await safeReadDir(this.projectsRoot());
|
|
1467
427
|
for (const projectId of projectIds) {
|
|
1468
428
|
const candidate = this.runDir(runId, projectId);
|
|
1469
|
-
const run = await readJsonFile(
|
|
1470
|
-
if (run || await pathExists(
|
|
429
|
+
const run = await readJsonFile(join2(candidate, "run.json"));
|
|
430
|
+
if (run || await pathExists(join2(candidate, "events.ndjson"))) {
|
|
1471
431
|
return candidate;
|
|
1472
432
|
}
|
|
1473
433
|
}
|
|
@@ -1481,7 +441,7 @@ var OrchestratorFsStore = class {
|
|
|
1481
441
|
if (relativePath.startsWith("..")) {
|
|
1482
442
|
return null;
|
|
1483
443
|
}
|
|
1484
|
-
const mirrorPath =
|
|
444
|
+
const mirrorPath = join2(this.resolvedEventsMirrorRoot, relativePath);
|
|
1485
445
|
return mirrorPath === primaryPath ? null : mirrorPath;
|
|
1486
446
|
}
|
|
1487
447
|
};
|
|
@@ -1493,7 +453,7 @@ async function writeJsonFile(path, value) {
|
|
|
1493
453
|
}
|
|
1494
454
|
async function pathExists(path) {
|
|
1495
455
|
try {
|
|
1496
|
-
await
|
|
456
|
+
await stat2(path);
|
|
1497
457
|
return true;
|
|
1498
458
|
} catch (error) {
|
|
1499
459
|
if (isFileMissing(error)) {
|
|
@@ -2126,7 +1086,7 @@ var ISSUE_PROJECT_ITEMS_PAGE_QUERY = `
|
|
|
2126
1086
|
`;
|
|
2127
1087
|
|
|
2128
1088
|
// ../tracker-github/dist/orchestrator-adapter.js
|
|
2129
|
-
import { createHash
|
|
1089
|
+
import { createHash } from "crypto";
|
|
2130
1090
|
var githubProjectTrackerAdapter = {
|
|
2131
1091
|
async listIssues(project, dependencies = {}) {
|
|
2132
1092
|
return listProjectIssues(project, dependencies);
|
|
@@ -2214,7 +1174,7 @@ function hashToken(token) {
|
|
|
2214
1174
|
if (!token) {
|
|
2215
1175
|
return null;
|
|
2216
1176
|
}
|
|
2217
|
-
return
|
|
1177
|
+
return createHash("sha256").update(token).digest("hex");
|
|
2218
1178
|
}
|
|
2219
1179
|
var trackerAdapters = {
|
|
2220
1180
|
"github-project": githubProjectTrackerAdapter
|
|
@@ -2263,7 +1223,7 @@ function parseIssueNumber(identifier) {
|
|
|
2263
1223
|
}
|
|
2264
1224
|
|
|
2265
1225
|
// ../tracker-file/dist/file-tracker-adapter.js
|
|
2266
|
-
import { readFile as
|
|
1226
|
+
import { readFile as readFile2 } from "fs/promises";
|
|
2267
1227
|
function requireTrackerSetting2(project, key) {
|
|
2268
1228
|
const value = project.tracker.settings?.[key];
|
|
2269
1229
|
if (typeof value !== "string" || value.length === 0) {
|
|
@@ -2285,7 +1245,7 @@ var fileTrackerAdapter = {
|
|
|
2285
1245
|
async listIssues(project) {
|
|
2286
1246
|
const issuesPath = requireTrackerSetting2(project, "issuesPath");
|
|
2287
1247
|
try {
|
|
2288
|
-
const raw = await
|
|
1248
|
+
const raw = await readFile2(issuesPath, "utf-8");
|
|
2289
1249
|
const parsed = JSON.parse(raw);
|
|
2290
1250
|
if (!Array.isArray(parsed)) {
|
|
2291
1251
|
throw new Error(`Expected an array of issues in ${issuesPath}, got ${typeof parsed}`);
|
|
@@ -2369,7 +1329,7 @@ function resolveTrackerAdapter2(tracker) {
|
|
|
2369
1329
|
}
|
|
2370
1330
|
|
|
2371
1331
|
// ../orchestrator/dist/service.js
|
|
2372
|
-
var
|
|
1332
|
+
var DEFAULT_POLL_INTERVAL_MS = 3e4;
|
|
2373
1333
|
var DEFAULT_CONCURRENCY = 3;
|
|
2374
1334
|
var DEFAULT_RETRY_BACKOFF_MS = 3e4;
|
|
2375
1335
|
var CONTINUATION_RETRY_DELAY_MS = 1e3;
|
|
@@ -2479,7 +1439,7 @@ var OrchestratorService = class {
|
|
|
2479
1439
|
codex_session_logs: currentRun === null ? [] : [
|
|
2480
1440
|
{
|
|
2481
1441
|
label: "worker",
|
|
2482
|
-
path:
|
|
1442
|
+
path: join3(this.store.runDir(currentRun.runId, currentRun.projectId), "worker.log"),
|
|
2483
1443
|
url: null
|
|
2484
1444
|
}
|
|
2485
1445
|
]
|
|
@@ -2542,7 +1502,7 @@ var OrchestratorService = class {
|
|
|
2542
1502
|
return this.dependencies.pollIntervalMs;
|
|
2543
1503
|
}
|
|
2544
1504
|
const configuredIntervals = [...this.projectPollIntervals.values()].filter((value) => Number.isFinite(value) && value > 0);
|
|
2545
|
-
return configuredIntervals.length ? Math.min(...configuredIntervals) :
|
|
1505
|
+
return configuredIntervals.length ? Math.min(...configuredIntervals) : DEFAULT_POLL_INTERVAL_MS;
|
|
2546
1506
|
}
|
|
2547
1507
|
async reconcileProject(tenant, issueIdentifier, trackerDependencies = {}) {
|
|
2548
1508
|
const trackerAdapter = resolveTrackerAdapter2(tenant.tracker);
|
|
@@ -2551,7 +1511,7 @@ var OrchestratorService = class {
|
|
|
2551
1511
|
let dispatched = 0;
|
|
2552
1512
|
let suppressed = 0;
|
|
2553
1513
|
let recovered = 0;
|
|
2554
|
-
let pollIntervalMs =
|
|
1514
|
+
let pollIntervalMs = DEFAULT_POLL_INTERVAL_MS;
|
|
2555
1515
|
let rateLimits = null;
|
|
2556
1516
|
let issueRecords = await this.store.loadProjectIssueOrchestrations(tenant.projectId);
|
|
2557
1517
|
const allRuns = (await this.store.loadAllRuns()).filter((run) => run.projectId === tenant.projectId);
|
|
@@ -2870,8 +1830,8 @@ var OrchestratorService = class {
|
|
|
2870
1830
|
async runSerialized(operation) {
|
|
2871
1831
|
const previous = this.reconcilePromise;
|
|
2872
1832
|
let release;
|
|
2873
|
-
this.reconcilePromise = new Promise((
|
|
2874
|
-
release =
|
|
1833
|
+
this.reconcilePromise = new Promise((resolve4) => {
|
|
1834
|
+
release = resolve4;
|
|
2875
1835
|
});
|
|
2876
1836
|
await previous;
|
|
2877
1837
|
try {
|
|
@@ -2967,7 +1927,7 @@ var OrchestratorService = class {
|
|
|
2967
1927
|
return this.loadProjectWorkflowUncached(tenant, repository);
|
|
2968
1928
|
}
|
|
2969
1929
|
async loadProjectWorkflowUncached(tenant, repository) {
|
|
2970
|
-
const cacheRoot =
|
|
1930
|
+
const cacheRoot = join3(this.store.projectDir(tenant.projectId), "cache", repository.owner, repository.name);
|
|
2971
1931
|
const { repositoryDirectory, changed } = await syncRepositoryForRun({
|
|
2972
1932
|
repository,
|
|
2973
1933
|
targetDirectory: cacheRoot
|
|
@@ -3055,7 +2015,7 @@ var OrchestratorService = class {
|
|
|
3055
2015
|
state: issue.state
|
|
3056
2016
|
});
|
|
3057
2017
|
mkdirSync(runDir, { recursive: true });
|
|
3058
|
-
const workerLogStream = (this.dependencies.createWriteStreamImpl ?? createWriteStream)(
|
|
2018
|
+
const workerLogStream = (this.dependencies.createWriteStreamImpl ?? createWriteStream)(join3(runDir, "worker.log"), {
|
|
3059
2019
|
flags: "a"
|
|
3060
2020
|
});
|
|
3061
2021
|
let workerLogAvailable = true;
|
|
@@ -3078,7 +2038,7 @@ var OrchestratorService = class {
|
|
|
3078
2038
|
const message = error instanceof Error ? error.message : String(error ?? "unknown");
|
|
3079
2039
|
this.writeStderr(`[orchestrator] failed to write worker log for ${runId}: ${message}`);
|
|
3080
2040
|
};
|
|
3081
|
-
const child = (this.dependencies.spawnImpl ??
|
|
2041
|
+
const child = (this.dependencies.spawnImpl ?? spawn2)("bash", ["-lc", resolveWorkerCommand()], {
|
|
3082
2042
|
cwd: process.cwd(),
|
|
3083
2043
|
env: this.buildProjectExecutionEnv(tenant.projectId, {
|
|
3084
2044
|
GITHUB_GRAPHQL_TOKEN: process.env.GITHUB_GRAPHQL_TOKEN ?? "",
|
|
@@ -3624,11 +2584,11 @@ var OrchestratorService = class {
|
|
|
3624
2584
|
this.sleepResolver = null;
|
|
3625
2585
|
}
|
|
3626
2586
|
createPendingSleepPromise() {
|
|
3627
|
-
return new Promise((
|
|
2587
|
+
return new Promise((resolve4) => {
|
|
3628
2588
|
this.sleepResolver = () => {
|
|
3629
2589
|
this.sleepResolver = null;
|
|
3630
2590
|
this.sleepTimer = null;
|
|
3631
|
-
|
|
2591
|
+
resolve4();
|
|
3632
2592
|
};
|
|
3633
2593
|
});
|
|
3634
2594
|
}
|
|
@@ -3709,12 +2669,12 @@ var OrchestratorService = class {
|
|
|
3709
2669
|
}
|
|
3710
2670
|
async readPersistedWorkerTokenUsage(run) {
|
|
3711
2671
|
const artifactPaths = [
|
|
3712
|
-
|
|
3713
|
-
|
|
2672
|
+
join3(run.workspaceRuntimeDir, "token-usage.json"),
|
|
2673
|
+
join3(run.workspaceRuntimeDir, ".orchestrator", "runs", run.runId, "token-usage.json")
|
|
3714
2674
|
];
|
|
3715
2675
|
for (const artifactPath of artifactPaths) {
|
|
3716
2676
|
try {
|
|
3717
|
-
const raw = await
|
|
2677
|
+
const raw = await readFile3(artifactPath, "utf8");
|
|
3718
2678
|
const tokenUsage = JSON.parse(raw);
|
|
3719
2679
|
if (hasTokenUsage(tokenUsage)) {
|
|
3720
2680
|
return tokenUsage;
|
|
@@ -3749,7 +2709,7 @@ var OrchestratorService = class {
|
|
|
3749
2709
|
}
|
|
3750
2710
|
}
|
|
3751
2711
|
readProjectEnv(projectId) {
|
|
3752
|
-
const envPath =
|
|
2712
|
+
const envPath = join3(this.store.projectDir(projectId), ".env");
|
|
3753
2713
|
try {
|
|
3754
2714
|
return readEnvFile(envPath);
|
|
3755
2715
|
} catch (error) {
|
|
@@ -3844,7 +2804,7 @@ var OrchestratorService = class {
|
|
|
3844
2804
|
return isUsableWorkflowResolution(resolution) ? resolution.workflow.polling.intervalMs : NaN;
|
|
3845
2805
|
}));
|
|
3846
2806
|
const validIntervals = intervals.filter((value) => Number.isFinite(value) && value > 0);
|
|
3847
|
-
return validIntervals.length ? Math.min(...validIntervals) :
|
|
2807
|
+
return validIntervals.length ? Math.min(...validIntervals) : DEFAULT_POLL_INTERVAL_MS;
|
|
3848
2808
|
}
|
|
3849
2809
|
async loadProjectMaxConcurrentByState(tenant) {
|
|
3850
2810
|
const result = {};
|
|
@@ -3952,13 +2912,13 @@ var OrchestratorService = class {
|
|
|
3952
2912
|
return null;
|
|
3953
2913
|
}
|
|
3954
2914
|
const snapshotPath = this.lastKnownGoodWorkflowPath(cacheRoot);
|
|
3955
|
-
const markdown = await
|
|
3956
|
-
await mkdir3(
|
|
2915
|
+
const markdown = await readFile3(resolution.workflowPath, "utf8");
|
|
2916
|
+
await mkdir3(join3(cacheRoot, "last-known-good"), { recursive: true });
|
|
3957
2917
|
await writeFile3(snapshotPath, markdown, "utf8");
|
|
3958
2918
|
return snapshotPath;
|
|
3959
2919
|
}
|
|
3960
2920
|
lastKnownGoodWorkflowPath(cacheRoot) {
|
|
3961
|
-
return
|
|
2921
|
+
return join3(cacheRoot, "last-known-good", "WORKFLOW.md");
|
|
3962
2922
|
}
|
|
3963
2923
|
workflowCacheKey(repository) {
|
|
3964
2924
|
return `${repository.owner}/${repository.name}:${this.normalizeRepositoryCloneUrl(repository.cloneUrl)}`;
|
|
@@ -4064,14 +3024,14 @@ var OrchestratorService = class {
|
|
|
4064
3024
|
function hasTokenUsage(tokenUsage) {
|
|
4065
3025
|
return Boolean(tokenUsage && (tokenUsage.inputTokens > 0 || tokenUsage.outputTokens > 0 || tokenUsage.totalTokens > 0));
|
|
4066
3026
|
}
|
|
4067
|
-
function
|
|
3027
|
+
function isRecord(value) {
|
|
4068
3028
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
4069
3029
|
}
|
|
4070
3030
|
function resolveProjectRateLimits(runs, issues) {
|
|
4071
3031
|
let latestRunRateLimits = null;
|
|
4072
3032
|
let latestRunTimestamp = -Infinity;
|
|
4073
3033
|
for (const run of runs) {
|
|
4074
|
-
if (!
|
|
3034
|
+
if (!isRecord(run.rateLimits)) {
|
|
4075
3035
|
continue;
|
|
4076
3036
|
}
|
|
4077
3037
|
const timestamp = parseTimestampMs(run.lastEventAt ?? run.updatedAt ?? run.startedAt);
|
|
@@ -4085,7 +3045,7 @@ function resolveProjectRateLimits(runs, issues) {
|
|
|
4085
3045
|
return latestRunRateLimits;
|
|
4086
3046
|
}
|
|
4087
3047
|
for (const issue of issues) {
|
|
4088
|
-
if (
|
|
3048
|
+
if (isRecord(issue.rateLimits)) {
|
|
4089
3049
|
return issue.rateLimits;
|
|
4090
3050
|
}
|
|
4091
3051
|
}
|
|
@@ -4191,7 +3151,12 @@ function resolveWorkerCommand() {
|
|
|
4191
3151
|
const workerUrl = import.meta.resolve("@gh-symphony/worker");
|
|
4192
3152
|
return `node ${fileURLToPath(workerUrl)}`;
|
|
4193
3153
|
} catch {
|
|
4194
|
-
|
|
3154
|
+
try {
|
|
3155
|
+
const bundledWorker = join3(fileURLToPath(new URL(".", import.meta.url)), "worker-entry.js");
|
|
3156
|
+
return `node ${bundledWorker}`;
|
|
3157
|
+
} catch {
|
|
3158
|
+
return DEFAULT_WORKER_COMMAND;
|
|
3159
|
+
}
|
|
4195
3160
|
}
|
|
4196
3161
|
}
|
|
4197
3162
|
function createStore(runtimeRoot = ".runtime", options = {}) {
|
|
@@ -4234,7 +3199,7 @@ function createProjectItemsCache() {
|
|
|
4234
3199
|
};
|
|
4235
3200
|
}
|
|
4236
3201
|
function wait2(ms) {
|
|
4237
|
-
return new Promise((
|
|
3202
|
+
return new Promise((resolve4) => setTimeout(resolve4, ms));
|
|
4238
3203
|
}
|
|
4239
3204
|
function createRunId(now, projectId, issueIdentifier) {
|
|
4240
3205
|
return [
|
|
@@ -4272,8 +3237,8 @@ function isActiveRunStatus(status) {
|
|
|
4272
3237
|
|
|
4273
3238
|
// ../orchestrator/dist/lock.js
|
|
4274
3239
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
4275
|
-
import { mkdir as mkdir4, open as open2, readFile as
|
|
4276
|
-
import { dirname as dirname2, isAbsolute, join as
|
|
3240
|
+
import { mkdir as mkdir4, open as open2, readFile as readFile4, rm as rm4 } from "fs/promises";
|
|
3241
|
+
import { dirname as dirname2, isAbsolute, join as join4, relative as relative2, resolve as resolve2 } from "path";
|
|
4277
3242
|
import { setTimeout as delay } from "timers/promises";
|
|
4278
3243
|
var LOCK_READ_RETRY_DELAY_MS = 10;
|
|
4279
3244
|
var LOCK_READ_RETRY_LIMIT = 20;
|
|
@@ -4339,7 +3304,7 @@ async function releaseProjectLock(lock) {
|
|
|
4339
3304
|
}
|
|
4340
3305
|
async function readProjectLock(lockPath) {
|
|
4341
3306
|
try {
|
|
4342
|
-
const raw = await
|
|
3307
|
+
const raw = await readFile4(lockPath, "utf8");
|
|
4343
3308
|
const record = parseProjectLock(raw);
|
|
4344
3309
|
if (!record) {
|
|
4345
3310
|
return { status: "invalid" };
|
|
@@ -4359,13 +3324,13 @@ function assertValidProjectId(projectId) {
|
|
|
4359
3324
|
}
|
|
4360
3325
|
function resolveProjectLockPath(runtimeRoot, projectId) {
|
|
4361
3326
|
const store = new OrchestratorFsStore(runtimeRoot);
|
|
4362
|
-
const projectsRoot =
|
|
4363
|
-
const projectDir =
|
|
3327
|
+
const projectsRoot = resolve2(runtimeRoot, "projects");
|
|
3328
|
+
const projectDir = resolve2(store.projectDir(projectId));
|
|
4364
3329
|
const relativeProjectDir = relative2(projectsRoot, projectDir);
|
|
4365
3330
|
if (relativeProjectDir.length === 0 || relativeProjectDir.startsWith("..") || isAbsolute(relativeProjectDir)) {
|
|
4366
3331
|
throw new Error(`Invalid project ID "${projectId}". Project lock path must stay within "${projectsRoot}".`);
|
|
4367
3332
|
}
|
|
4368
|
-
return
|
|
3333
|
+
return join4(projectDir, ".lock");
|
|
4369
3334
|
}
|
|
4370
3335
|
function parseProjectLock(raw) {
|
|
4371
3336
|
try {
|
|
@@ -4399,7 +3364,7 @@ function isMissingFileError2(error) {
|
|
|
4399
3364
|
|
|
4400
3365
|
// ../orchestrator/dist/index.js
|
|
4401
3366
|
import { pathToFileURL } from "url";
|
|
4402
|
-
import { resolve as
|
|
3367
|
+
import { resolve as resolve3 } from "path";
|
|
4403
3368
|
function resolveOrchestratorLogLevel(value) {
|
|
4404
3369
|
if (!value || value === "normal") {
|
|
4405
3370
|
return "normal";
|
|
@@ -4415,7 +3380,7 @@ async function runCli(argv, dependencies = {}) {
|
|
|
4415
3380
|
if (parsed.projectId) {
|
|
4416
3381
|
assertValidProjectId(parsed.projectId);
|
|
4417
3382
|
}
|
|
4418
|
-
const runtimeRoot =
|
|
3383
|
+
const runtimeRoot = resolve3(parsed.runtimeRoot ?? ".runtime");
|
|
4419
3384
|
const stderr = dependencies.stderr ?? process.stderr;
|
|
4420
3385
|
const eventsDir = resolveOptionalPath(parsed.eventsDir ?? process.env.SYMPHONY_EVENTS_DIR);
|
|
4421
3386
|
const logLevel = resolveOrchestratorLogLevel(parsed.logLevel ?? process.env.SYMPHONY_LOG_LEVEL);
|
|
@@ -4590,7 +3555,7 @@ function resolveOptionalPath(value) {
|
|
|
4590
3555
|
if (!value || value.trim().length === 0) {
|
|
4591
3556
|
return void 0;
|
|
4592
3557
|
}
|
|
4593
|
-
return
|
|
3558
|
+
return resolve3(value.trim());
|
|
4594
3559
|
}
|
|
4595
3560
|
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
4596
3561
|
main().catch((error) => {
|
|
@@ -4601,13 +3566,6 @@ if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href)
|
|
|
4601
3566
|
}
|
|
4602
3567
|
|
|
4603
3568
|
export {
|
|
4604
|
-
deriveIssueWorkspaceKeyFromIdentifier,
|
|
4605
|
-
readJsonFile,
|
|
4606
|
-
safeReadDir,
|
|
4607
|
-
isFileMissing,
|
|
4608
|
-
parseRecentEvents,
|
|
4609
|
-
isMatchingIssueRun,
|
|
4610
|
-
mapIssueOrchestrationStateToStatus,
|
|
4611
3569
|
OrchestratorService,
|
|
4612
3570
|
createStore,
|
|
4613
3571
|
acquireProjectLock,
|