astrocode-workflow 0.3.0 → 0.4.0
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/tools/workflow.js +90 -105
- package/dist/ui/inject.d.ts +6 -0
- package/dist/ui/inject.js +70 -63
- package/dist/workflow/state-machine.d.ts +32 -32
- package/dist/workflow/state-machine.js +85 -170
- package/package.json +1 -1
- package/src/tools/workflow.ts +108 -120
- package/src/ui/inject.ts +96 -74
- package/src/workflow/state-machine.ts +123 -227
- package/src/tools/workflow.ts.backup +0 -681
package/dist/tools/workflow.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
// src/tools/workflow.ts
|
|
2
2
|
import { tool } from "@opencode-ai/plugin/tool";
|
|
3
|
+
import { withTx } from "../state/db";
|
|
3
4
|
import { buildContextSnapshot } from "../workflow/context";
|
|
4
|
-
import { decideNextAction, createRunForStory, startStage, completeRun, getActiveRun, EVENT_TYPES } from "../workflow/state-machine";
|
|
5
|
+
import { decideNextAction, createRunForStory, startStage, completeRun, failRun, getActiveRun, EVENT_TYPES, } from "../workflow/state-machine";
|
|
5
6
|
import { buildStageDirective, directiveHash } from "../workflow/directives";
|
|
6
7
|
import { injectChatPrompt } from "../ui/inject";
|
|
7
8
|
import { nowISO } from "../shared/time";
|
|
@@ -104,6 +105,46 @@ function buildDelegationPrompt(opts) {
|
|
|
104
105
|
debug(`Delegating stage ${stage_key} to agent ${stage_agent_name}`, { prompt_length: prompt.length });
|
|
105
106
|
return prompt;
|
|
106
107
|
}
|
|
108
|
+
function buildUiMessage(e) {
|
|
109
|
+
switch (e.kind) {
|
|
110
|
+
case "stage_started": {
|
|
111
|
+
const agent = e.agent_name ? ` (${e.agent_name})` : "";
|
|
112
|
+
const title = "Astrocode";
|
|
113
|
+
const message = `Stage started: ${e.stage_key}${agent}`;
|
|
114
|
+
const chatText = [
|
|
115
|
+
`[SYSTEM DIRECTIVE: ASTROCODE — STAGE_STARTED]`,
|
|
116
|
+
``,
|
|
117
|
+
`Run: ${e.run_id}`,
|
|
118
|
+
`Stage: ${e.stage_key}${agent}`,
|
|
119
|
+
].join("\n");
|
|
120
|
+
return { title, message, variant: "info", chatText };
|
|
121
|
+
}
|
|
122
|
+
case "run_completed": {
|
|
123
|
+
const title = "Astrocode";
|
|
124
|
+
const message = `Run completed: ${e.run_id}`;
|
|
125
|
+
const chatText = [
|
|
126
|
+
`[SYSTEM DIRECTIVE: ASTROCODE — RUN_COMPLETED]`,
|
|
127
|
+
``,
|
|
128
|
+
`Run: ${e.run_id}`,
|
|
129
|
+
`Story: ${e.story_key}`,
|
|
130
|
+
].join("\n");
|
|
131
|
+
return { title, message, variant: "success", chatText };
|
|
132
|
+
}
|
|
133
|
+
case "run_failed": {
|
|
134
|
+
const title = "Astrocode";
|
|
135
|
+
const message = `Run failed: ${e.run_id} (${e.stage_key})`;
|
|
136
|
+
const chatText = [
|
|
137
|
+
`[SYSTEM DIRECTIVE: ASTROCODE — RUN_FAILED]`,
|
|
138
|
+
``,
|
|
139
|
+
`Run: ${e.run_id}`,
|
|
140
|
+
`Story: ${e.story_key}`,
|
|
141
|
+
`Stage: ${e.stage_key}`,
|
|
142
|
+
`Error: ${e.error_text}`,
|
|
143
|
+
].join("\n");
|
|
144
|
+
return { title, message, variant: "error", chatText };
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
107
148
|
export function createAstroWorkflowProceedTool(opts) {
|
|
108
149
|
const { ctx, config, db, agents } = opts;
|
|
109
150
|
const toasts = createToastManager({ ctx, throttleMs: config.ui.toasts.throttle_ms });
|
|
@@ -119,6 +160,9 @@ export function createAstroWorkflowProceedTool(opts) {
|
|
|
119
160
|
const actions = [];
|
|
120
161
|
const warnings = [];
|
|
121
162
|
const startedAt = nowISO();
|
|
163
|
+
// Collect UI events emitted inside state-machine functions, then flush AFTER tx.
|
|
164
|
+
const uiEvents = [];
|
|
165
|
+
const emit = (e) => uiEvents.push(e);
|
|
122
166
|
for (let i = 0; i < steps; i++) {
|
|
123
167
|
const next = decideNextAction(db, config);
|
|
124
168
|
if (next.kind === "idle") {
|
|
@@ -126,55 +170,24 @@ export function createAstroWorkflowProceedTool(opts) {
|
|
|
126
170
|
break;
|
|
127
171
|
}
|
|
128
172
|
if (next.kind === "start_run") {
|
|
129
|
-
//
|
|
130
|
-
const { run_id } = createRunForStory(db, config, next.story_key);
|
|
173
|
+
// SINGLE tx boundary: caller owns tx, state-machine is pure.
|
|
174
|
+
const { run_id } = withTx(db, () => createRunForStory(db, config, next.story_key));
|
|
131
175
|
actions.push(`started run ${run_id} for story ${next.story_key}`);
|
|
132
|
-
if (config.ui.toasts.enabled && config.ui.toasts.show_run_started) {
|
|
133
|
-
await toasts.show({ title: "Astrocode", message: `Run started (${run_id})`, variant: "success" });
|
|
134
|
-
}
|
|
135
|
-
if (sessionId) {
|
|
136
|
-
await injectChatPrompt({
|
|
137
|
-
ctx,
|
|
138
|
-
sessionId,
|
|
139
|
-
agent: "Astro",
|
|
140
|
-
text: [
|
|
141
|
-
`[SYSTEM DIRECTIVE: ASTROCODE — RUN_STARTED]`,
|
|
142
|
-
``,
|
|
143
|
-
`Run started: \`${run_id}\``,
|
|
144
|
-
`Story: \`${next.story_key}\``,
|
|
145
|
-
``,
|
|
146
|
-
`Next: call **astro_workflow_proceed** again to delegate the first stage.`,
|
|
147
|
-
].join("\n"),
|
|
148
|
-
});
|
|
149
|
-
actions.push(`injected run started message for ${run_id}`);
|
|
150
|
-
}
|
|
151
176
|
if (mode === "step")
|
|
152
177
|
break;
|
|
153
178
|
continue;
|
|
154
179
|
}
|
|
155
180
|
if (next.kind === "complete_run") {
|
|
156
|
-
|
|
157
|
-
completeRun(db, next.run_id);
|
|
181
|
+
withTx(db, () => completeRun(db, next.run_id, emit));
|
|
158
182
|
actions.push(`completed run ${next.run_id}`);
|
|
159
|
-
if (
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
agent: "Astro",
|
|
168
|
-
text: [
|
|
169
|
-
`[SYSTEM DIRECTIVE: ASTROCODE — RUN_COMPLETED]`,
|
|
170
|
-
``,
|
|
171
|
-
`Run \`${next.run_id}\` completed.`,
|
|
172
|
-
``,
|
|
173
|
-
`Next: call **astro_workflow_proceed** (mode=step) to start the next approved story (if any).`,
|
|
174
|
-
].join("\n"),
|
|
175
|
-
});
|
|
176
|
-
actions.push(`injected run completed message for ${next.run_id}`);
|
|
177
|
-
}
|
|
183
|
+
if (mode === "step")
|
|
184
|
+
break;
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
if (next.kind === "failed") {
|
|
188
|
+
// Ensure DB state reflects failure in one tx; emit UI event.
|
|
189
|
+
withTx(db, () => failRun(db, next.run_id, next.stage_key, next.error_text, emit));
|
|
190
|
+
actions.push(`failed: ${next.stage_key} — ${next.error_text}`);
|
|
178
191
|
if (mode === "step")
|
|
179
192
|
break;
|
|
180
193
|
continue;
|
|
@@ -207,27 +220,9 @@ export function createAstroWorkflowProceedTool(opts) {
|
|
|
207
220
|
}
|
|
208
221
|
}
|
|
209
222
|
// NOTE: startStage owns its own tx (state-machine.ts).
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
await toasts.show({ title: "Astrocode", message: `Stage started: ${next.stage_key}`, variant: "info" });
|
|
214
|
-
}
|
|
215
|
-
// ✅ explicit injection on startStage (requested)
|
|
216
|
-
if (sessionId) {
|
|
217
|
-
await injectChatPrompt({
|
|
218
|
-
ctx,
|
|
219
|
-
sessionId,
|
|
220
|
-
agent: "Astro",
|
|
221
|
-
text: [
|
|
222
|
-
`[SYSTEM DIRECTIVE: ASTROCODE — STAGE_STARTED]`,
|
|
223
|
-
``,
|
|
224
|
-
`Run: \`${active.run_id}\``,
|
|
225
|
-
`Stage: \`${next.stage_key}\``,
|
|
226
|
-
`Delegated to: \`${agentName}\``,
|
|
227
|
-
].join("\n"),
|
|
228
|
-
});
|
|
229
|
-
actions.push(`injected stage started message for ${next.stage_key}`);
|
|
230
|
-
}
|
|
223
|
+
withTx(db, () => {
|
|
224
|
+
startStage(db, active.run_id, next.stage_key, { subagent_type: agentName }, emit);
|
|
225
|
+
});
|
|
231
226
|
const context = buildContextSnapshot({
|
|
232
227
|
db,
|
|
233
228
|
config,
|
|
@@ -251,18 +246,14 @@ export function createAstroWorkflowProceedTool(opts) {
|
|
|
251
246
|
stage_key: next.stage_key,
|
|
252
247
|
stage_agent_name: agentName,
|
|
253
248
|
});
|
|
254
|
-
//
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
catch (e) {
|
|
263
|
-
warnings.push(`continuations insert failed (non-fatal): ${String(e)}`);
|
|
249
|
+
// Record continuation (best-effort; no tx wrapper needed but safe either way)
|
|
250
|
+
const h = directiveHash(delegatePrompt);
|
|
251
|
+
const now = nowISO();
|
|
252
|
+
if (sessionId) {
|
|
253
|
+
// This assumes continuations table exists in vNext schema.
|
|
254
|
+
db.prepare("INSERT INTO continuations (session_id, run_id, directive_hash, kind, reason, created_at) VALUES (?, ?, ?, 'stage', ?, ?)").run(sessionId, active.run_id, h, `delegate ${next.stage_key}`, now);
|
|
264
255
|
}
|
|
265
|
-
// Visible injection so user can see state
|
|
256
|
+
// Visible injection so user can see state (awaited)
|
|
266
257
|
if (sessionId) {
|
|
267
258
|
await injectChatPrompt({ ctx, sessionId, text: delegatePrompt, agent: "Astro" });
|
|
268
259
|
const continueMessage = [
|
|
@@ -273,7 +264,7 @@ export function createAstroWorkflowProceedTool(opts) {
|
|
|
273
264
|
`When \`${agentName}\` completes, call:`,
|
|
274
265
|
`astro_stage_complete(run_id="${active.run_id}", stage_key="${next.stage_key}", output_text="[paste subagent output here]")`,
|
|
275
266
|
``,
|
|
276
|
-
`
|
|
267
|
+
`This advances the workflow.`,
|
|
277
268
|
].join("\n");
|
|
278
269
|
await injectChatPrompt({ ctx, sessionId, text: continueMessage, agent: "Astro" });
|
|
279
270
|
}
|
|
@@ -299,46 +290,40 @@ export function createAstroWorkflowProceedTool(opts) {
|
|
|
299
290
|
`Context snapshot:`,
|
|
300
291
|
context,
|
|
301
292
|
].join("\n").trim();
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
db.prepare("INSERT INTO continuations (session_id, run_id, directive_hash, kind, reason, created_at) VALUES (?, ?, ?, 'continue', ?, ?)").run(sessionId, next.run_id, h, `await ${next.stage_key}`, now);
|
|
306
|
-
}
|
|
307
|
-
catch (e) {
|
|
308
|
-
warnings.push(`continuations insert failed (non-fatal): ${String(e)}`);
|
|
309
|
-
}
|
|
293
|
+
const h = directiveHash(prompt);
|
|
294
|
+
const now = nowISO();
|
|
295
|
+
db.prepare("INSERT INTO continuations (session_id, run_id, directive_hash, kind, reason, created_at) VALUES (?, ?, ?, 'continue', ?, ?)").run(sessionId, next.run_id, h, `await ${next.stage_key}`, now);
|
|
310
296
|
await injectChatPrompt({ ctx, sessionId, text: prompt, agent: "Astro" });
|
|
311
297
|
}
|
|
312
298
|
break;
|
|
313
299
|
}
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
300
|
+
actions.push(`unhandled next action: ${next.kind}`);
|
|
301
|
+
break;
|
|
302
|
+
}
|
|
303
|
+
// Flush UI events (toast + prompt) AFTER state transitions
|
|
304
|
+
if (uiEvents.length > 0) {
|
|
305
|
+
for (const e of uiEvents) {
|
|
306
|
+
const msg = buildUiMessage(e);
|
|
307
|
+
if (config.ui.toasts.enabled) {
|
|
308
|
+
await toasts.show({
|
|
309
|
+
title: msg.title,
|
|
310
|
+
message: msg.message,
|
|
311
|
+
variant: msg.variant,
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
if (ctx?.sessionID) {
|
|
317
315
|
await injectChatPrompt({
|
|
318
316
|
ctx,
|
|
319
|
-
sessionId,
|
|
317
|
+
sessionId: ctx.sessionID,
|
|
318
|
+
text: msg.chatText,
|
|
320
319
|
agent: "Astro",
|
|
321
|
-
text: [
|
|
322
|
-
`[SYSTEM DIRECTIVE: ASTROCODE — RUN_FAILED]`,
|
|
323
|
-
``,
|
|
324
|
-
`Run \`${next.run_id}\` failed at stage \`${next.stage_key}\`.`,
|
|
325
|
-
`Error: ${next.error_text}`,
|
|
326
|
-
].join("\n"),
|
|
327
320
|
});
|
|
328
|
-
actions.push(`injected run failed message for ${next.run_id}`);
|
|
329
321
|
}
|
|
330
|
-
break;
|
|
331
322
|
}
|
|
332
|
-
actions.push(`
|
|
333
|
-
break;
|
|
334
|
-
}
|
|
335
|
-
// Housekeeping event (best-effort)
|
|
336
|
-
try {
|
|
337
|
-
db.prepare("INSERT INTO events (event_id, run_id, stage_key, type, body_json, created_at) VALUES (?, NULL, NULL, ?, ?, ?)").run(newEventId(), EVENT_TYPES.WORKFLOW_PROCEED, JSON.stringify({ started_at: startedAt, mode, max_steps: steps, actions }), nowISO());
|
|
338
|
-
}
|
|
339
|
-
catch (e) {
|
|
340
|
-
warnings.push(`workflow.proceed event insert failed (non-fatal): ${String(e)}`);
|
|
323
|
+
actions.push(`ui: flushed ${uiEvents.length} event(s)`);
|
|
341
324
|
}
|
|
325
|
+
// Housekeeping event
|
|
326
|
+
db.prepare("INSERT INTO events (event_id, run_id, stage_key, type, body_json, created_at) VALUES (?, NULL, NULL, ?, ?, ?)").run(newEventId(), EVENT_TYPES.WORKFLOW_PROCEED, JSON.stringify({ started_at: startedAt, mode, max_steps: steps, actions }), nowISO());
|
|
342
327
|
const active = getActiveRun(db);
|
|
343
328
|
const lines = [];
|
|
344
329
|
lines.push(`# astro_workflow_proceed`);
|
package/dist/ui/inject.d.ts
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Inject a visible prompt into the conversation.
|
|
3
|
+
* - Deterministic ordering per session
|
|
4
|
+
* - Correct SDK binding (prevents `this._client` undefined)
|
|
5
|
+
* - Awaitable: resolves when delivered, rejects after max retries
|
|
6
|
+
*/
|
|
1
7
|
export declare function injectChatPrompt(opts: {
|
|
2
8
|
ctx: any;
|
|
3
9
|
sessionId?: string;
|
package/dist/ui/inject.js
CHANGED
|
@@ -1,88 +1,95 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
const MAX_ATTEMPTS = 4;
|
|
2
|
+
const RETRY_DELAYS_MS = [250, 500, 1000, 2000]; // attempt 1..4
|
|
3
|
+
// Per-session queues so one stuck session doesn't block others
|
|
4
|
+
const queues = new Map();
|
|
5
|
+
const running = new Set();
|
|
4
6
|
function sleep(ms) {
|
|
5
7
|
return new Promise((r) => setTimeout(r, ms));
|
|
6
8
|
}
|
|
7
|
-
function
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
if (typeof direct === "string" && direct.length > 0)
|
|
12
|
-
return direct;
|
|
13
|
-
return null;
|
|
14
|
-
}
|
|
15
|
-
async function tryInjectOnce(opts) {
|
|
16
|
-
const { ctx, sessionId, text, agent } = opts;
|
|
17
|
-
// Prefer explicit chat prompt API
|
|
18
|
-
const promptApi = ctx?.client?.session?.prompt;
|
|
19
|
-
if (!promptApi) {
|
|
9
|
+
function getPromptInvoker(ctx) {
|
|
10
|
+
const session = ctx?.client?.session;
|
|
11
|
+
const prompt = session?.prompt;
|
|
12
|
+
if (!session || typeof prompt !== "function") {
|
|
20
13
|
throw new Error("API not available (ctx.client.session.prompt)");
|
|
21
14
|
}
|
|
15
|
+
return { session, prompt };
|
|
16
|
+
}
|
|
17
|
+
async function tryInjectOnce(item) {
|
|
18
|
+
const { ctx, sessionId, text, agent = "Astro" } = item;
|
|
22
19
|
const prefixedText = `[${agent}]\n\n${text}`;
|
|
23
|
-
|
|
24
|
-
|
|
20
|
+
const { session, prompt } = getPromptInvoker(ctx);
|
|
21
|
+
// IMPORTANT: force correct `this` binding
|
|
22
|
+
await prompt.call(session, {
|
|
25
23
|
path: { id: sessionId },
|
|
26
24
|
body: {
|
|
27
25
|
parts: [{ type: "text", text: prefixedText }],
|
|
26
|
+
agent,
|
|
28
27
|
},
|
|
29
28
|
});
|
|
30
29
|
}
|
|
31
|
-
async function
|
|
32
|
-
if (
|
|
30
|
+
async function runSessionQueue(sessionId) {
|
|
31
|
+
if (running.has(sessionId))
|
|
33
32
|
return;
|
|
34
|
-
|
|
35
|
-
return;
|
|
36
|
-
isInjecting = true;
|
|
33
|
+
running.add(sessionId);
|
|
37
34
|
try {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
// eslint-disable-next-line no-console
|
|
48
|
-
console.warn("[Astrocode] Injection skipped: no sessionId");
|
|
49
|
-
continue;
|
|
35
|
+
// eslint-disable-next-line no-constant-condition
|
|
36
|
+
while (true) {
|
|
37
|
+
const q = queues.get(sessionId);
|
|
38
|
+
if (!q || q.length === 0)
|
|
39
|
+
break;
|
|
40
|
+
const item = q.shift();
|
|
41
|
+
try {
|
|
42
|
+
await tryInjectOnce(item);
|
|
43
|
+
item.resolve();
|
|
50
44
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
}
|
|
60
|
-
catch (e) {
|
|
61
|
-
lastErr = e;
|
|
62
|
-
const delay = baseDelayMs * Math.pow(2, attempt - 1); // 250, 500, 1000, 2000
|
|
63
|
-
// eslint-disable-next-line no-console
|
|
64
|
-
console.warn(`[Astrocode] Injection attempt ${attempt}/${maxAttempts} failed: ${String(e)}; retrying in ${delay}ms`);
|
|
65
|
-
await sleep(delay);
|
|
45
|
+
catch (err) {
|
|
46
|
+
item.attempts += 1;
|
|
47
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
48
|
+
const delay = RETRY_DELAYS_MS[Math.min(item.attempts - 1, RETRY_DELAYS_MS.length - 1)] ?? 2000;
|
|
49
|
+
if (item.attempts >= MAX_ATTEMPTS) {
|
|
50
|
+
console.warn(`[Astrocode] Injection failed permanently after ${item.attempts} attempts: ${msg}`);
|
|
51
|
+
item.reject(err);
|
|
52
|
+
continue;
|
|
66
53
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
//
|
|
70
|
-
|
|
54
|
+
console.warn(`[Astrocode] Injection attempt ${item.attempts}/${MAX_ATTEMPTS} failed: ${msg}; retrying in ${delay}ms`);
|
|
55
|
+
await sleep(delay);
|
|
56
|
+
// Requeue at front to preserve order (and avoid starving later messages)
|
|
57
|
+
const q2 = queues.get(sessionId) ?? [];
|
|
58
|
+
q2.unshift(item);
|
|
59
|
+
queues.set(sessionId, q2);
|
|
71
60
|
}
|
|
72
61
|
}
|
|
73
62
|
}
|
|
74
63
|
finally {
|
|
75
|
-
|
|
64
|
+
running.delete(sessionId);
|
|
76
65
|
}
|
|
77
66
|
}
|
|
67
|
+
/**
|
|
68
|
+
* Inject a visible prompt into the conversation.
|
|
69
|
+
* - Deterministic ordering per session
|
|
70
|
+
* - Correct SDK binding (prevents `this._client` undefined)
|
|
71
|
+
* - Awaitable: resolves when delivered, rejects after max retries
|
|
72
|
+
*/
|
|
78
73
|
export async function injectChatPrompt(opts) {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
74
|
+
const sessionId = opts.sessionId ?? opts.ctx?.sessionID;
|
|
75
|
+
if (!sessionId) {
|
|
76
|
+
console.warn("[Astrocode] Skipping injection: No sessionId provided");
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
return new Promise((resolve, reject) => {
|
|
80
|
+
const item = {
|
|
81
|
+
ctx: opts.ctx,
|
|
82
|
+
sessionId,
|
|
83
|
+
text: opts.text,
|
|
84
|
+
agent: opts.agent,
|
|
85
|
+
attempts: 0,
|
|
86
|
+
resolve,
|
|
87
|
+
reject,
|
|
88
|
+
};
|
|
89
|
+
const q = queues.get(sessionId) ?? [];
|
|
90
|
+
q.push(item);
|
|
91
|
+
queues.set(sessionId, q);
|
|
92
|
+
// Fire worker (don't await here; caller awaits the returned Promise)
|
|
93
|
+
void runSessionQueue(sessionId);
|
|
85
94
|
});
|
|
86
|
-
// Fire-and-forget; queue drain is serialized by isInjecting.
|
|
87
|
-
void processQueue();
|
|
88
95
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import type { AstrocodeConfig } from "../config/schema";
|
|
2
2
|
import type { SqliteDb } from "../state/db";
|
|
3
3
|
import type { RunRow, StageKey, StageRunRow, StoryRow } from "../state/types";
|
|
4
|
-
import type { ToastOptions } from "../ui/toasts";
|
|
5
4
|
export declare const EVENT_TYPES: {
|
|
6
5
|
readonly RUN_STARTED: "run.started";
|
|
7
6
|
readonly RUN_COMPLETED: "run.completed";
|
|
@@ -11,28 +10,39 @@ export declare const EVENT_TYPES: {
|
|
|
11
10
|
readonly STAGE_STARTED: "stage.started";
|
|
12
11
|
readonly WORKFLOW_PROCEED: "workflow.proceed";
|
|
13
12
|
};
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
13
|
+
export type UiEmitEvent = {
|
|
14
|
+
kind: "stage_started";
|
|
15
|
+
run_id: string;
|
|
16
|
+
stage_key: StageKey;
|
|
17
|
+
agent_name?: string;
|
|
18
|
+
} | {
|
|
19
|
+
kind: "run_completed";
|
|
20
|
+
run_id: string;
|
|
21
|
+
story_key: string;
|
|
22
|
+
} | {
|
|
23
|
+
kind: "run_failed";
|
|
24
|
+
run_id: string;
|
|
25
|
+
story_key: string;
|
|
26
|
+
stage_key: StageKey;
|
|
27
|
+
error_text: string;
|
|
28
28
|
};
|
|
29
|
+
export type UiEmit = (e: UiEmitEvent) => void;
|
|
29
30
|
/**
|
|
30
31
|
* PLANNING-FIRST REDESIGN
|
|
31
32
|
* ----------------------
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
33
|
+
* Never mutate story title/body.
|
|
34
|
+
*
|
|
35
|
+
* Deterministic trigger:
|
|
36
|
+
* - config.workflow.genesis_planning:
|
|
37
|
+
* - "off" => never attach directive
|
|
38
|
+
* - "first_story_only"=> only when story_key === "S-0001"
|
|
39
|
+
* - "always" => attach for every run
|
|
40
|
+
*
|
|
41
|
+
* Contract: DB is already initialized before workflow is used:
|
|
42
|
+
* - schema tables exist
|
|
43
|
+
* - repo_state singleton row (id=1) exists
|
|
44
|
+
*
|
|
45
|
+
* IMPORTANT: Do NOT call withTx() in here. The caller owns transaction boundaries.
|
|
36
46
|
*/
|
|
37
47
|
export type NextAction = {
|
|
38
48
|
kind: "idle";
|
|
@@ -67,20 +77,10 @@ export declare function decideNextAction(db: SqliteDb, config: AstrocodeConfig):
|
|
|
67
77
|
export declare function createRunForStory(db: SqliteDb, config: AstrocodeConfig, storyKey: string): {
|
|
68
78
|
run_id: string;
|
|
69
79
|
};
|
|
70
|
-
/**
|
|
71
|
-
* STAGE MOVEMENT (START) — now async so UI injection is deterministic.
|
|
72
|
-
*/
|
|
73
80
|
export declare function startStage(db: SqliteDb, runId: string, stageKey: StageKey, meta?: {
|
|
74
81
|
subagent_type?: string;
|
|
75
82
|
subagent_session_id?: string;
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
* STAGE CLOSED (RUN COMPLETED) — now async so UI injection is deterministic.
|
|
80
|
-
*/
|
|
81
|
-
export declare function completeRun(db: SqliteDb, runId: string, ui?: WorkflowUi): Promise<void>;
|
|
82
|
-
/**
|
|
83
|
-
* STAGE CLOSED (RUN FAILED) — now async so UI injection is deterministic.
|
|
84
|
-
*/
|
|
85
|
-
export declare function failRun(db: SqliteDb, runId: string, stageKey: StageKey, errorText: string, ui?: WorkflowUi): Promise<void>;
|
|
83
|
+
}, emit?: UiEmit): void;
|
|
84
|
+
export declare function completeRun(db: SqliteDb, runId: string, emit?: UiEmit): void;
|
|
85
|
+
export declare function failRun(db: SqliteDb, runId: string, stageKey: StageKey, errorText: string, emit?: UiEmit): void;
|
|
86
86
|
export declare function abortRun(db: SqliteDb, runId: string, reason: string): void;
|