@gh-symphony/cli 0.0.14 → 0.0.16
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-5NV3LSAJ.js +11 -0
- package/dist/chunk-6HBZC3BE.js +468 -0
- package/dist/chunk-76QPITKI.js +109 -0
- package/dist/chunk-EFMFGOWM.js +3575 -0
- package/dist/chunk-IWR4UQEJ.js +2250 -0
- package/dist/chunk-JO3AXHQI.js +130 -0
- package/dist/chunk-MHIWAIVD.js +876 -0
- package/dist/chunk-MVRF7BES.js +68 -0
- package/dist/chunk-ROGRTUFI.js +108 -0
- package/dist/chunk-TF3QNWNC.js +1121 -0
- package/dist/chunk-TH5QPO3Y.js +67 -0
- package/dist/config-cmd-AZ7POMAA.js +110 -0
- package/dist/index.d.ts +5 -4
- package/dist/index.js +568 -356
- package/dist/init-EZXQAXZM.js +17 -0
- package/dist/logs-6LNGT2GF.js +188 -0
- package/dist/project-557FE2GD.js +679 -0
- package/dist/recover-LVBI2TGH.js +131 -0
- package/dist/repo-R3XBIVAX.js +121 -0
- package/dist/run-WITYAYFZ.js +108 -0
- package/dist/start-JUFKNL3N.js +16 -0
- package/dist/status-3WK5BWRZ.js +11 -0
- package/dist/stop-AA3AP5M6.js +9 -0
- package/dist/version-VBB62JWI.js +30 -0
- package/dist/worker-entry.js +1828 -0
- package/package.json +9 -4
- package/dist/ansi.d.ts +0 -15
- package/dist/ansi.js +0 -53
- package/dist/commands/config-cmd.d.ts +0 -3
- package/dist/commands/config-cmd.js +0 -90
- package/dist/commands/help.d.ts +0 -3
- package/dist/commands/help.js +0 -55
- package/dist/commands/init.d.ts +0 -34
- package/dist/commands/init.js +0 -477
- package/dist/commands/logs.d.ts +0 -3
- package/dist/commands/logs.js +0 -184
- package/dist/commands/project.d.ts +0 -3
- package/dist/commands/project.js +0 -649
- package/dist/commands/recover.d.ts +0 -3
- package/dist/commands/recover.js +0 -119
- package/dist/commands/repo.d.ts +0 -3
- package/dist/commands/repo.js +0 -103
- package/dist/commands/run.d.ts +0 -3
- package/dist/commands/run.js +0 -95
- package/dist/commands/start.d.ts +0 -20
- package/dist/commands/start.js +0 -344
- package/dist/commands/status-refresh.d.ts +0 -9
- package/dist/commands/status-refresh.js +0 -27
- package/dist/commands/status.d.ts +0 -3
- package/dist/commands/status.js +0 -237
- package/dist/commands/stop.d.ts +0 -3
- package/dist/commands/stop.js +0 -92
- package/dist/commands/version.d.ts +0 -3
- package/dist/commands/version.js +0 -21
- package/dist/completion.d.ts +0 -1
- package/dist/completion.js +0 -204
- package/dist/config.d.ts +0 -38
- package/dist/config.js +0 -82
- package/dist/context/context-types.d.ts +0 -36
- package/dist/context/context-types.js +0 -1
- package/dist/context/generate-context-yaml.d.ts +0 -15
- package/dist/context/generate-context-yaml.js +0 -129
- package/dist/dashboard/renderer.d.ts +0 -9
- package/dist/dashboard/renderer.js +0 -220
- package/dist/detection/environment-detector.d.ts +0 -11
- package/dist/detection/environment-detector.js +0 -140
- package/dist/github/client.d.ts +0 -71
- package/dist/github/client.js +0 -348
- package/dist/github/gh-auth.d.ts +0 -34
- package/dist/github/gh-auth.js +0 -110
- package/dist/mapping/smart-defaults.d.ts +0 -17
- package/dist/mapping/smart-defaults.js +0 -86
- package/dist/orchestrator-runtime.d.ts +0 -1
- package/dist/orchestrator-runtime.js +0 -4
- package/dist/orchestrator-status-endpoint.d.ts +0 -5
- package/dist/orchestrator-status-endpoint.js +0 -27
- package/dist/project-selection.d.ts +0 -8
- package/dist/project-selection.js +0 -56
- package/dist/skills/skill-writer.d.ts +0 -14
- package/dist/skills/skill-writer.js +0 -62
- package/dist/skills/templates/commit.d.ts +0 -2
- package/dist/skills/templates/commit.js +0 -45
- package/dist/skills/templates/document.d.ts +0 -7
- package/dist/skills/templates/document.js +0 -16
- package/dist/skills/templates/gh-project.d.ts +0 -2
- package/dist/skills/templates/gh-project.js +0 -88
- package/dist/skills/templates/gh-symphony.d.ts +0 -2
- package/dist/skills/templates/gh-symphony.js +0 -125
- package/dist/skills/templates/index.d.ts +0 -8
- package/dist/skills/templates/index.js +0 -28
- package/dist/skills/templates/land.d.ts +0 -2
- package/dist/skills/templates/land.js +0 -59
- package/dist/skills/templates/pull.d.ts +0 -2
- package/dist/skills/templates/pull.js +0 -41
- package/dist/skills/templates/push.d.ts +0 -2
- package/dist/skills/templates/push.js +0 -36
- package/dist/skills/types.d.ts +0 -23
- package/dist/skills/types.js +0 -1
- package/dist/workflow/generate-reference-workflow.d.ts +0 -9
- package/dist/workflow/generate-reference-workflow.js +0 -261
- package/dist/workflow/generate-workflow-md.d.ts +0 -12
- package/dist/workflow/generate-workflow-md.js +0 -134
|
@@ -0,0 +1,1121 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// ../core/dist/contracts/status-surface.js
|
|
4
|
+
var WORKFLOW_EXECUTION_PHASES = [
|
|
5
|
+
"planning",
|
|
6
|
+
"human-review",
|
|
7
|
+
"implementation",
|
|
8
|
+
"awaiting-merge",
|
|
9
|
+
"completed"
|
|
10
|
+
];
|
|
11
|
+
function isWorkflowExecutionPhase(value) {
|
|
12
|
+
return typeof value === "string" && WORKFLOW_EXECUTION_PHASES.includes(value);
|
|
13
|
+
}
|
|
14
|
+
var SESSION_EXIT_CLASSIFICATIONS = [
|
|
15
|
+
"completed",
|
|
16
|
+
"budget-exceeded",
|
|
17
|
+
"convergence-detected",
|
|
18
|
+
"max-turns-reached",
|
|
19
|
+
"user-input-required",
|
|
20
|
+
"timeout",
|
|
21
|
+
"error"
|
|
22
|
+
];
|
|
23
|
+
function isSessionExitClassification(value) {
|
|
24
|
+
return typeof value === "string" && SESSION_EXIT_CLASSIFICATIONS.includes(value);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// ../core/dist/contracts/run-attempt-phase.js
|
|
28
|
+
var RUN_ATTEMPT_PHASES = [
|
|
29
|
+
"preparing_workspace",
|
|
30
|
+
"building_prompt",
|
|
31
|
+
"launching_agent",
|
|
32
|
+
"initializing_session",
|
|
33
|
+
"streaming_turn",
|
|
34
|
+
"finishing",
|
|
35
|
+
"succeeded",
|
|
36
|
+
"failed",
|
|
37
|
+
"timed_out",
|
|
38
|
+
"stalled",
|
|
39
|
+
"canceled_by_reconciliation"
|
|
40
|
+
];
|
|
41
|
+
function isRunAttemptPhase(value) {
|
|
42
|
+
return typeof value === "string" && RUN_ATTEMPT_PHASES.includes(value);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ../core/dist/contracts/orchestrator-channel.js
|
|
46
|
+
function isRecord(value) {
|
|
47
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
48
|
+
}
|
|
49
|
+
function isTokenUsage(value) {
|
|
50
|
+
if (!isRecord(value)) {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
return typeof value.inputTokens === "number" && typeof value.outputTokens === "number" && typeof value.totalTokens === "number";
|
|
54
|
+
}
|
|
55
|
+
function isSessionInfo(value) {
|
|
56
|
+
if (!isRecord(value)) {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
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));
|
|
60
|
+
}
|
|
61
|
+
function isNullableString(value) {
|
|
62
|
+
return typeof value === "string" || value === null;
|
|
63
|
+
}
|
|
64
|
+
function isTurnEventBase(value) {
|
|
65
|
+
return typeof value.startedAt === "string" && isNullableString(value.threadId) && isNullableString(value.turnId) && typeof value.turnCount === "number" && isNullableString(value.sessionId);
|
|
66
|
+
}
|
|
67
|
+
function isOrchestratorChannelEvent(value) {
|
|
68
|
+
if (!isRecord(value)) {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
if (typeof value.issueId !== "string") {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
if (value.type === "codex_update") {
|
|
75
|
+
if (typeof value.lastEventAt !== "string") {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
if ("event" in value && value.event !== void 0 && typeof value.event !== "string") {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
if ("tokenUsage" in value && value.tokenUsage !== void 0 && !isTokenUsage(value.tokenUsage)) {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
if ("rateLimits" in value && value.rateLimits !== void 0 && !isRecord(value.rateLimits)) {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
if ("sessionInfo" in value && value.sessionInfo !== void 0 && !isSessionInfo(value.sessionInfo)) {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
if ("executionPhase" in value && value.executionPhase !== void 0 && value.executionPhase !== null && !isWorkflowExecutionPhase(value.executionPhase)) {
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
if ("runPhase" in value && value.runPhase !== void 0 && value.runPhase !== null && !isRunAttemptPhase(value.runPhase)) {
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
if ("lastError" in value && value.lastError !== void 0 && value.lastError !== null && typeof value.lastError !== "string") {
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
return true;
|
|
100
|
+
}
|
|
101
|
+
if (value.type === "heartbeat") {
|
|
102
|
+
if (value.lastEventAt !== null && typeof value.lastEventAt !== "string") {
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
if (!isTokenUsage(value.tokenUsage)) {
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
if (value.rateLimits !== null && !isRecord(value.rateLimits)) {
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
if (value.sessionInfo !== null && !isSessionInfo(value.sessionInfo)) {
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
if (value.executionPhase !== null && !isWorkflowExecutionPhase(value.executionPhase)) {
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
if (value.runPhase !== null && !isRunAttemptPhase(value.runPhase)) {
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
if (value.lastError !== null && typeof value.lastError !== "string") {
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
return true;
|
|
124
|
+
}
|
|
125
|
+
if (value.type === "turn_started") {
|
|
126
|
+
return isTurnEventBase(value);
|
|
127
|
+
}
|
|
128
|
+
if (value.type === "turn_completed") {
|
|
129
|
+
return isTurnEventBase(value) && typeof value.completedAt === "string" && typeof value.durationMs === "number" && isTokenUsage(value.tokenUsage);
|
|
130
|
+
}
|
|
131
|
+
if (value.type === "turn_failed") {
|
|
132
|
+
return isTurnEventBase(value) && typeof value.failedAt === "string" && typeof value.durationMs === "number" && isTokenUsage(value.tokenUsage) && isNullableString(value.error);
|
|
133
|
+
}
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// ../core/dist/workflow/lifecycle.js
|
|
138
|
+
var DEFAULT_WORKFLOW_LIFECYCLE = {
|
|
139
|
+
stateFieldName: "Status",
|
|
140
|
+
activeStates: ["Todo", "In Progress"],
|
|
141
|
+
terminalStates: ["Done"],
|
|
142
|
+
blockerCheckStates: ["Todo"]
|
|
143
|
+
};
|
|
144
|
+
function isStateActive(state, lifecycle) {
|
|
145
|
+
return matchesWorkflowState(state, lifecycle.activeStates);
|
|
146
|
+
}
|
|
147
|
+
function isStateTerminal(state, lifecycle) {
|
|
148
|
+
return matchesWorkflowState(state, lifecycle.terminalStates);
|
|
149
|
+
}
|
|
150
|
+
function matchesWorkflowState(state, candidates) {
|
|
151
|
+
const normalizedState = normalizeWorkflowState(state);
|
|
152
|
+
return candidates.some((candidate) => normalizeWorkflowState(candidate) === normalizedState);
|
|
153
|
+
}
|
|
154
|
+
function normalizeWorkflowState(state) {
|
|
155
|
+
return state.trim().toLowerCase();
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// ../core/dist/workflow/config.js
|
|
159
|
+
var DEFAULT_CODEX_COMMAND = "codex app-server";
|
|
160
|
+
var DEFAULT_AGENT_COMMAND = DEFAULT_CODEX_COMMAND;
|
|
161
|
+
var DEFAULT_HOOK_TIMEOUT_MS = 6e4;
|
|
162
|
+
var DEFAULT_POLL_INTERVAL_MS = 3e4;
|
|
163
|
+
var DEFAULT_MAX_RETRY_BACKOFF_MS = 3e5;
|
|
164
|
+
var DEFAULT_MAX_DELAY_MS = DEFAULT_MAX_RETRY_BACKOFF_MS;
|
|
165
|
+
var DEFAULT_BASE_DELAY_MS = 1e4;
|
|
166
|
+
var DEFAULT_MAX_TURNS = 20;
|
|
167
|
+
var DEFAULT_READ_TIMEOUT_MS = 5e3;
|
|
168
|
+
var DEFAULT_TURN_TIMEOUT_MS = 36e5;
|
|
169
|
+
var DEFAULT_STALL_TIMEOUT_MS = 3e5;
|
|
170
|
+
var DEFAULT_MAX_CONCURRENT_AGENTS = 10;
|
|
171
|
+
var DEFAULT_WORKFLOW_HOOKS = {
|
|
172
|
+
afterCreate: null,
|
|
173
|
+
beforeRun: null,
|
|
174
|
+
afterRun: null,
|
|
175
|
+
beforeRemove: null,
|
|
176
|
+
timeoutMs: DEFAULT_HOOK_TIMEOUT_MS
|
|
177
|
+
};
|
|
178
|
+
var DEFAULT_WORKFLOW_TRACKER = {
|
|
179
|
+
kind: null,
|
|
180
|
+
endpoint: null,
|
|
181
|
+
apiKey: null,
|
|
182
|
+
projectSlug: null,
|
|
183
|
+
activeStates: DEFAULT_WORKFLOW_LIFECYCLE.activeStates,
|
|
184
|
+
terminalStates: DEFAULT_WORKFLOW_LIFECYCLE.terminalStates,
|
|
185
|
+
projectId: null,
|
|
186
|
+
stateFieldName: DEFAULT_WORKFLOW_LIFECYCLE.stateFieldName,
|
|
187
|
+
priorityFieldName: null,
|
|
188
|
+
blockerCheckStates: DEFAULT_WORKFLOW_LIFECYCLE.blockerCheckStates
|
|
189
|
+
};
|
|
190
|
+
var DEFAULT_WORKFLOW_WORKSPACE = {
|
|
191
|
+
root: null
|
|
192
|
+
};
|
|
193
|
+
var DEFAULT_WORKFLOW_AGENT = {
|
|
194
|
+
maxConcurrentAgents: DEFAULT_MAX_CONCURRENT_AGENTS,
|
|
195
|
+
maxRetryBackoffMs: DEFAULT_MAX_RETRY_BACKOFF_MS,
|
|
196
|
+
maxConcurrentAgentsByState: {},
|
|
197
|
+
maxTurns: DEFAULT_MAX_TURNS,
|
|
198
|
+
retryBaseDelayMs: DEFAULT_BASE_DELAY_MS
|
|
199
|
+
};
|
|
200
|
+
var DEFAULT_WORKFLOW_CODEX = {
|
|
201
|
+
command: DEFAULT_CODEX_COMMAND,
|
|
202
|
+
approvalPolicy: null,
|
|
203
|
+
threadSandbox: null,
|
|
204
|
+
turnSandboxPolicy: null,
|
|
205
|
+
turnTimeoutMs: DEFAULT_TURN_TIMEOUT_MS,
|
|
206
|
+
readTimeoutMs: DEFAULT_READ_TIMEOUT_MS,
|
|
207
|
+
stallTimeoutMs: DEFAULT_STALL_TIMEOUT_MS
|
|
208
|
+
};
|
|
209
|
+
var DEFAULT_WORKFLOW_DEFINITION = {
|
|
210
|
+
promptTemplate: "",
|
|
211
|
+
continuationGuidance: null,
|
|
212
|
+
tracker: DEFAULT_WORKFLOW_TRACKER,
|
|
213
|
+
polling: {
|
|
214
|
+
intervalMs: DEFAULT_POLL_INTERVAL_MS
|
|
215
|
+
},
|
|
216
|
+
workspace: DEFAULT_WORKFLOW_WORKSPACE,
|
|
217
|
+
hooks: DEFAULT_WORKFLOW_HOOKS,
|
|
218
|
+
agent: DEFAULT_WORKFLOW_AGENT,
|
|
219
|
+
codex: DEFAULT_WORKFLOW_CODEX,
|
|
220
|
+
lifecycle: DEFAULT_WORKFLOW_LIFECYCLE,
|
|
221
|
+
format: "default",
|
|
222
|
+
githubProjectId: null,
|
|
223
|
+
agentCommand: DEFAULT_CODEX_COMMAND,
|
|
224
|
+
hookPath: null,
|
|
225
|
+
maxConcurrentByState: {}
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
// ../core/dist/workflow/parser.js
|
|
229
|
+
function parseWorkflowMarkdown(markdown, env = process.env, options = {}) {
|
|
230
|
+
const compatibilityMode = options.compatibilityMode ?? "strict";
|
|
231
|
+
const frontMatterMatch = markdown.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
|
|
232
|
+
if (!frontMatterMatch) {
|
|
233
|
+
if (compatibilityMode === "legacy") {
|
|
234
|
+
return parseLegacyWorkflowMarkdown(markdown);
|
|
235
|
+
}
|
|
236
|
+
throw new Error("WORKFLOW.md must use YAML front matter.");
|
|
237
|
+
}
|
|
238
|
+
const [, rawFrontMatter, rawPromptTemplate = ""] = frontMatterMatch;
|
|
239
|
+
const frontMatter = parseFrontMatter(rawFrontMatter);
|
|
240
|
+
const promptTemplate = rawPromptTemplate.trim();
|
|
241
|
+
const tracker = readRequiredObject(frontMatter, "tracker");
|
|
242
|
+
const polling = readObject(frontMatter, "polling");
|
|
243
|
+
const workspace = readObject(frontMatter, "workspace");
|
|
244
|
+
const hooks = readObject(frontMatter, "hooks");
|
|
245
|
+
const agent = readObject(frontMatter, "agent");
|
|
246
|
+
const codex = readRequiredObject(frontMatter, "codex");
|
|
247
|
+
const trackerKind = readRequiredString(tracker, "kind", env);
|
|
248
|
+
const activeStates = readStringList(tracker, "active_states") ?? DEFAULT_WORKFLOW_TRACKER.activeStates;
|
|
249
|
+
const terminalStates = readStringList(tracker, "terminal_states") ?? DEFAULT_WORKFLOW_TRACKER.terminalStates;
|
|
250
|
+
const blockerCheckStates = readStringList(tracker, "blocker_check_states") ?? DEFAULT_WORKFLOW_TRACKER.blockerCheckStates;
|
|
251
|
+
const maxConcurrentAgentsByState = readNumberMap(agent, "max_concurrent_agents_by_state");
|
|
252
|
+
const command = readOptionalString(codex, "command", env) ?? DEFAULT_AGENT_COMMAND;
|
|
253
|
+
const parsed = {
|
|
254
|
+
promptTemplate,
|
|
255
|
+
continuationGuidance: readOptionalWorkflowString(frontMatter, "continuationGuidance", "continuation_guidance", env),
|
|
256
|
+
tracker: {
|
|
257
|
+
kind: trackerKind,
|
|
258
|
+
endpoint: readOptionalString(tracker, "endpoint", env),
|
|
259
|
+
apiKey: readOptionalString(tracker, "api_key", env),
|
|
260
|
+
projectSlug: readOptionalString(tracker, "project_slug", env),
|
|
261
|
+
activeStates,
|
|
262
|
+
terminalStates,
|
|
263
|
+
projectId: readOptionalString(tracker, "project_id", env),
|
|
264
|
+
stateFieldName: readOptionalString(tracker, "state_field", env) ?? DEFAULT_WORKFLOW_TRACKER.stateFieldName,
|
|
265
|
+
priorityFieldName: readOptionalString(tracker, "priority_field", env),
|
|
266
|
+
blockerCheckStates
|
|
267
|
+
},
|
|
268
|
+
polling: {
|
|
269
|
+
intervalMs: readOptionalIntegerLike(polling, "interval_ms") ?? DEFAULT_POLL_INTERVAL_MS
|
|
270
|
+
},
|
|
271
|
+
workspace: {
|
|
272
|
+
root: readOptionalString(workspace, "root", env)
|
|
273
|
+
},
|
|
274
|
+
hooks: {
|
|
275
|
+
afterCreate: readOptionalString(hooks, "after_create", env),
|
|
276
|
+
beforeRun: readOptionalString(hooks, "before_run", env),
|
|
277
|
+
afterRun: readOptionalString(hooks, "after_run", env),
|
|
278
|
+
beforeRemove: readOptionalString(hooks, "before_remove", env),
|
|
279
|
+
timeoutMs: readOptionalIntegerLike(hooks, "timeout_ms") ?? DEFAULT_HOOK_TIMEOUT_MS
|
|
280
|
+
},
|
|
281
|
+
agent: {
|
|
282
|
+
maxConcurrentAgents: readOptionalIntegerLike(agent, "max_concurrent_agents") ?? DEFAULT_MAX_CONCURRENT_AGENTS,
|
|
283
|
+
maxRetryBackoffMs: readOptionalIntegerLike(agent, "max_retry_backoff_ms") ?? DEFAULT_MAX_RETRY_BACKOFF_MS,
|
|
284
|
+
maxConcurrentAgentsByState,
|
|
285
|
+
maxTurns: readOptionalIntegerLike(agent, "max_turns") ?? DEFAULT_MAX_TURNS,
|
|
286
|
+
retryBaseDelayMs: readOptionalIntegerLike(agent, "retry_base_delay_ms") ?? DEFAULT_BASE_DELAY_MS
|
|
287
|
+
},
|
|
288
|
+
codex: {
|
|
289
|
+
command,
|
|
290
|
+
approvalPolicy: readOptionalString(codex, "approval_policy", env),
|
|
291
|
+
threadSandbox: readOptionalString(codex, "thread_sandbox", env),
|
|
292
|
+
turnSandboxPolicy: readOptionalString(codex, "turn_sandbox_policy", env),
|
|
293
|
+
turnTimeoutMs: readOptionalIntegerLike(codex, "turn_timeout_ms") ?? DEFAULT_TURN_TIMEOUT_MS,
|
|
294
|
+
readTimeoutMs: readOptionalIntegerLike(codex, "read_timeout_ms") ?? DEFAULT_READ_TIMEOUT_MS,
|
|
295
|
+
stallTimeoutMs: readOptionalIntegerLike(codex, "stall_timeout_ms") ?? DEFAULT_STALL_TIMEOUT_MS
|
|
296
|
+
},
|
|
297
|
+
lifecycle: {
|
|
298
|
+
stateFieldName: readOptionalString(tracker, "state_field", env) ?? DEFAULT_WORKFLOW_TRACKER.stateFieldName,
|
|
299
|
+
activeStates,
|
|
300
|
+
terminalStates,
|
|
301
|
+
blockerCheckStates
|
|
302
|
+
},
|
|
303
|
+
format: "front-matter",
|
|
304
|
+
githubProjectId: readOptionalString(tracker, "project_id", env),
|
|
305
|
+
agentCommand: command,
|
|
306
|
+
hookPath: readOptionalString(hooks, "after_create", env),
|
|
307
|
+
maxConcurrentByState: maxConcurrentAgentsByState
|
|
308
|
+
};
|
|
309
|
+
return parsed;
|
|
310
|
+
}
|
|
311
|
+
function parseLegacyWorkflowMarkdown(markdown) {
|
|
312
|
+
const promptGuidelines = matchOptionalSection(markdown, "Prompt Guidelines") ?? "";
|
|
313
|
+
return {
|
|
314
|
+
...DEFAULT_WORKFLOW_DEFINITION,
|
|
315
|
+
promptTemplate: promptGuidelines,
|
|
316
|
+
format: "legacy-sectioned"
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
function parseFrontMatter(frontMatter) {
|
|
320
|
+
const lines = frontMatter.replace(/\r\n/g, "\n").split("\n");
|
|
321
|
+
const [value] = parseBlock(lines, 0, 0);
|
|
322
|
+
if (!value || Array.isArray(value) || typeof value !== "object") {
|
|
323
|
+
throw new Error("Workflow front matter must be a YAML object.");
|
|
324
|
+
}
|
|
325
|
+
return value;
|
|
326
|
+
}
|
|
327
|
+
function parseBlock(lines, startIndex, indent) {
|
|
328
|
+
let index = startIndex;
|
|
329
|
+
let collectionType = null;
|
|
330
|
+
const arrayValues = [];
|
|
331
|
+
const objectValues = {};
|
|
332
|
+
while (index < lines.length) {
|
|
333
|
+
const line = lines[index] ?? "";
|
|
334
|
+
if (!line.trim()) {
|
|
335
|
+
index += 1;
|
|
336
|
+
continue;
|
|
337
|
+
}
|
|
338
|
+
const lineIndent = countIndent(line);
|
|
339
|
+
if (lineIndent < indent) {
|
|
340
|
+
break;
|
|
341
|
+
}
|
|
342
|
+
if (lineIndent > indent) {
|
|
343
|
+
throw new Error(`Invalid workflow front matter indentation near "${line.trim()}".`);
|
|
344
|
+
}
|
|
345
|
+
const trimmed = line.trim();
|
|
346
|
+
if (trimmed.startsWith("- ")) {
|
|
347
|
+
if (collectionType === "object") {
|
|
348
|
+
throw new Error("Cannot mix array and object values in workflow front matter.");
|
|
349
|
+
}
|
|
350
|
+
collectionType = "array";
|
|
351
|
+
const itemText = trimmed.slice(2).trim();
|
|
352
|
+
if (itemText === "|" || itemText === "|-") {
|
|
353
|
+
const [multiline, nextIndex3] = parseMultilineScalar(lines, index + 1, indent + 2);
|
|
354
|
+
arrayValues.push(multiline);
|
|
355
|
+
index = nextIndex3;
|
|
356
|
+
continue;
|
|
357
|
+
}
|
|
358
|
+
if (itemText) {
|
|
359
|
+
arrayValues.push(parseScalar(itemText));
|
|
360
|
+
index += 1;
|
|
361
|
+
continue;
|
|
362
|
+
}
|
|
363
|
+
const [child2, nextIndex2] = parseBlock(lines, index + 1, indent + 2);
|
|
364
|
+
arrayValues.push(child2);
|
|
365
|
+
index = nextIndex2;
|
|
366
|
+
continue;
|
|
367
|
+
}
|
|
368
|
+
if (collectionType === "array") {
|
|
369
|
+
throw new Error("Cannot mix object and array values in workflow front matter.");
|
|
370
|
+
}
|
|
371
|
+
collectionType = "object";
|
|
372
|
+
const separatorIndex = trimmed.indexOf(":");
|
|
373
|
+
if (separatorIndex < 0) {
|
|
374
|
+
throw new Error(`Invalid workflow front matter line "${trimmed}".`);
|
|
375
|
+
}
|
|
376
|
+
const key = trimmed.slice(0, separatorIndex).trim();
|
|
377
|
+
const remainder = trimmed.slice(separatorIndex + 1).trim();
|
|
378
|
+
if (remainder === "|" || remainder === "|-") {
|
|
379
|
+
const [multiline, nextIndex2] = parseMultilineScalar(lines, index + 1, indent + 2);
|
|
380
|
+
objectValues[key] = multiline;
|
|
381
|
+
index = nextIndex2;
|
|
382
|
+
continue;
|
|
383
|
+
}
|
|
384
|
+
if (remainder) {
|
|
385
|
+
objectValues[key] = parseScalar(remainder);
|
|
386
|
+
index += 1;
|
|
387
|
+
continue;
|
|
388
|
+
}
|
|
389
|
+
const [child, nextIndex] = parseBlock(lines, index + 1, indent + 2);
|
|
390
|
+
objectValues[key] = child;
|
|
391
|
+
index = nextIndex;
|
|
392
|
+
}
|
|
393
|
+
return [collectionType === "array" ? arrayValues : objectValues, index];
|
|
394
|
+
}
|
|
395
|
+
function parseMultilineScalar(lines, startIndex, indent) {
|
|
396
|
+
let index = startIndex;
|
|
397
|
+
const collected = [];
|
|
398
|
+
while (index < lines.length) {
|
|
399
|
+
const line = lines[index] ?? "";
|
|
400
|
+
if (!line.trim()) {
|
|
401
|
+
collected.push("");
|
|
402
|
+
index += 1;
|
|
403
|
+
continue;
|
|
404
|
+
}
|
|
405
|
+
const lineIndent = countIndent(line);
|
|
406
|
+
if (lineIndent < indent) {
|
|
407
|
+
break;
|
|
408
|
+
}
|
|
409
|
+
collected.push(line.slice(indent));
|
|
410
|
+
index += 1;
|
|
411
|
+
}
|
|
412
|
+
return [collected.join("\n").trimEnd(), index];
|
|
413
|
+
}
|
|
414
|
+
function countIndent(line) {
|
|
415
|
+
return line.match(/^ */)?.[0].length ?? 0;
|
|
416
|
+
}
|
|
417
|
+
function parseScalar(value) {
|
|
418
|
+
if (value === "null")
|
|
419
|
+
return null;
|
|
420
|
+
if (value === "true")
|
|
421
|
+
return true;
|
|
422
|
+
if (value === "false")
|
|
423
|
+
return false;
|
|
424
|
+
if (/^-?\d+$/.test(value))
|
|
425
|
+
return Number.parseInt(value, 10);
|
|
426
|
+
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
|
427
|
+
return value.slice(1, -1);
|
|
428
|
+
}
|
|
429
|
+
return value;
|
|
430
|
+
}
|
|
431
|
+
function readObject(input, key) {
|
|
432
|
+
const value = input[key];
|
|
433
|
+
if (value === void 0 || value === null) {
|
|
434
|
+
return {};
|
|
435
|
+
}
|
|
436
|
+
if (typeof value !== "object" || Array.isArray(value)) {
|
|
437
|
+
throw new Error(`Workflow front matter field "${key}" must be an object.`);
|
|
438
|
+
}
|
|
439
|
+
return value;
|
|
440
|
+
}
|
|
441
|
+
function readRequiredObject(input, key) {
|
|
442
|
+
if (!(key in input)) {
|
|
443
|
+
throw new Error(`Workflow front matter field "${key}" is required.`);
|
|
444
|
+
}
|
|
445
|
+
return readObject(input, key);
|
|
446
|
+
}
|
|
447
|
+
function readOptionalString(input, key, env) {
|
|
448
|
+
const value = input[key];
|
|
449
|
+
if (value === void 0 || value === null) {
|
|
450
|
+
return null;
|
|
451
|
+
}
|
|
452
|
+
if (typeof value !== "string") {
|
|
453
|
+
throw new Error(`Workflow front matter field "${key}" must be a string.`);
|
|
454
|
+
}
|
|
455
|
+
return resolveEnvironmentValue(value, env);
|
|
456
|
+
}
|
|
457
|
+
function readOptionalWorkflowString(input, primaryKey, fallbackKey, env) {
|
|
458
|
+
return readOptionalString(input, primaryKey, env) ?? readOptionalString(input, fallbackKey, env);
|
|
459
|
+
}
|
|
460
|
+
function readRequiredString(input, key, env) {
|
|
461
|
+
const value = readOptionalString(input, key, env);
|
|
462
|
+
if (!value) {
|
|
463
|
+
throw new Error(`Workflow front matter field "${key}" is required.`);
|
|
464
|
+
}
|
|
465
|
+
return value;
|
|
466
|
+
}
|
|
467
|
+
function readStringList(input, key) {
|
|
468
|
+
const value = input[key];
|
|
469
|
+
if (value === void 0 || value === null) {
|
|
470
|
+
return void 0;
|
|
471
|
+
}
|
|
472
|
+
if (typeof value === "string") {
|
|
473
|
+
return value.split(",").map((entry) => entry.trim()).filter(Boolean);
|
|
474
|
+
}
|
|
475
|
+
if (!Array.isArray(value) || value.some((entry) => typeof entry !== "string")) {
|
|
476
|
+
throw new Error(`Workflow front matter field "${key}" must be an array of strings or comma-separated string.`);
|
|
477
|
+
}
|
|
478
|
+
return value;
|
|
479
|
+
}
|
|
480
|
+
function readOptionalIntegerLike(input, key) {
|
|
481
|
+
const value = input[key];
|
|
482
|
+
if (value === void 0 || value === null) {
|
|
483
|
+
return null;
|
|
484
|
+
}
|
|
485
|
+
if (typeof value === "number") {
|
|
486
|
+
return value;
|
|
487
|
+
}
|
|
488
|
+
if (typeof value === "string" && /^-?\d+$/.test(value)) {
|
|
489
|
+
return Number.parseInt(value, 10);
|
|
490
|
+
}
|
|
491
|
+
throw new Error(`Workflow front matter field "${key}" must be an integer.`);
|
|
492
|
+
}
|
|
493
|
+
function readNumberMap(input, key) {
|
|
494
|
+
const value = input[key];
|
|
495
|
+
if (value === void 0 || value === null) {
|
|
496
|
+
return {};
|
|
497
|
+
}
|
|
498
|
+
if (typeof value !== "object" || Array.isArray(value)) {
|
|
499
|
+
throw new Error(`Workflow front matter field "${key}" must be an object.`);
|
|
500
|
+
}
|
|
501
|
+
const result = {};
|
|
502
|
+
for (const [entryKey, entryValue] of Object.entries(value)) {
|
|
503
|
+
if (typeof entryValue === "number") {
|
|
504
|
+
result[entryKey] = entryValue;
|
|
505
|
+
continue;
|
|
506
|
+
}
|
|
507
|
+
if (typeof entryValue === "string" && /^\d+$/.test(entryValue)) {
|
|
508
|
+
result[entryKey] = Number.parseInt(entryValue, 10);
|
|
509
|
+
continue;
|
|
510
|
+
}
|
|
511
|
+
throw new Error(`Workflow front matter field "${key}.${entryKey}" must be an integer.`);
|
|
512
|
+
}
|
|
513
|
+
return result;
|
|
514
|
+
}
|
|
515
|
+
function resolveEnvironmentValue(value, env) {
|
|
516
|
+
const envTokenMatch = value.match(/^(?:env:)?([A-Z0-9_]+)$/);
|
|
517
|
+
if (value.startsWith("env:") && envTokenMatch) {
|
|
518
|
+
const resolved = env[envTokenMatch[1]];
|
|
519
|
+
if (!resolved) {
|
|
520
|
+
throw new Error(`Workflow front matter requires environment variable ${envTokenMatch[1]}.`);
|
|
521
|
+
}
|
|
522
|
+
return resolved;
|
|
523
|
+
}
|
|
524
|
+
return value.replace(/\$\{([A-Z0-9_]+)\}/g, (_, name) => {
|
|
525
|
+
const resolved = env[name];
|
|
526
|
+
if (!resolved) {
|
|
527
|
+
throw new Error(`Workflow front matter requires environment variable ${name}.`);
|
|
528
|
+
}
|
|
529
|
+
return resolved;
|
|
530
|
+
});
|
|
531
|
+
}
|
|
532
|
+
function matchOptionalSection(markdown, heading) {
|
|
533
|
+
const escapedHeading = heading.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
534
|
+
const pattern = new RegExp(`## ${escapedHeading}\\n\\n([\\s\\S]*?)(?=\\n## |$)`);
|
|
535
|
+
const match = markdown.match(pattern);
|
|
536
|
+
return match?.[1]?.trim() ?? null;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
// ../core/dist/workflow/render.js
|
|
540
|
+
import { Liquid, ParseError, RenderError, TokenizationError, UndefinedVariableError } from "liquidjs";
|
|
541
|
+
function buildPromptVariables(issue, options) {
|
|
542
|
+
return {
|
|
543
|
+
issue: {
|
|
544
|
+
id: issue.id,
|
|
545
|
+
identifier: issue.identifier,
|
|
546
|
+
number: issue.number,
|
|
547
|
+
title: issue.title,
|
|
548
|
+
description: issue.description,
|
|
549
|
+
priority: issue.priority,
|
|
550
|
+
url: issue.url,
|
|
551
|
+
state: issue.state,
|
|
552
|
+
labels: issue.labels,
|
|
553
|
+
blocked_by: issue.blockedBy,
|
|
554
|
+
branch_name: issue.branchName,
|
|
555
|
+
created_at: issue.createdAt,
|
|
556
|
+
updated_at: issue.updatedAt,
|
|
557
|
+
repository: `${issue.repository.owner}/${issue.repository.name}`
|
|
558
|
+
},
|
|
559
|
+
attempt: options.attempt
|
|
560
|
+
};
|
|
561
|
+
}
|
|
562
|
+
var STRICT_LIQUID_ENGINE = new Liquid({
|
|
563
|
+
strictVariables: true,
|
|
564
|
+
strictFilters: true,
|
|
565
|
+
ownPropertyOnly: true
|
|
566
|
+
});
|
|
567
|
+
function renderPrompt(template, variables, options = {}) {
|
|
568
|
+
const strict = options.strict ?? true;
|
|
569
|
+
if (!strict) {
|
|
570
|
+
return renderLegacyPrompt(template, variables);
|
|
571
|
+
}
|
|
572
|
+
try {
|
|
573
|
+
return STRICT_LIQUID_ENGINE.parseAndRenderSync(template, variables);
|
|
574
|
+
} catch (error) {
|
|
575
|
+
throw normalizeTemplateError(error);
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
function normalizeTemplateError(error) {
|
|
579
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
580
|
+
if (error instanceof UndefinedVariableError || error instanceof RenderError || error instanceof ParseError && message.startsWith("undefined filter:")) {
|
|
581
|
+
return new Error(`template_render_error: ${message}`, { cause: error });
|
|
582
|
+
}
|
|
583
|
+
if (error instanceof ParseError || error instanceof TokenizationError) {
|
|
584
|
+
return new Error(`template_parse_error: ${message}`, { cause: error });
|
|
585
|
+
}
|
|
586
|
+
return new Error(`template_render_error: ${message}`, { cause: error });
|
|
587
|
+
}
|
|
588
|
+
function flattenVariables(obj, prefix = "") {
|
|
589
|
+
const result = /* @__PURE__ */ new Map();
|
|
590
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
591
|
+
const fullKey = prefix ? `${prefix}.${key}` : key;
|
|
592
|
+
if (value !== null && typeof value === "object" && !Array.isArray(value)) {
|
|
593
|
+
for (const [nestedKey, nestedValue] of flattenVariables(value, fullKey)) {
|
|
594
|
+
result.set(nestedKey, nestedValue);
|
|
595
|
+
}
|
|
596
|
+
} else {
|
|
597
|
+
result.set(fullKey, value);
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
return result;
|
|
601
|
+
}
|
|
602
|
+
function renderLegacyPrompt(template, variables) {
|
|
603
|
+
const flatVars = flattenVariables(variables);
|
|
604
|
+
return template.replace(/\{\{([a-zA-Z_][a-zA-Z0-9_.]*)\}\}/g, (match, key) => {
|
|
605
|
+
const value = flatVars.get(key);
|
|
606
|
+
if (value === void 0) {
|
|
607
|
+
return match;
|
|
608
|
+
}
|
|
609
|
+
if (value === null) {
|
|
610
|
+
return "";
|
|
611
|
+
}
|
|
612
|
+
return String(value);
|
|
613
|
+
});
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
// ../core/dist/workflow/exit-classification.js
|
|
617
|
+
function classifySessionExit(params) {
|
|
618
|
+
if (params.userInputRequired) {
|
|
619
|
+
return "user-input-required";
|
|
620
|
+
}
|
|
621
|
+
if (params.budgetExceeded) {
|
|
622
|
+
return "budget-exceeded";
|
|
623
|
+
}
|
|
624
|
+
if (params.convergenceDetected) {
|
|
625
|
+
return "convergence-detected";
|
|
626
|
+
}
|
|
627
|
+
if (params.runPhase === "timed_out" || params.runPhase === "stalled") {
|
|
628
|
+
return "timeout";
|
|
629
|
+
}
|
|
630
|
+
if (params.maxTurnsReached) {
|
|
631
|
+
return "max-turns-reached";
|
|
632
|
+
}
|
|
633
|
+
if (params.runPhase === "succeeded") {
|
|
634
|
+
return "completed";
|
|
635
|
+
}
|
|
636
|
+
return "error";
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
// ../core/dist/orchestration/retry-policy.js
|
|
640
|
+
function calculateRetryDelay(attempt, options = {}) {
|
|
641
|
+
const baseDelayMs = options.baseDelayMs ?? DEFAULT_BASE_DELAY_MS;
|
|
642
|
+
const maxDelayMs = options.maxDelayMs ?? DEFAULT_MAX_DELAY_MS;
|
|
643
|
+
const normalizedAttempt = Math.max(1, attempt);
|
|
644
|
+
const delay = baseDelayMs * 2 ** (normalizedAttempt - 1);
|
|
645
|
+
return Math.min(delay, maxDelayMs);
|
|
646
|
+
}
|
|
647
|
+
function scheduleRetryAt(now, attempt, options = {}) {
|
|
648
|
+
return new Date(now.getTime() + calculateRetryDelay(attempt, options));
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
// ../core/dist/workspace/env-file.js
|
|
652
|
+
import { existsSync, readFileSync } from "fs";
|
|
653
|
+
function readEnvFile(path) {
|
|
654
|
+
if (!existsSync(path)) {
|
|
655
|
+
return {};
|
|
656
|
+
}
|
|
657
|
+
return readFileSync(path, "utf8").split("\n").map((line) => line.trim()).filter((line) => line && !line.startsWith("#") && line.includes("=")).reduce((result, line) => {
|
|
658
|
+
const separatorIndex = line.indexOf("=");
|
|
659
|
+
const key = line.slice(0, separatorIndex).trim();
|
|
660
|
+
const value = line.slice(separatorIndex + 1).trim();
|
|
661
|
+
if (key) {
|
|
662
|
+
result[key] = value;
|
|
663
|
+
}
|
|
664
|
+
return result;
|
|
665
|
+
}, {});
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
// ../core/dist/workspace/identity.js
|
|
669
|
+
import { resolve, join } from "path";
|
|
670
|
+
import { createHash } from "crypto";
|
|
671
|
+
function deriveIssueWorkspaceKey(identity, issueIdentifier) {
|
|
672
|
+
if (issueIdentifier) {
|
|
673
|
+
return deriveIssueWorkspaceKeyFromIdentifier(issueIdentifier);
|
|
674
|
+
}
|
|
675
|
+
return deriveLegacyIssueWorkspaceKey(identity);
|
|
676
|
+
}
|
|
677
|
+
function deriveIssueWorkspaceKeyFromIdentifier(issueIdentifier) {
|
|
678
|
+
const sanitized = issueIdentifier.replace(/[^A-Za-z0-9._-]+/g, "_").replace(/^_+|_+$/g, "");
|
|
679
|
+
if (!sanitized || /^[.]+$/.test(sanitized)) {
|
|
680
|
+
return "issue";
|
|
681
|
+
}
|
|
682
|
+
return sanitized;
|
|
683
|
+
}
|
|
684
|
+
function deriveLegacyIssueWorkspaceKey(identity) {
|
|
685
|
+
const input = [
|
|
686
|
+
identity.projectId,
|
|
687
|
+
identity.adapter,
|
|
688
|
+
identity.issueSubjectId
|
|
689
|
+
].join(":");
|
|
690
|
+
return createHash("sha256").update(input).digest("hex").slice(0, 16);
|
|
691
|
+
}
|
|
692
|
+
function resolveIssueWorkspaceDirectory(projectDirectory, workspaceKey) {
|
|
693
|
+
const normalizedProjectDirectory = resolve(projectDirectory);
|
|
694
|
+
const candidate = resolve(normalizedProjectDirectory, "issues", workspaceKey);
|
|
695
|
+
if (!candidate.startsWith(`${normalizedProjectDirectory}/`)) {
|
|
696
|
+
throw new Error("Issue workspace path escapes the configured project directory.");
|
|
697
|
+
}
|
|
698
|
+
return candidate;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
// ../core/dist/workspace/hooks.js
|
|
702
|
+
import { spawn } from "child_process";
|
|
703
|
+
var DEFAULT_HOOK_TIMEOUT_MS2 = 6e4;
|
|
704
|
+
async function executeHook(options) {
|
|
705
|
+
const { kind, command, cwd, env, timeoutMs } = options;
|
|
706
|
+
const start = Date.now();
|
|
707
|
+
const normalizedCommand = normalizeHookCommand(command);
|
|
708
|
+
return new Promise((resolveResult) => {
|
|
709
|
+
let timedOut = false;
|
|
710
|
+
let timer = null;
|
|
711
|
+
const child = spawn("bash", ["-lc", normalizedCommand], {
|
|
712
|
+
cwd,
|
|
713
|
+
env: { ...process.env, ...env },
|
|
714
|
+
stdio: "pipe"
|
|
715
|
+
});
|
|
716
|
+
const stderrChunks = [];
|
|
717
|
+
child.stderr?.on("data", (chunk) => {
|
|
718
|
+
stderrChunks.push(chunk);
|
|
719
|
+
});
|
|
720
|
+
if (timeoutMs > 0) {
|
|
721
|
+
timer = setTimeout(() => {
|
|
722
|
+
timedOut = true;
|
|
723
|
+
child.kill("SIGTERM");
|
|
724
|
+
setTimeout(() => {
|
|
725
|
+
try {
|
|
726
|
+
child.kill("SIGKILL");
|
|
727
|
+
} catch {
|
|
728
|
+
}
|
|
729
|
+
}, 5e3);
|
|
730
|
+
}, timeoutMs);
|
|
731
|
+
}
|
|
732
|
+
child.on("close", (code) => {
|
|
733
|
+
if (timer) {
|
|
734
|
+
clearTimeout(timer);
|
|
735
|
+
}
|
|
736
|
+
const durationMs = Date.now() - start;
|
|
737
|
+
const stderr = Buffer.concat(stderrChunks).toString("utf8").trim();
|
|
738
|
+
if (timedOut) {
|
|
739
|
+
resolveResult({
|
|
740
|
+
kind,
|
|
741
|
+
outcome: "timeout",
|
|
742
|
+
exitCode: code,
|
|
743
|
+
durationMs,
|
|
744
|
+
error: `Hook "${kind}" timed out after ${timeoutMs}ms`
|
|
745
|
+
});
|
|
746
|
+
return;
|
|
747
|
+
}
|
|
748
|
+
if (code !== 0) {
|
|
749
|
+
resolveResult({
|
|
750
|
+
kind,
|
|
751
|
+
outcome: "failure",
|
|
752
|
+
exitCode: code,
|
|
753
|
+
durationMs,
|
|
754
|
+
error: stderr || `Hook "${kind}" exited with code ${code}`
|
|
755
|
+
});
|
|
756
|
+
return;
|
|
757
|
+
}
|
|
758
|
+
resolveResult({
|
|
759
|
+
kind,
|
|
760
|
+
outcome: "success",
|
|
761
|
+
exitCode: 0,
|
|
762
|
+
durationMs,
|
|
763
|
+
error: null
|
|
764
|
+
});
|
|
765
|
+
});
|
|
766
|
+
child.on("error", (err) => {
|
|
767
|
+
if (timer) {
|
|
768
|
+
clearTimeout(timer);
|
|
769
|
+
}
|
|
770
|
+
resolveResult({
|
|
771
|
+
kind,
|
|
772
|
+
outcome: "failure",
|
|
773
|
+
exitCode: null,
|
|
774
|
+
durationMs: Date.now() - start,
|
|
775
|
+
error: err.message
|
|
776
|
+
});
|
|
777
|
+
});
|
|
778
|
+
});
|
|
779
|
+
}
|
|
780
|
+
function buildHookEnv(context) {
|
|
781
|
+
const env = {
|
|
782
|
+
SYMPHONY_PROJECT_ID: context.projectId,
|
|
783
|
+
SYMPHONY_ISSUE_WORKSPACE_KEY: context.workspaceKey,
|
|
784
|
+
SYMPHONY_ISSUE_SUBJECT_ID: context.issueSubjectId,
|
|
785
|
+
SYMPHONY_ISSUE_IDENTIFIER: context.issueIdentifier,
|
|
786
|
+
SYMPHONY_WORKSPACE_PATH: context.workspacePath,
|
|
787
|
+
SYMPHONY_REPOSITORY_PATH: context.repositoryPath
|
|
788
|
+
};
|
|
789
|
+
if (context.runId) {
|
|
790
|
+
env.SYMPHONY_RUN_ID = context.runId;
|
|
791
|
+
}
|
|
792
|
+
if (context.state) {
|
|
793
|
+
env.SYMPHONY_ISSUE_STATE = context.state;
|
|
794
|
+
}
|
|
795
|
+
return env;
|
|
796
|
+
}
|
|
797
|
+
function resolveHookCommand(hooks, kind) {
|
|
798
|
+
switch (kind) {
|
|
799
|
+
case "after_create":
|
|
800
|
+
return hooks.afterCreate;
|
|
801
|
+
case "before_run":
|
|
802
|
+
return hooks.beforeRun;
|
|
803
|
+
case "after_run":
|
|
804
|
+
return hooks.afterRun;
|
|
805
|
+
case "before_remove":
|
|
806
|
+
return hooks.beforeRemove;
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
async function executeWorkspaceHook(options) {
|
|
810
|
+
const hookCommand = resolveHookCommand(options.hooks, options.kind);
|
|
811
|
+
if (!hookCommand) {
|
|
812
|
+
return {
|
|
813
|
+
kind: options.kind,
|
|
814
|
+
outcome: "skipped",
|
|
815
|
+
exitCode: null,
|
|
816
|
+
durationMs: 0,
|
|
817
|
+
error: null
|
|
818
|
+
};
|
|
819
|
+
}
|
|
820
|
+
return executeHook({
|
|
821
|
+
kind: options.kind,
|
|
822
|
+
command: hookCommand,
|
|
823
|
+
cwd: options.repositoryPath,
|
|
824
|
+
env: options.env,
|
|
825
|
+
timeoutMs: options.timeoutMs ?? DEFAULT_HOOK_TIMEOUT_MS2
|
|
826
|
+
});
|
|
827
|
+
}
|
|
828
|
+
function normalizeHookCommand(command) {
|
|
829
|
+
const trimmed = command.trim();
|
|
830
|
+
if (trimmed.includes("/") && !trimmed.startsWith("/") && !trimmed.startsWith("./") && !trimmed.startsWith("../") && !/\s/.test(trimmed)) {
|
|
831
|
+
return `bash ./${trimmed}`;
|
|
832
|
+
}
|
|
833
|
+
return command;
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
// ../core/dist/observability/snapshot-builder.js
|
|
837
|
+
function buildProjectSnapshot(input) {
|
|
838
|
+
const { project, activeRuns, allRuns, summary, lastTickAt, lastError, rateLimits } = input;
|
|
839
|
+
return {
|
|
840
|
+
projectId: project.projectId,
|
|
841
|
+
slug: project.slug,
|
|
842
|
+
tracker: {
|
|
843
|
+
adapter: project.tracker.adapter,
|
|
844
|
+
bindingId: project.tracker.bindingId
|
|
845
|
+
},
|
|
846
|
+
lastTickAt,
|
|
847
|
+
health: lastError ? "degraded" : activeRuns.length > 0 ? "running" : "idle",
|
|
848
|
+
summary: {
|
|
849
|
+
dispatched: summary.dispatched,
|
|
850
|
+
suppressed: summary.suppressed,
|
|
851
|
+
recovered: summary.recovered,
|
|
852
|
+
activeRuns: activeRuns.length
|
|
853
|
+
},
|
|
854
|
+
activeRuns: activeRuns.map((run) => ({
|
|
855
|
+
runId: run.runId,
|
|
856
|
+
issueIdentifier: run.issueIdentifier,
|
|
857
|
+
issueState: run.issueState,
|
|
858
|
+
status: run.status,
|
|
859
|
+
retryKind: run.retryKind,
|
|
860
|
+
port: run.port,
|
|
861
|
+
runtimeSession: run.runtimeSession ?? null,
|
|
862
|
+
// New fields from live worker data
|
|
863
|
+
processId: run.processId ?? null,
|
|
864
|
+
turnCount: run.turnCount,
|
|
865
|
+
startedAt: run.startedAt ?? null,
|
|
866
|
+
lastEvent: run.lastEvent ?? null,
|
|
867
|
+
lastEventAt: run.lastEventAt ?? null,
|
|
868
|
+
executionPhase: run.executionPhase ?? null,
|
|
869
|
+
runPhase: run.runPhase ?? null,
|
|
870
|
+
tokenUsage: run.tokenUsage
|
|
871
|
+
})),
|
|
872
|
+
retryQueue: activeRuns.filter((run) => run.status === "retrying" && run.retryKind).map((run) => ({
|
|
873
|
+
runId: run.runId,
|
|
874
|
+
issueIdentifier: run.issueIdentifier,
|
|
875
|
+
retryKind: run.retryKind ?? "failure",
|
|
876
|
+
nextRetryAt: run.nextRetryAt
|
|
877
|
+
})),
|
|
878
|
+
lastError,
|
|
879
|
+
codexTotals: aggregateTokenUsage(allRuns ?? activeRuns, lastTickAt),
|
|
880
|
+
rateLimits: rateLimits ?? null
|
|
881
|
+
};
|
|
882
|
+
}
|
|
883
|
+
function aggregateTokenUsage(runs, lastTickAt) {
|
|
884
|
+
let inputTokens = 0;
|
|
885
|
+
let outputTokens = 0;
|
|
886
|
+
let totalTokens = 0;
|
|
887
|
+
let earliestStart = null;
|
|
888
|
+
let latestEnd = null;
|
|
889
|
+
for (const run of runs) {
|
|
890
|
+
if (run.tokenUsage) {
|
|
891
|
+
inputTokens += run.tokenUsage.inputTokens;
|
|
892
|
+
outputTokens += run.tokenUsage.outputTokens;
|
|
893
|
+
totalTokens += run.tokenUsage.totalTokens;
|
|
894
|
+
}
|
|
895
|
+
if (run.startedAt) {
|
|
896
|
+
const start = new Date(run.startedAt).getTime();
|
|
897
|
+
if (earliestStart === null || start < earliestStart) {
|
|
898
|
+
earliestStart = start;
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
const end = run.completedAt ? new Date(run.completedAt).getTime() : new Date(lastTickAt).getTime();
|
|
902
|
+
if (latestEnd === null || end > latestEnd) {
|
|
903
|
+
latestEnd = end;
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
const secondsRunning = earliestStart !== null && latestEnd !== null ? Math.max(0, Math.round((latestEnd - earliestStart) / 1e3)) : 0;
|
|
907
|
+
return { inputTokens, outputTokens, totalTokens, secondsRunning };
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
// ../core/dist/observability/fs-reader.js
|
|
911
|
+
import { readFile, readdir } from "fs/promises";
|
|
912
|
+
async function readJsonFile(path) {
|
|
913
|
+
try {
|
|
914
|
+
const raw = await readFile(path, "utf8");
|
|
915
|
+
return JSON.parse(raw);
|
|
916
|
+
} catch (error) {
|
|
917
|
+
if (isFileMissing(error)) {
|
|
918
|
+
return null;
|
|
919
|
+
}
|
|
920
|
+
throw error;
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
async function safeReadDir(path) {
|
|
924
|
+
try {
|
|
925
|
+
return await readdir(path);
|
|
926
|
+
} catch (error) {
|
|
927
|
+
if (isFileMissing(error)) {
|
|
928
|
+
return [];
|
|
929
|
+
}
|
|
930
|
+
throw error;
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
function isFileMissing(error) {
|
|
934
|
+
return Boolean(error && typeof error === "object" && "code" in error && (error.code === "ENOENT" || error.code === "ENOTDIR"));
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
// ../core/dist/observability/event-formatter.js
|
|
938
|
+
function formatEventMessage(event) {
|
|
939
|
+
switch (event.event) {
|
|
940
|
+
case "run-dispatched":
|
|
941
|
+
return event.issueState ? `Dispatched from ${event.issueState}` : "Dispatched";
|
|
942
|
+
case "run-recovered":
|
|
943
|
+
return "Recovered existing run";
|
|
944
|
+
case "run-retried":
|
|
945
|
+
return `Retry ${event.attempt} scheduled (${event.retryKind})`;
|
|
946
|
+
case "run-failed":
|
|
947
|
+
return event.lastError;
|
|
948
|
+
case "run-suppressed":
|
|
949
|
+
return event.reason;
|
|
950
|
+
case "hook-executed":
|
|
951
|
+
return `${event.hook}: ${event.outcome}`;
|
|
952
|
+
case "hook-failed":
|
|
953
|
+
return event.error;
|
|
954
|
+
case "workspace-cleanup":
|
|
955
|
+
return event.error ? `${event.outcome}: ${event.error}` : event.outcome;
|
|
956
|
+
case "worker-error":
|
|
957
|
+
return event.error;
|
|
958
|
+
case "turn_started":
|
|
959
|
+
return `Turn ${event.turnCount} started`;
|
|
960
|
+
case "turn_completed":
|
|
961
|
+
return `Turn ${event.turnCount} completed in ${event.durationMs}ms`;
|
|
962
|
+
case "turn_failed":
|
|
963
|
+
return event.error ?? `Turn ${event.turnCount} failed`;
|
|
964
|
+
default:
|
|
965
|
+
return null;
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
function parseRecentEvents(raw, limit, options) {
|
|
969
|
+
const lines = raw.split("\n");
|
|
970
|
+
if (options.allowPartialFirstLine) {
|
|
971
|
+
lines.shift();
|
|
972
|
+
}
|
|
973
|
+
const events = [];
|
|
974
|
+
for (let index = lines.length - 1; index >= 0; index -= 1) {
|
|
975
|
+
const line = lines[index]?.trim();
|
|
976
|
+
if (!line) {
|
|
977
|
+
continue;
|
|
978
|
+
}
|
|
979
|
+
const event = parseRunEventLine(line);
|
|
980
|
+
if (!event) {
|
|
981
|
+
continue;
|
|
982
|
+
}
|
|
983
|
+
events.push({
|
|
984
|
+
at: event.at,
|
|
985
|
+
event: event.event,
|
|
986
|
+
message: formatEventMessage(event)
|
|
987
|
+
});
|
|
988
|
+
if (events.length === limit) {
|
|
989
|
+
break;
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
return events.reverse();
|
|
993
|
+
}
|
|
994
|
+
function parseRunEventLine(line) {
|
|
995
|
+
try {
|
|
996
|
+
return JSON.parse(line);
|
|
997
|
+
} catch {
|
|
998
|
+
return null;
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
// ../core/dist/observability/status-assembler.js
|
|
1003
|
+
function isMatchingIssueRun(run, projectId, issueId, issueIdentifier) {
|
|
1004
|
+
return Boolean(run && run.projectId === projectId && (run.issueId === issueId || run.issueIdentifier === issueIdentifier));
|
|
1005
|
+
}
|
|
1006
|
+
function mapIssueOrchestrationStateToStatus(state) {
|
|
1007
|
+
switch (state) {
|
|
1008
|
+
case "claimed":
|
|
1009
|
+
return "starting";
|
|
1010
|
+
case "running":
|
|
1011
|
+
return "running";
|
|
1012
|
+
case "retry_queued":
|
|
1013
|
+
return "retrying";
|
|
1014
|
+
case "released":
|
|
1015
|
+
return "released";
|
|
1016
|
+
case "unclaimed":
|
|
1017
|
+
return "pending";
|
|
1018
|
+
default:
|
|
1019
|
+
return state;
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
// ../core/dist/workflow/loader.js
|
|
1024
|
+
import { createHash as createHash2 } from "crypto";
|
|
1025
|
+
import { access, readFile as readFile2, stat } from "fs/promises";
|
|
1026
|
+
import { constants } from "fs";
|
|
1027
|
+
var WorkflowConfigStore = class {
|
|
1028
|
+
cache = /* @__PURE__ */ new Map();
|
|
1029
|
+
async load(workflowPath, env = process.env) {
|
|
1030
|
+
await access(workflowPath, constants.R_OK);
|
|
1031
|
+
const fileStat = await stat(workflowPath);
|
|
1032
|
+
const fingerprint = `${fileStat.mtimeMs}:${fileStat.size}`;
|
|
1033
|
+
const cached = this.cache.get(workflowPath);
|
|
1034
|
+
if (cached && cached.fingerprint === fingerprint) {
|
|
1035
|
+
return toWorkflowResolution(workflowPath, cached.workflow, {
|
|
1036
|
+
isValid: true,
|
|
1037
|
+
usedLastKnownGood: false,
|
|
1038
|
+
validationError: null
|
|
1039
|
+
});
|
|
1040
|
+
}
|
|
1041
|
+
const markdown = await readFile2(workflowPath, "utf8");
|
|
1042
|
+
try {
|
|
1043
|
+
const workflow = parseWorkflowMarkdown(markdown, env);
|
|
1044
|
+
this.cache.set(workflowPath, {
|
|
1045
|
+
fingerprint,
|
|
1046
|
+
workflow,
|
|
1047
|
+
loadedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1048
|
+
});
|
|
1049
|
+
return toWorkflowResolution(workflowPath, workflow, {
|
|
1050
|
+
isValid: true,
|
|
1051
|
+
usedLastKnownGood: false,
|
|
1052
|
+
validationError: null
|
|
1053
|
+
});
|
|
1054
|
+
} catch (error) {
|
|
1055
|
+
if (cached) {
|
|
1056
|
+
return toWorkflowResolution(workflowPath, cached.workflow, {
|
|
1057
|
+
isValid: false,
|
|
1058
|
+
usedLastKnownGood: true,
|
|
1059
|
+
validationError: error instanceof Error ? error.message : "Invalid workflow definition."
|
|
1060
|
+
});
|
|
1061
|
+
}
|
|
1062
|
+
throw error;
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
};
|
|
1066
|
+
function createDefaultWorkflowResolution() {
|
|
1067
|
+
return createInvalidWorkflowResolution(null, "missing_workflow_file");
|
|
1068
|
+
}
|
|
1069
|
+
function createInvalidWorkflowResolution(workflowPath, validationError) {
|
|
1070
|
+
return toWorkflowResolution(workflowPath, DEFAULT_WORKFLOW_DEFINITION, {
|
|
1071
|
+
isValid: false,
|
|
1072
|
+
usedLastKnownGood: false,
|
|
1073
|
+
validationError
|
|
1074
|
+
});
|
|
1075
|
+
}
|
|
1076
|
+
function toWorkflowResolution(workflowPath, workflow, metadata) {
|
|
1077
|
+
return {
|
|
1078
|
+
workflowPath,
|
|
1079
|
+
workflow,
|
|
1080
|
+
lifecycle: workflow.lifecycle,
|
|
1081
|
+
promptTemplate: workflow.promptTemplate,
|
|
1082
|
+
agentCommand: workflow.agentCommand,
|
|
1083
|
+
hookPath: workflow.hookPath ?? "",
|
|
1084
|
+
isValid: metadata.isValid,
|
|
1085
|
+
usedLastKnownGood: metadata.usedLastKnownGood,
|
|
1086
|
+
validationError: metadata.validationError
|
|
1087
|
+
};
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
// ../core/dist/workspace/safety.js
|
|
1091
|
+
import { resolve as resolve2 } from "path";
|
|
1092
|
+
|
|
1093
|
+
export {
|
|
1094
|
+
isOrchestratorChannelEvent,
|
|
1095
|
+
DEFAULT_WORKFLOW_LIFECYCLE,
|
|
1096
|
+
isStateActive,
|
|
1097
|
+
isStateTerminal,
|
|
1098
|
+
matchesWorkflowState,
|
|
1099
|
+
parseWorkflowMarkdown,
|
|
1100
|
+
WorkflowConfigStore,
|
|
1101
|
+
createDefaultWorkflowResolution,
|
|
1102
|
+
createInvalidWorkflowResolution,
|
|
1103
|
+
buildPromptVariables,
|
|
1104
|
+
renderPrompt,
|
|
1105
|
+
classifySessionExit,
|
|
1106
|
+
scheduleRetryAt,
|
|
1107
|
+
readEnvFile,
|
|
1108
|
+
deriveIssueWorkspaceKey,
|
|
1109
|
+
deriveIssueWorkspaceKeyFromIdentifier,
|
|
1110
|
+
deriveLegacyIssueWorkspaceKey,
|
|
1111
|
+
resolveIssueWorkspaceDirectory,
|
|
1112
|
+
buildHookEnv,
|
|
1113
|
+
executeWorkspaceHook,
|
|
1114
|
+
buildProjectSnapshot,
|
|
1115
|
+
readJsonFile,
|
|
1116
|
+
safeReadDir,
|
|
1117
|
+
isFileMissing,
|
|
1118
|
+
parseRecentEvents,
|
|
1119
|
+
isMatchingIssueRun,
|
|
1120
|
+
mapIssueOrchestrationStateToStatus
|
|
1121
|
+
};
|