ace-swarm 2.0.6 → 2.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. package/README.md +17 -0
  2. package/assets/.agents/skills/landing-review-watcher/SKILL.md +68 -0
  3. package/assets/.agents/skills/problem-triage/SKILL.md +57 -0
  4. package/assets/.agents/skills/problem-triage/agents/openai.yaml +3 -0
  5. package/assets/.agents/skills/skill-auditor/SKILL.md +52 -0
  6. package/assets/.github/hooks/ace-copilot.json +68 -0
  7. package/assets/agent-state/ACE_WORKFLOW.md +66 -0
  8. package/assets/agent-state/INTERFACE_REGISTRY.md +50 -0
  9. package/assets/agent-state/MODULES/gates/gate-typescript-public-surface.json +7 -0
  10. package/assets/agent-state/MODULES/registry.json +10 -2
  11. package/assets/agent-state/MODULES/schemas/ACE_RUNTIME_PROFILE.schema.json +210 -0
  12. package/assets/agent-state/MODULES/schemas/RUNTIME_EXECUTOR_SESSION_REGISTRY.schema.json +290 -0
  13. package/assets/agent-state/MODULES/schemas/RUNTIME_TOOL_SPEC_REGISTRY.schema.json +144 -0
  14. package/assets/agent-state/MODULES/schemas/TRACKER_SNAPSHOT.schema.json +134 -0
  15. package/assets/agent-state/MODULES/schemas/VERICIFY_BRIDGE_SNAPSHOT.schema.json +157 -0
  16. package/assets/agent-state/MODULES/schemas/VERICIFY_PROCESS_POST_LOG.schema.json +92 -0
  17. package/assets/agent-state/MODULES/schemas/WORKSPACE_SESSION_REGISTRY.schema.json +133 -0
  18. package/assets/agent-state/SKILL_CATALOG.md +48 -0
  19. package/assets/agent-state/runtime-executor-sessions.json +5 -0
  20. package/assets/agent-state/runtime-tool-specs.json +5 -0
  21. package/assets/agent-state/runtime-workspaces.json +5 -0
  22. package/assets/agent-state/tracker-snapshot.json +7 -0
  23. package/assets/agent-state/vericify/ace-bridge.json +60 -0
  24. package/assets/agent-state/vericify/process-posts.json +5 -0
  25. package/assets/scripts/copilot-hook-dispatch.mjs +267 -0
  26. package/dist/helpers.d.ts.map +1 -1
  27. package/dist/helpers.js +284 -1
  28. package/dist/helpers.js.map +1 -1
  29. package/dist/problem-triage.d.ts +23 -0
  30. package/dist/problem-triage.d.ts.map +1 -0
  31. package/dist/problem-triage.js +429 -0
  32. package/dist/problem-triage.js.map +1 -0
  33. package/dist/prompts.d.ts.map +1 -1
  34. package/dist/prompts.js +46 -0
  35. package/dist/prompts.js.map +1 -1
  36. package/dist/public-surface.d.ts +30 -0
  37. package/dist/public-surface.d.ts.map +1 -0
  38. package/dist/public-surface.js +310 -0
  39. package/dist/public-surface.js.map +1 -0
  40. package/dist/resources.d.ts.map +1 -1
  41. package/dist/resources.js +148 -0
  42. package/dist/resources.js.map +1 -1
  43. package/dist/runtime-command.d.ts +18 -0
  44. package/dist/runtime-command.d.ts.map +1 -0
  45. package/dist/runtime-command.js +76 -0
  46. package/dist/runtime-command.js.map +1 -0
  47. package/dist/runtime-executor.d.ts +104 -0
  48. package/dist/runtime-executor.d.ts.map +1 -0
  49. package/dist/runtime-executor.js +774 -0
  50. package/dist/runtime-executor.js.map +1 -0
  51. package/dist/runtime-profile.d.ts +98 -0
  52. package/dist/runtime-profile.d.ts.map +1 -0
  53. package/dist/runtime-profile.js +441 -0
  54. package/dist/runtime-profile.js.map +1 -0
  55. package/dist/runtime-tool-specs.d.ts +68 -0
  56. package/dist/runtime-tool-specs.d.ts.map +1 -0
  57. package/dist/runtime-tool-specs.js +424 -0
  58. package/dist/runtime-tool-specs.js.map +1 -0
  59. package/dist/schemas.d.ts +6 -0
  60. package/dist/schemas.d.ts.map +1 -1
  61. package/dist/schemas.js +305 -0
  62. package/dist/schemas.js.map +1 -1
  63. package/dist/shared.d.ts +36 -3
  64. package/dist/shared.d.ts.map +1 -1
  65. package/dist/shared.js +36 -3
  66. package/dist/shared.js.map +1 -1
  67. package/dist/skill-auditor.d.ts +26 -0
  68. package/dist/skill-auditor.d.ts.map +1 -0
  69. package/dist/skill-auditor.js +184 -0
  70. package/dist/skill-auditor.js.map +1 -0
  71. package/dist/skill-catalog.d.ts +60 -0
  72. package/dist/skill-catalog.d.ts.map +1 -0
  73. package/dist/skill-catalog.js +263 -0
  74. package/dist/skill-catalog.js.map +1 -0
  75. package/dist/status-events.d.ts.map +1 -1
  76. package/dist/status-events.js +51 -8
  77. package/dist/status-events.js.map +1 -1
  78. package/dist/tools-agent.d.ts.map +1 -1
  79. package/dist/tools-agent.js +869 -0
  80. package/dist/tools-agent.js.map +1 -1
  81. package/dist/tools-files.d.ts.map +1 -1
  82. package/dist/tools-files.js +212 -1
  83. package/dist/tools-files.js.map +1 -1
  84. package/dist/tools-framework.d.ts.map +1 -1
  85. package/dist/tools-framework.js +86 -0
  86. package/dist/tools-framework.js.map +1 -1
  87. package/dist/tools-skills.d.ts +3 -0
  88. package/dist/tools-skills.d.ts.map +1 -0
  89. package/dist/tools-skills.js +104 -0
  90. package/dist/tools-skills.js.map +1 -0
  91. package/dist/tools.d.ts.map +1 -1
  92. package/dist/tools.js +2 -0
  93. package/dist/tools.js.map +1 -1
  94. package/dist/tracker-adapters.d.ts +74 -0
  95. package/dist/tracker-adapters.d.ts.map +1 -0
  96. package/dist/tracker-adapters.js +777 -0
  97. package/dist/tracker-adapters.js.map +1 -0
  98. package/dist/tracker-sync.d.ts +10 -0
  99. package/dist/tracker-sync.d.ts.map +1 -0
  100. package/dist/tracker-sync.js +84 -0
  101. package/dist/tracker-sync.js.map +1 -0
  102. package/dist/vericify-bridge.d.ts +142 -0
  103. package/dist/vericify-bridge.d.ts.map +1 -0
  104. package/dist/vericify-bridge.js +481 -0
  105. package/dist/vericify-bridge.js.map +1 -0
  106. package/dist/workspace-manager.d.ts +103 -0
  107. package/dist/workspace-manager.d.ts.map +1 -0
  108. package/dist/workspace-manager.js +526 -0
  109. package/dist/workspace-manager.js.map +1 -0
  110. package/package.json +1 -1
@@ -0,0 +1,774 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync, } from "node:fs";
3
+ import { dirname, resolve } from "node:path";
4
+ import { z } from "zod";
5
+ import { resolveWorkspaceRoot, withFileLock, } from "./helpers.js";
6
+ import { appendRunLedgerEntrySafe } from "./run-ledger.js";
7
+ import { runShellCommand } from "./runtime-command.js";
8
+ import { getRuntimeProfilePath, renderRuntimePrompt, readRuntimeProfile, } from "./runtime-profile.js";
9
+ import { executeRuntimeTool, listRuntimeToolSpecs, } from "./runtime-tool-specs.js";
10
+ import { validateRuntimeExecutorSessionRegistryPayload, } from "./schemas.js";
11
+ import { appendStatusEventSafe } from "./status-events.js";
12
+ import { appendVericifyProcessPostSafe, deriveVericifyRunRef, isVericifyBridgeEnabled, refreshVericifyBridgeSnapshotSafe, } from "./vericify-bridge.js";
13
+ import { createWorkspaceSession, removeWorkspaceSession, runWorkspaceSessionHook, } from "./workspace-manager.js";
14
+ export const RUNTIME_EXECUTOR_SESSION_REGISTRY_REL_PATH = "agent-state/runtime-executor-sessions.json";
15
+ export const RUNTIME_EXECUTOR_SESSION_SCHEMA_REL_PATH = "agent-state/MODULES/schemas/RUNTIME_EXECUTOR_SESSION_REGISTRY.schema.json";
16
+ export const RUNTIME_EXECUTOR_SESSION_SCHEMA_NAME = "runtime-executor-session-registry@1.0.0";
17
+ const activeSessions = new Map();
18
+ const turnResponseSchema = z
19
+ .object({
20
+ status: z.enum(["continue", "done", "blocked", "failed"]),
21
+ summary: z.string().min(1),
22
+ next_task: z.string().optional(),
23
+ tool_calls: z
24
+ .array(z
25
+ .object({
26
+ name: z.string().min(1),
27
+ input: z.unknown(),
28
+ })
29
+ .strict())
30
+ .default([]),
31
+ })
32
+ .strict();
33
+ function defaultRegistry() {
34
+ return {
35
+ version: 1,
36
+ updated_at: "1970-01-01T00:00:00.000Z",
37
+ sessions: [],
38
+ };
39
+ }
40
+ function resolveWorkspaceArtifactPath(filePath) {
41
+ return resolve(resolveWorkspaceRoot(), filePath);
42
+ }
43
+ function safeWriteWorkspaceFile(filePath, content) {
44
+ const abs = resolveWorkspaceArtifactPath(filePath);
45
+ mkdirSync(dirname(abs), { recursive: true });
46
+ const tmpPath = `${abs}.${process.pid}.${Date.now()}.tmp`;
47
+ writeFileSync(tmpPath, content, "utf-8");
48
+ renameSync(tmpPath, abs);
49
+ return abs;
50
+ }
51
+ function parseRegistry(raw) {
52
+ let parsed;
53
+ try {
54
+ parsed = JSON.parse(raw);
55
+ }
56
+ catch (error) {
57
+ throw new Error(`Runtime executor session registry contains invalid JSON: ${error instanceof Error ? error.message : String(error)}`);
58
+ }
59
+ const validation = validateRuntimeExecutorSessionRegistryPayload(parsed);
60
+ if (!validation.ok) {
61
+ throw new Error(`Runtime executor session registry failed validation (${validation.schema}): ${validation.errors.join("; ")}`);
62
+ }
63
+ return parsed;
64
+ }
65
+ function readRegistry() {
66
+ const path = resolveWorkspaceArtifactPath(RUNTIME_EXECUTOR_SESSION_REGISTRY_REL_PATH);
67
+ if (!existsSync(path))
68
+ return defaultRegistry();
69
+ return parseRegistry(readFileSync(path, "utf-8"));
70
+ }
71
+ function writeRegistry(registry) {
72
+ const validation = validateRuntimeExecutorSessionRegistryPayload(registry);
73
+ if (!validation.ok) {
74
+ throw new Error(`Runtime executor session registry failed validation (${validation.schema}): ${validation.errors.join("; ")}`);
75
+ }
76
+ return safeWriteWorkspaceFile(RUNTIME_EXECUTOR_SESSION_REGISTRY_REL_PATH, JSON.stringify(registry, null, 2));
77
+ }
78
+ function registryPath() {
79
+ return resolveWorkspaceArtifactPath(RUNTIME_EXECUTOR_SESSION_REGISTRY_REL_PATH);
80
+ }
81
+ async function mutateRegistry(updater) {
82
+ return withFileLock(RUNTIME_EXECUTOR_SESSION_REGISTRY_REL_PATH, async () => {
83
+ const registry = readRegistry();
84
+ const result = await updater(registry);
85
+ registry.updated_at = new Date().toISOString();
86
+ writeRegistry(registry);
87
+ return result;
88
+ });
89
+ }
90
+ function findSession(registry, sessionId) {
91
+ return registry.sessions.find((session) => session.session_id === sessionId);
92
+ }
93
+ function buildTurnPaths(workspacePath, turnNumber) {
94
+ const runtimeDir = resolve(workspacePath, ".ace-runtime", "turns");
95
+ mkdirSync(runtimeDir, { recursive: true });
96
+ const stem = `turn-${String(turnNumber).padStart(4, "0")}`;
97
+ return {
98
+ promptPath: resolve(runtimeDir, `${stem}.prompt.md`),
99
+ requestPath: resolve(runtimeDir, `${stem}.request.json`),
100
+ responsePath: resolve(runtimeDir, `${stem}.response.json`),
101
+ };
102
+ }
103
+ function terminalStatus(status) {
104
+ return status === "completed" || status === "failed" || status === "blocked" || status === "stopped";
105
+ }
106
+ function clipOutput(value) {
107
+ const trimmed = value.trim();
108
+ return trimmed.length > 4000 ? `${trimmed.slice(0, 3997)}...` : trimmed;
109
+ }
110
+ function mapToolCallResult(result, startedAt) {
111
+ return {
112
+ tool_name: result.name,
113
+ ok: result.ok,
114
+ summary: result.summary,
115
+ request_path: result.request_path,
116
+ response_path: result.response_path,
117
+ started_at: startedAt,
118
+ ended_at: new Date().toISOString(),
119
+ output: result.output,
120
+ error: result.error,
121
+ validation_errors: result.validation_errors,
122
+ };
123
+ }
124
+ function vericifyPayloadForSession(session) {
125
+ const refs = deriveVericifyRunRef(session);
126
+ return {
127
+ run_id: refs.run_id,
128
+ branch_id: refs.branch_id,
129
+ lane_id: refs.lane_id,
130
+ process_id: session.session_id,
131
+ objective_id: session.objective_id,
132
+ tracker_item_id: session.tracker_item_id,
133
+ workspace_path: session.workspace_path,
134
+ };
135
+ }
136
+ async function emitVericifyProcessPostForSession(session, kind, summary, toolRefs = [], evidenceRefs = []) {
137
+ if (!isVericifyBridgeEnabled())
138
+ return;
139
+ const refs = deriveVericifyRunRef(session);
140
+ await appendVericifyProcessPostSafe({
141
+ run_id: refs.run_id,
142
+ branch_id: refs.branch_id,
143
+ lane_id: refs.lane_id,
144
+ agent_id: "capability-framework",
145
+ kind,
146
+ summary,
147
+ tool_refs: toolRefs,
148
+ evidence_refs: evidenceRefs,
149
+ }).catch(() => undefined);
150
+ refreshVericifyBridgeSnapshotSafe();
151
+ }
152
+ async function emitExecutorEvent(eventType, status, summary, payload) {
153
+ await appendStatusEventSafe({
154
+ source_module: "capability-framework",
155
+ event_type: eventType,
156
+ status,
157
+ summary,
158
+ payload,
159
+ }).catch(() => undefined);
160
+ }
161
+ async function finalizeSession(sessionId, nextStatus, summary, error) {
162
+ return mutateRegistry((registry) => {
163
+ const session = findSession(registry, sessionId);
164
+ if (!session)
165
+ return undefined;
166
+ session.status = nextStatus;
167
+ session.updated_at = new Date().toISOString();
168
+ session.ended_at = session.updated_at;
169
+ session.result_summary = summary;
170
+ if (error)
171
+ session.last_error = error;
172
+ return { ...session };
173
+ });
174
+ }
175
+ function readTurnResponse(responsePath) {
176
+ if (!existsSync(responsePath)) {
177
+ throw new Error(`Turn response file not found: ${responsePath}`);
178
+ }
179
+ let parsed;
180
+ try {
181
+ parsed = JSON.parse(readFileSync(responsePath, "utf-8"));
182
+ }
183
+ catch (error) {
184
+ throw new Error(`Turn response JSON is invalid: ${error instanceof Error ? error.message : String(error)}`);
185
+ }
186
+ const validation = turnResponseSchema.safeParse(parsed);
187
+ if (!validation.success) {
188
+ throw new Error(`Turn response failed validation: ${validation.error.issues.map((issue) => issue.message).join("; ")}`);
189
+ }
190
+ return validation.data;
191
+ }
192
+ async function runCleanup(sessionId, workspaceSessionId, shouldRunAfterHook) {
193
+ let cleanupError;
194
+ let cleanupStatus = "pending";
195
+ if (shouldRunAfterHook) {
196
+ const afterRun = runWorkspaceSessionHook({
197
+ session_id: workspaceSessionId,
198
+ kind: "after_run",
199
+ });
200
+ if (!afterRun.ok) {
201
+ cleanupError = afterRun.error ?? "after_run hook failed";
202
+ cleanupStatus = "failed";
203
+ }
204
+ }
205
+ const removal = removeWorkspaceSession({ session_id: workspaceSessionId });
206
+ if (removal.ok && removal.session) {
207
+ cleanupStatus =
208
+ removal.session.status === "archived" ? "archived" : "removed";
209
+ }
210
+ else if (!cleanupError) {
211
+ cleanupError = removal.error ?? "Workspace session cleanup failed";
212
+ cleanupStatus = "failed";
213
+ }
214
+ await mutateRegistry((registry) => {
215
+ const session = findSession(registry, sessionId);
216
+ if (!session)
217
+ return;
218
+ session.workspace_cleanup_status = cleanupStatus;
219
+ session.updated_at = new Date().toISOString();
220
+ if (cleanupError)
221
+ session.cleanup_error = cleanupError;
222
+ });
223
+ }
224
+ async function runSessionLoop(sessionId, context, autoCleanup) {
225
+ const active = activeSessions.get(sessionId);
226
+ let shouldRunAfterHook = false;
227
+ let workspaceSessionId = "";
228
+ try {
229
+ const runtimeProfile = readRuntimeProfile();
230
+ const registryStart = await mutateRegistry((registry) => {
231
+ const session = findSession(registry, sessionId);
232
+ if (!session)
233
+ throw new Error(`Session disappeared before execution: ${sessionId}`);
234
+ session.status = "running";
235
+ session.started_at = new Date().toISOString();
236
+ session.updated_at = session.started_at;
237
+ workspaceSessionId = session.workspace_session_id;
238
+ return { ...session };
239
+ });
240
+ if (!registryStart) {
241
+ throw new Error(`Failed to start unattended session: ${sessionId}`);
242
+ }
243
+ const beforeRun = runWorkspaceSessionHook({
244
+ session_id: registryStart.workspace_session_id,
245
+ kind: "before_run",
246
+ });
247
+ if (!beforeRun.ok) {
248
+ const summary = beforeRun.error ?? "before_run hook failed";
249
+ await finalizeSession(sessionId, "failed", summary, summary);
250
+ await emitExecutorEvent("RUNTIME_EXECUTOR_SESSION_FAILED", "fail", summary, {
251
+ session_id: sessionId,
252
+ workspace_session_id: registryStart.workspace_session_id,
253
+ ...vericifyPayloadForSession(registryStart),
254
+ });
255
+ await emitVericifyProcessPostForSession(registryStart, "blocker", summary, [
256
+ "runtime-executor",
257
+ ]);
258
+ return;
259
+ }
260
+ shouldRunAfterHook = true;
261
+ let currentTask = registryStart.current_task;
262
+ let previousToolCalls = [];
263
+ for (let turnNumber = 1; turnNumber <= registryStart.max_turns; turnNumber += 1) {
264
+ if (active?.stop_requested) {
265
+ const stopped = await finalizeSession(sessionId, "stopped", `Unattended session ${sessionId} stopped by request.`);
266
+ if (stopped) {
267
+ await emitExecutorEvent("RUNTIME_EXECUTOR_SESSION_STOPPED", "done", stopped.result_summary ?? `Unattended session ${sessionId} stopped by request.`, {
268
+ session_id: sessionId,
269
+ ...vericifyPayloadForSession(stopped),
270
+ });
271
+ await emitVericifyProcessPostForSession(stopped, "progress", stopped.result_summary ?? `Unattended session ${sessionId} stopped by request.`, ["runtime-executor"]);
272
+ }
273
+ return;
274
+ }
275
+ const sessionSnapshot = await mutateRegistry((registry) => {
276
+ const session = findSession(registry, sessionId);
277
+ if (!session)
278
+ throw new Error(`Missing unattended session ${sessionId}`);
279
+ session.current_task = currentTask;
280
+ session.updated_at = new Date().toISOString();
281
+ return { ...session };
282
+ });
283
+ if (!sessionSnapshot)
284
+ throw new Error(`Missing unattended session ${sessionId}`);
285
+ const turnStartedAt = new Date().toISOString();
286
+ const paths = buildTurnPaths(sessionSnapshot.workspace_path, turnNumber);
287
+ const toolCatalog = listRuntimeToolSpecs().map((tool) => ({
288
+ name: tool.name,
289
+ description: tool.description,
290
+ input_schema: tool.input_schema,
291
+ }));
292
+ const prompt = renderRuntimePrompt({
293
+ task: currentTask,
294
+ session_id: sessionId,
295
+ turn_number: turnNumber,
296
+ workspace_path: sessionSnapshot.workspace_path,
297
+ runtime_tools: JSON.stringify(toolCatalog, null, 2),
298
+ });
299
+ writeFileSync(paths.promptPath, prompt, "utf-8");
300
+ const requestPayload = {
301
+ session_id: sessionId,
302
+ turn_number: turnNumber,
303
+ task: currentTask,
304
+ prompt,
305
+ context: context ?? {},
306
+ prior_turns: sessionSnapshot.turns.map((turn) => ({
307
+ turn_number: turn.turn_number,
308
+ status: turn.status,
309
+ summary: turn.summary,
310
+ response_status: turn.response_status,
311
+ })),
312
+ previous_tool_calls: previousToolCalls.map((tool) => ({
313
+ tool_name: tool.tool_name,
314
+ ok: tool.ok,
315
+ summary: tool.summary,
316
+ output: tool.output,
317
+ error: tool.error,
318
+ })),
319
+ available_tools: toolCatalog,
320
+ };
321
+ writeFileSync(paths.requestPath, JSON.stringify(requestPayload, null, 2), "utf-8");
322
+ await emitExecutorEvent("RUNTIME_EXECUTOR_TURN_STARTED", "in_progress", `Unattended session ${sessionId} turn ${turnNumber} started.`, {
323
+ session_id: sessionId,
324
+ turn_number: turnNumber,
325
+ workspace_path: sessionSnapshot.workspace_path,
326
+ ...vericifyPayloadForSession(sessionSnapshot),
327
+ });
328
+ const shellResult = await runShellCommand(runtimeProfile.executor.command ?? "", {
329
+ cwd: sessionSnapshot.workspace_path,
330
+ env: {
331
+ ...process.env,
332
+ ACE_RUNTIME_SESSION_ID: sessionId,
333
+ ACE_RUNTIME_TURN: String(turnNumber),
334
+ ACE_RUNTIME_WORKSPACE_PATH: sessionSnapshot.workspace_path,
335
+ ACE_RUNTIME_PROMPT_FILE: paths.promptPath,
336
+ ACE_RUNTIME_REQUEST_FILE: paths.requestPath,
337
+ ACE_RUNTIME_RESPONSE_FILE: paths.responsePath,
338
+ },
339
+ timeout_ms: sessionSnapshot.turn_timeout_ms,
340
+ on_spawn: (child) => {
341
+ if (active)
342
+ active.child = child;
343
+ },
344
+ });
345
+ if (active)
346
+ active.child = undefined;
347
+ if (active?.stop_requested) {
348
+ const turnEndedAt = new Date().toISOString();
349
+ await mutateRegistry((registry) => {
350
+ const session = findSession(registry, sessionId);
351
+ if (!session)
352
+ return;
353
+ const turnRecord = {
354
+ turn_number: turnNumber,
355
+ task: currentTask,
356
+ started_at: turnStartedAt,
357
+ ended_at: turnEndedAt,
358
+ status: "stopped",
359
+ response_status: "failed",
360
+ summary: `Turn ${turnNumber} stopped by request.`,
361
+ prompt_path: paths.promptPath,
362
+ request_path: paths.requestPath,
363
+ response_path: paths.responsePath,
364
+ exit_code: shellResult.exit_code,
365
+ stdout: clipOutput(shellResult.stdout),
366
+ stderr: clipOutput(shellResult.stderr),
367
+ tool_calls: [],
368
+ };
369
+ session.turns.push(turnRecord);
370
+ session.turn_count = session.turns.length;
371
+ });
372
+ const stopped = await finalizeSession(sessionId, "stopped", `Unattended session ${sessionId} stopped by request.`);
373
+ if (stopped) {
374
+ await emitExecutorEvent("RUNTIME_EXECUTOR_SESSION_STOPPED", "done", stopped.result_summary ?? `Unattended session ${sessionId} stopped by request.`, {
375
+ session_id: sessionId,
376
+ ...vericifyPayloadForSession(stopped),
377
+ });
378
+ await emitVericifyProcessPostForSession(stopped, "progress", stopped.result_summary ?? `Unattended session ${sessionId} stopped by request.`, ["runtime-executor"]);
379
+ }
380
+ return;
381
+ }
382
+ let response;
383
+ try {
384
+ response = readTurnResponse(paths.responsePath);
385
+ }
386
+ catch (error) {
387
+ const message = error instanceof Error ? error.message : String(error);
388
+ const turnEndedAt = new Date().toISOString();
389
+ await mutateRegistry((registry) => {
390
+ const session = findSession(registry, sessionId);
391
+ if (!session)
392
+ return;
393
+ const turnRecord = {
394
+ turn_number: turnNumber,
395
+ task: currentTask,
396
+ started_at: turnStartedAt,
397
+ ended_at: turnEndedAt,
398
+ status: "failed",
399
+ response_status: "failed",
400
+ summary: message,
401
+ prompt_path: paths.promptPath,
402
+ request_path: paths.requestPath,
403
+ response_path: paths.responsePath,
404
+ exit_code: shellResult.exit_code,
405
+ stdout: clipOutput(shellResult.stdout),
406
+ stderr: clipOutput(shellResult.stderr),
407
+ tool_calls: [],
408
+ };
409
+ session.turns.push(turnRecord);
410
+ session.turn_count = session.turns.length;
411
+ });
412
+ const failed = await finalizeSession(sessionId, "failed", message, message);
413
+ if (failed) {
414
+ await emitExecutorEvent("RUNTIME_EXECUTOR_SESSION_FAILED", "fail", message, {
415
+ session_id: sessionId,
416
+ turn_number: turnNumber,
417
+ ...vericifyPayloadForSession(failed),
418
+ });
419
+ await emitVericifyProcessPostForSession(failed, "blocker", message, [
420
+ "runtime-executor",
421
+ ]);
422
+ }
423
+ return;
424
+ }
425
+ const toolCalls = [];
426
+ for (const toolCall of response.tool_calls) {
427
+ const toolStartedAt = new Date().toISOString();
428
+ const toolResult = await executeRuntimeTool(toolCall.name, toolCall.input, {
429
+ session_id: sessionId,
430
+ workspace_path: sessionSnapshot.workspace_path,
431
+ turn_number: turnNumber,
432
+ });
433
+ toolCalls.push(mapToolCallResult(toolResult, toolStartedAt));
434
+ }
435
+ previousToolCalls = toolCalls;
436
+ const turnEndedAt = new Date().toISOString();
437
+ const turnStatus = response.status === "blocked"
438
+ ? "blocked"
439
+ : response.status === "failed"
440
+ ? "failed"
441
+ : "completed";
442
+ await mutateRegistry((registry) => {
443
+ const session = findSession(registry, sessionId);
444
+ if (!session)
445
+ return;
446
+ const turnRecord = {
447
+ turn_number: turnNumber,
448
+ task: currentTask,
449
+ started_at: turnStartedAt,
450
+ ended_at: turnEndedAt,
451
+ status: turnStatus,
452
+ response_status: response.status,
453
+ summary: response.summary,
454
+ prompt_path: paths.promptPath,
455
+ request_path: paths.requestPath,
456
+ response_path: paths.responsePath,
457
+ exit_code: shellResult.exit_code,
458
+ stdout: clipOutput(shellResult.stdout),
459
+ stderr: clipOutput(shellResult.stderr),
460
+ tool_calls: toolCalls,
461
+ };
462
+ session.turns.push(turnRecord);
463
+ session.turn_count = session.turns.length;
464
+ session.current_task = response.next_task?.trim() || currentTask;
465
+ session.updated_at = turnEndedAt;
466
+ });
467
+ await emitExecutorEvent(response.status === "failed"
468
+ ? "RUNTIME_EXECUTOR_TURN_FAILED"
469
+ : "RUNTIME_EXECUTOR_TURN_COMPLETED", response.status === "blocked" ? "blocked" : response.status === "failed" ? "fail" : "done", response.summary, {
470
+ session_id: sessionId,
471
+ turn_number: turnNumber,
472
+ response_status: response.status,
473
+ tool_call_count: toolCalls.length,
474
+ ...vericifyPayloadForSession(sessionSnapshot),
475
+ });
476
+ if (response.status === "continue") {
477
+ currentTask = response.next_task?.trim() || currentTask;
478
+ continue;
479
+ }
480
+ if (response.status === "blocked") {
481
+ await finalizeSession(sessionId, "blocked", response.summary, response.summary);
482
+ await emitExecutorEvent("RUNTIME_EXECUTOR_SESSION_BLOCKED", "blocked", response.summary, {
483
+ session_id: sessionId,
484
+ turn_number: turnNumber,
485
+ ...vericifyPayloadForSession(sessionSnapshot),
486
+ });
487
+ await emitVericifyProcessPostForSession(sessionSnapshot, "blocker", response.summary, [
488
+ "runtime-executor",
489
+ ]);
490
+ return;
491
+ }
492
+ if (response.status === "failed") {
493
+ const failed = await finalizeSession(sessionId, "failed", response.summary, response.summary);
494
+ await emitExecutorEvent("RUNTIME_EXECUTOR_SESSION_FAILED", "fail", response.summary, {
495
+ session_id: sessionId,
496
+ turn_number: turnNumber,
497
+ ...(failed ? vericifyPayloadForSession(failed) : vericifyPayloadForSession(sessionSnapshot)),
498
+ });
499
+ await emitVericifyProcessPostForSession(failed ?? sessionSnapshot, "blocker", response.summary, ["runtime-executor"]);
500
+ return;
501
+ }
502
+ const completed = await finalizeSession(sessionId, "completed", response.summary);
503
+ if (completed) {
504
+ await appendRunLedgerEntrySafe({
505
+ tool: "runtime-executor",
506
+ category: "major_update",
507
+ message: response.summary,
508
+ artifacts: [
509
+ RUNTIME_EXECUTOR_SESSION_REGISTRY_REL_PATH,
510
+ paths.promptPath,
511
+ paths.requestPath,
512
+ paths.responsePath,
513
+ ],
514
+ metadata: {
515
+ session_id: sessionId,
516
+ turn_number: turnNumber,
517
+ workspace_session_id: completed.workspace_session_id,
518
+ ...vericifyPayloadForSession(completed),
519
+ },
520
+ }).catch(() => undefined);
521
+ await emitExecutorEvent("RUNTIME_EXECUTOR_SESSION_COMPLETED", "done", response.summary, {
522
+ session_id: sessionId,
523
+ turn_number: turnNumber,
524
+ workspace_session_id: completed.workspace_session_id,
525
+ ...vericifyPayloadForSession(completed),
526
+ });
527
+ await emitVericifyProcessPostForSession(completed, "completion", response.summary, [
528
+ "runtime-executor",
529
+ ]);
530
+ }
531
+ return;
532
+ }
533
+ const exhausted = `Unattended session ${sessionId} exceeded max_turns=${registryStart.max_turns}`;
534
+ const failed = await finalizeSession(sessionId, "failed", exhausted, exhausted);
535
+ if (failed) {
536
+ await appendRunLedgerEntrySafe({
537
+ tool: "runtime-executor",
538
+ category: "regression",
539
+ message: exhausted,
540
+ artifacts: [RUNTIME_EXECUTOR_SESSION_REGISTRY_REL_PATH],
541
+ metadata: {
542
+ session_id: sessionId,
543
+ workspace_session_id: failed.workspace_session_id,
544
+ ...vericifyPayloadForSession(failed),
545
+ },
546
+ }).catch(() => undefined);
547
+ await emitExecutorEvent("RUNTIME_EXECUTOR_SESSION_FAILED", "fail", exhausted, {
548
+ session_id: sessionId,
549
+ reason: "max_turns_exceeded",
550
+ ...vericifyPayloadForSession(failed),
551
+ });
552
+ await emitVericifyProcessPostForSession(failed, "blocker", exhausted, [
553
+ "runtime-executor",
554
+ ]);
555
+ }
556
+ }
557
+ catch (error) {
558
+ const message = error instanceof Error ? error.message : String(error);
559
+ const failed = await finalizeSession(sessionId, "failed", message, message);
560
+ await appendRunLedgerEntrySafe({
561
+ tool: "runtime-executor",
562
+ category: "regression",
563
+ message,
564
+ artifacts: [RUNTIME_EXECUTOR_SESSION_REGISTRY_REL_PATH],
565
+ metadata: {
566
+ session_id: sessionId,
567
+ ...(failed ? vericifyPayloadForSession(failed) : {}),
568
+ },
569
+ }).catch(() => undefined);
570
+ await emitExecutorEvent("RUNTIME_EXECUTOR_SESSION_FAILED", "fail", message, {
571
+ session_id: sessionId,
572
+ workspace_session_id: failed?.workspace_session_id,
573
+ ...(failed ? vericifyPayloadForSession(failed) : {}),
574
+ });
575
+ if (failed) {
576
+ await emitVericifyProcessPostForSession(failed, "blocker", message, [
577
+ "runtime-executor",
578
+ ]);
579
+ }
580
+ }
581
+ finally {
582
+ if (workspaceSessionId && autoCleanup) {
583
+ await runCleanup(sessionId, workspaceSessionId, shouldRunAfterHook).catch(() => undefined);
584
+ }
585
+ const active = activeSessions.get(sessionId);
586
+ if (active) {
587
+ active.child = undefined;
588
+ activeSessions.delete(sessionId);
589
+ }
590
+ }
591
+ }
592
+ export function validateRuntimeExecutorSessionRegistryContent(raw) {
593
+ try {
594
+ parseRegistry(raw);
595
+ return { ok: true, schema: RUNTIME_EXECUTOR_SESSION_SCHEMA_NAME };
596
+ }
597
+ catch (error) {
598
+ return {
599
+ ok: false,
600
+ schema: RUNTIME_EXECUTOR_SESSION_SCHEMA_NAME,
601
+ errors: [error instanceof Error ? error.message : String(error)],
602
+ };
603
+ }
604
+ }
605
+ export function listUnattendedSessions() {
606
+ return readRegistry();
607
+ }
608
+ export function getUnattendedSession(sessionId) {
609
+ return findSession(readRegistry(), sessionId);
610
+ }
611
+ export function getRuntimeExecutorSessionRegistryPath() {
612
+ return registryPath();
613
+ }
614
+ export function startUnattendedSession(input) {
615
+ const registryPathValue = registryPath();
616
+ const runtimeProfile = readRuntimeProfile();
617
+ if (runtimeProfile.runtime.mode !== "unattended") {
618
+ return Promise.resolve({
619
+ ok: false,
620
+ registry_path: registryPathValue,
621
+ error: "ACE_WORKFLOW.md runtime.mode must be \"unattended\" before starting an unattended session.",
622
+ });
623
+ }
624
+ if (!runtimeProfile.executor.command || runtimeProfile.executor.command.trim().length === 0) {
625
+ return Promise.resolve({
626
+ ok: false,
627
+ registry_path: registryPathValue,
628
+ error: "ACE_WORKFLOW.md executor.command must be configured before starting an unattended session.",
629
+ });
630
+ }
631
+ const sessionId = input.session_id?.trim() || randomUUID();
632
+ const duplicate = findSession(readRegistry(), sessionId);
633
+ if (duplicate) {
634
+ return Promise.resolve({
635
+ ok: false,
636
+ registry_path: registryPathValue,
637
+ error: `Unattended session id already exists: ${sessionId}`,
638
+ });
639
+ }
640
+ const workspace = createWorkspaceSession({
641
+ source: "executor",
642
+ workspace_name: input.workspace_name,
643
+ workspace_path: input.workspace_path,
644
+ objective_id: input.objective_id,
645
+ tracker_item_id: input.tracker_item_id,
646
+ });
647
+ if (!workspace.ok || !workspace.session) {
648
+ return Promise.resolve({
649
+ ok: false,
650
+ registry_path: registryPathValue,
651
+ error: workspace.error ?? "Failed to create managed workspace session.",
652
+ });
653
+ }
654
+ const maxTurns = Math.max(1, input.max_turns ?? runtimeProfile.executor.max_turns ?? 6);
655
+ const turnTimeout = Math.max(1_000, input.turn_timeout_ms ?? runtimeProfile.executor.turn_timeout_ms ?? 300_000);
656
+ const now = new Date().toISOString();
657
+ const sessionRecord = {
658
+ session_id: sessionId,
659
+ status: "starting",
660
+ task: input.task,
661
+ current_task: input.task,
662
+ runtime_profile_path: getRuntimeProfilePath(),
663
+ workspace_session_id: workspace.session.session_id,
664
+ workspace_path: workspace.session.workspace_path,
665
+ objective_id: input.objective_id,
666
+ tracker_item_id: input.tracker_item_id,
667
+ command: runtimeProfile.executor.command,
668
+ max_turns: maxTurns,
669
+ turn_timeout_ms: turnTimeout,
670
+ turn_count: 0,
671
+ created_at: now,
672
+ updated_at: now,
673
+ workspace_cleanup_status: input.auto_cleanup === false ? "pending" : "pending",
674
+ turns: [],
675
+ };
676
+ return mutateRegistry((registry) => {
677
+ registry.sessions.push(sessionRecord);
678
+ }).then(() => {
679
+ const active = {
680
+ stop_requested: false,
681
+ };
682
+ activeSessions.set(sessionId, active);
683
+ active.completion = runSessionLoop(sessionId, input.context, input.auto_cleanup !== false).finally(() => {
684
+ activeSessions.delete(sessionId);
685
+ });
686
+ void emitExecutorEvent("RUNTIME_EXECUTOR_SESSION_STARTED", "started", `Unattended session ${sessionId} created.`, {
687
+ session_id: sessionId,
688
+ workspace_session_id: workspace.session?.session_id,
689
+ workspace_path: workspace.session?.workspace_path,
690
+ max_turns: maxTurns,
691
+ ...vericifyPayloadForSession(sessionRecord),
692
+ });
693
+ void emitVericifyProcessPostForSession(sessionRecord, "intent", `Unattended session ${sessionId} created for task: ${input.task}`, ["runtime-executor"]);
694
+ if (isVericifyBridgeEnabled()) {
695
+ refreshVericifyBridgeSnapshotSafe();
696
+ }
697
+ return {
698
+ ok: true,
699
+ registry_path: registryPathValue,
700
+ session: sessionRecord,
701
+ workspace: workspace.session,
702
+ };
703
+ });
704
+ }
705
+ export async function waitForUnattendedSession(sessionId, timeoutMs = 30_000) {
706
+ const registryPathValue = registryPath();
707
+ const active = activeSessions.get(sessionId);
708
+ if (!active?.completion) {
709
+ const session = getUnattendedSession(sessionId);
710
+ if (!session) {
711
+ return {
712
+ ok: false,
713
+ timed_out: false,
714
+ registry_path: registryPathValue,
715
+ error: `Unknown unattended session: ${sessionId}`,
716
+ };
717
+ }
718
+ return {
719
+ ok: terminalStatus(session.status),
720
+ timed_out: false,
721
+ registry_path: registryPathValue,
722
+ session,
723
+ error: terminalStatus(session.status)
724
+ ? undefined
725
+ : `Session ${sessionId} is not active but has not reached a terminal state.`,
726
+ };
727
+ }
728
+ const timeoutPromise = new Promise((resolve) => {
729
+ setTimeout(() => resolve("timeout"), timeoutMs);
730
+ });
731
+ const completion = active.completion.then(() => "done");
732
+ const outcome = await Promise.race([completion, timeoutPromise]);
733
+ const session = getUnattendedSession(sessionId);
734
+ if (outcome === "timeout") {
735
+ return {
736
+ ok: false,
737
+ timed_out: true,
738
+ registry_path: registryPathValue,
739
+ session,
740
+ error: `Timed out waiting for unattended session ${sessionId}`,
741
+ };
742
+ }
743
+ return {
744
+ ok: !!session && terminalStatus(session.status),
745
+ timed_out: false,
746
+ registry_path: registryPathValue,
747
+ session,
748
+ };
749
+ }
750
+ export async function stopUnattendedSession(sessionId) {
751
+ const registryPathValue = registryPath();
752
+ const active = activeSessions.get(sessionId);
753
+ if (!active) {
754
+ const session = getUnattendedSession(sessionId);
755
+ return {
756
+ ok: false,
757
+ registry_path: registryPathValue,
758
+ session,
759
+ error: session
760
+ ? `Session ${sessionId} is not currently active.`
761
+ : `Unknown unattended session: ${sessionId}`,
762
+ };
763
+ }
764
+ active.stop_requested = true;
765
+ active.child?.kill("SIGTERM");
766
+ const waited = await waitForUnattendedSession(sessionId, 15_000);
767
+ return {
768
+ ok: waited.ok,
769
+ registry_path: registryPathValue,
770
+ session: waited.session,
771
+ error: waited.ok ? undefined : waited.error,
772
+ };
773
+ }
774
+ //# sourceMappingURL=runtime-executor.js.map