astrocode-workflow 0.2.0 → 0.3.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/state/db.d.ts +1 -1
- package/dist/state/db.js +62 -4
- package/dist/tools/workflow.d.ts +1 -1
- package/dist/tools/workflow.js +114 -94
- package/dist/ui/inject.d.ts +3 -17
- package/dist/ui/inject.js +68 -98
- package/package.json +1 -1
- package/src/state/db.ts +63 -4
- package/src/tools/workflow.ts +155 -136
- package/src/tools/workflow.ts.backup +681 -0
- package/src/ui/inject.ts +78 -107
package/package.json
CHANGED
package/src/state/db.ts
CHANGED
|
@@ -48,7 +48,32 @@ export function configurePragmas(db: SqliteDb, pragmas: Record<string, any>) {
|
|
|
48
48
|
if (pragmas.temp_store) db.pragma(`temp_store = ${pragmas.temp_store}`);
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
/**
|
|
51
|
+
/**
|
|
52
|
+
* Re-entrant transaction helper.
|
|
53
|
+
*
|
|
54
|
+
* SQLite rejects BEGIN inside BEGIN. We use:
|
|
55
|
+
* - depth=0: BEGIN IMMEDIATE ... COMMIT/ROLLBACK
|
|
56
|
+
* - depth>0: SAVEPOINT sp_n ... RELEASE / ROLLBACK TO + RELEASE
|
|
57
|
+
*
|
|
58
|
+
* This allows callers to safely nest withTx across layers (tools -> workflow -> state machine)
|
|
59
|
+
* without "cannot start a transaction within a transaction".
|
|
60
|
+
*/
|
|
61
|
+
const TX_DEPTH = new WeakMap<object, number>();
|
|
62
|
+
|
|
63
|
+
function getDepth(db: SqliteDb): number {
|
|
64
|
+
return TX_DEPTH.get(db as any) ?? 0;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function setDepth(db: SqliteDb, depth: number) {
|
|
68
|
+
if (depth <= 0) TX_DEPTH.delete(db as any);
|
|
69
|
+
else TX_DEPTH.set(db as any, depth);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function savepointName(depth: number): string {
|
|
73
|
+
return `sp_${depth}`;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** BEGIN IMMEDIATE transaction helper (re-entrant). */
|
|
52
77
|
export function withTx<T>(db: SqliteDb, fn: () => T, opts?: { require?: boolean }): T {
|
|
53
78
|
const adapter = createDatabaseAdapter();
|
|
54
79
|
const available = adapter.isAvailable();
|
|
@@ -58,18 +83,52 @@ export function withTx<T>(db: SqliteDb, fn: () => T, opts?: { require?: boolean
|
|
|
58
83
|
return fn();
|
|
59
84
|
}
|
|
60
85
|
|
|
61
|
-
db
|
|
86
|
+
const depth = getDepth(db);
|
|
87
|
+
|
|
88
|
+
if (depth === 0) {
|
|
89
|
+
db.exec("BEGIN IMMEDIATE");
|
|
90
|
+
setDepth(db, 1);
|
|
91
|
+
try {
|
|
92
|
+
const out = fn();
|
|
93
|
+
db.exec("COMMIT");
|
|
94
|
+
return out;
|
|
95
|
+
} catch (e) {
|
|
96
|
+
try {
|
|
97
|
+
db.exec("ROLLBACK");
|
|
98
|
+
} catch {
|
|
99
|
+
// ignore
|
|
100
|
+
}
|
|
101
|
+
throw e;
|
|
102
|
+
} finally {
|
|
103
|
+
setDepth(db, 0);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Nested: use SAVEPOINT
|
|
108
|
+
const nextDepth = depth + 1;
|
|
109
|
+
const sp = savepointName(nextDepth);
|
|
110
|
+
|
|
111
|
+
db.exec(`SAVEPOINT ${sp}`);
|
|
112
|
+
setDepth(db, nextDepth);
|
|
113
|
+
|
|
62
114
|
try {
|
|
63
115
|
const out = fn();
|
|
64
|
-
db.exec(
|
|
116
|
+
db.exec(`RELEASE SAVEPOINT ${sp}`);
|
|
65
117
|
return out;
|
|
66
118
|
} catch (e) {
|
|
67
119
|
try {
|
|
68
|
-
db.exec(
|
|
120
|
+
db.exec(`ROLLBACK TO SAVEPOINT ${sp}`);
|
|
121
|
+
} catch {
|
|
122
|
+
// ignore
|
|
123
|
+
}
|
|
124
|
+
try {
|
|
125
|
+
db.exec(`RELEASE SAVEPOINT ${sp}`);
|
|
69
126
|
} catch {
|
|
70
127
|
// ignore
|
|
71
128
|
}
|
|
72
129
|
throw e;
|
|
130
|
+
} finally {
|
|
131
|
+
setDepth(db, depth);
|
|
73
132
|
}
|
|
74
133
|
}
|
|
75
134
|
|
package/src/tools/workflow.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
// src/tools/workflow.ts
|
|
1
2
|
import { tool, type ToolDefinition } from "@opencode-ai/plugin/tool";
|
|
2
3
|
import type { AstrocodeConfig } from "../config/schema";
|
|
3
4
|
import type { SqliteDb } from "../state/db";
|
|
4
|
-
import { withTx } from "../state/db";
|
|
5
5
|
import type { StageKey } from "../state/types";
|
|
6
6
|
import { buildContextSnapshot } from "../workflow/context";
|
|
7
7
|
import { decideNextAction, createRunForStory, startStage, completeRun, getActiveRun, EVENT_TYPES } from "../workflow/state-machine";
|
|
@@ -11,6 +11,7 @@ import { nowISO } from "../shared/time";
|
|
|
11
11
|
import { newEventId } from "../state/ids";
|
|
12
12
|
import { debug } from "../shared/log";
|
|
13
13
|
import { createToastManager } from "../ui/toasts";
|
|
14
|
+
import type { AgentConfig } from "@opencode-ai/sdk";
|
|
14
15
|
|
|
15
16
|
// Agent name mapping for case-sensitive resolution
|
|
16
17
|
export const STAGE_TO_AGENT_MAP: Record<string, string> = {
|
|
@@ -37,23 +38,17 @@ export function resolveAgentName(stageKey: StageKey, config: AstrocodeConfig, ag
|
|
|
37
38
|
// Validate that the agent actually exists in the registry
|
|
38
39
|
if (agents && !agents[candidate]) {
|
|
39
40
|
const warning = `Agent "${candidate}" not found in registry for stage "${stageKey}". Falling back to General.`;
|
|
40
|
-
if (warnings)
|
|
41
|
-
|
|
42
|
-
} else {
|
|
43
|
-
console.warn(`[Astrocode] ${warning}`);
|
|
44
|
-
}
|
|
41
|
+
if (warnings) warnings.push(warning);
|
|
42
|
+
else console.warn(`[Astrocode] ${warning}`);
|
|
45
43
|
candidate = "General";
|
|
46
44
|
}
|
|
47
45
|
|
|
48
46
|
// Final guard: ensure General exists, fallback to built-in "general" if not
|
|
49
47
|
if (agents && !agents[candidate]) {
|
|
50
48
|
const finalWarning = `Critical: General agent not found in registry. Falling back to built-in "general".`;
|
|
51
|
-
if (warnings)
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
console.warn(`[Astrocode] ${finalWarning}`);
|
|
55
|
-
}
|
|
56
|
-
return "general"; // built-in, guaranteed by OpenCode
|
|
49
|
+
if (warnings) warnings.push(finalWarning);
|
|
50
|
+
else console.warn(`[Astrocode] ${finalWarning}`);
|
|
51
|
+
return "general";
|
|
57
52
|
}
|
|
58
53
|
|
|
59
54
|
return candidate;
|
|
@@ -94,10 +89,6 @@ function stageConstraints(stage: StageKey, cfg: AstrocodeConfig): string[] {
|
|
|
94
89
|
return common;
|
|
95
90
|
}
|
|
96
91
|
|
|
97
|
-
function agentNameForStage(stage: StageKey, cfg: AstrocodeConfig): string {
|
|
98
|
-
return cfg.agents.stage_agent_names[stage];
|
|
99
|
-
}
|
|
100
|
-
|
|
101
92
|
function buildDelegationPrompt(opts: {
|
|
102
93
|
stageDirective: string;
|
|
103
94
|
run_id: string;
|
|
@@ -126,14 +117,11 @@ function buildDelegationPrompt(opts: {
|
|
|
126
117
|
`Important: do NOT do any stage work yourself in orchestrator mode.`,
|
|
127
118
|
].join("\n").trim();
|
|
128
119
|
|
|
129
|
-
// Debug log the delegation prompt to troubleshoot agent output issues
|
|
130
120
|
debug(`Delegating stage ${stage_key} to agent ${stage_agent_name}`, { prompt_length: prompt.length });
|
|
131
121
|
|
|
132
122
|
return prompt;
|
|
133
123
|
}
|
|
134
124
|
|
|
135
|
-
import { AgentConfig } from "@opencode-ai/sdk";
|
|
136
|
-
|
|
137
125
|
export function createAstroWorkflowProceedTool(opts: { ctx: any; config: AstrocodeConfig; db: SqliteDb; agents?: Record<string, AgentConfig> }): ToolDefinition {
|
|
138
126
|
const { ctx, config, db, agents } = opts;
|
|
139
127
|
const toasts = createToastManager({ ctx, throttleMs: config.ui.toasts.throttle_ms });
|
|
@@ -162,71 +150,59 @@ export function createAstroWorkflowProceedTool(opts: { ctx: any; config: Astroco
|
|
|
162
150
|
}
|
|
163
151
|
|
|
164
152
|
if (next.kind === "start_run") {
|
|
165
|
-
|
|
153
|
+
// NOTE: createRunForStory owns its own tx (state-machine.ts).
|
|
154
|
+
const { run_id } = createRunForStory(db, config, next.story_key);
|
|
166
155
|
actions.push(`started run ${run_id} for story ${next.story_key}`);
|
|
167
156
|
|
|
168
157
|
if (config.ui.toasts.enabled && config.ui.toasts.show_run_started) {
|
|
169
158
|
await toasts.show({ title: "Astrocode", message: `Run started (${run_id})`, variant: "success" });
|
|
170
159
|
}
|
|
171
160
|
|
|
161
|
+
if (sessionId) {
|
|
162
|
+
await injectChatPrompt({
|
|
163
|
+
ctx,
|
|
164
|
+
sessionId,
|
|
165
|
+
agent: "Astro",
|
|
166
|
+
text: [
|
|
167
|
+
`[SYSTEM DIRECTIVE: ASTROCODE — RUN_STARTED]`,
|
|
168
|
+
``,
|
|
169
|
+
`Run started: \`${run_id}\``,
|
|
170
|
+
`Story: \`${next.story_key}\``,
|
|
171
|
+
``,
|
|
172
|
+
`Next: call **astro_workflow_proceed** again to delegate the first stage.`,
|
|
173
|
+
].join("\n"),
|
|
174
|
+
});
|
|
175
|
+
actions.push(`injected run started message for ${run_id}`);
|
|
176
|
+
}
|
|
177
|
+
|
|
172
178
|
if (mode === "step") break;
|
|
173
179
|
continue;
|
|
174
180
|
}
|
|
175
181
|
|
|
176
182
|
if (next.kind === "complete_run") {
|
|
177
|
-
|
|
183
|
+
// NOTE: completeRun owns its own tx (state-machine.ts).
|
|
184
|
+
completeRun(db, next.run_id);
|
|
178
185
|
actions.push(`completed run ${next.run_id}`);
|
|
179
186
|
|
|
180
187
|
if (config.ui.toasts.enabled && config.ui.toasts.show_run_completed) {
|
|
181
188
|
await toasts.show({ title: "Astrocode", message: `Run completed (${next.run_id})`, variant: "success" });
|
|
182
189
|
}
|
|
183
190
|
|
|
184
|
-
//
|
|
191
|
+
// ✅ explicit injection on completeRun (requested)
|
|
185
192
|
if (sessionId) {
|
|
186
|
-
const continueDirective = [
|
|
187
|
-
`[SYSTEM DIRECTIVE: ASTROCODE — CONTINUE]`,
|
|
188
|
-
``,
|
|
189
|
-
`Run ${next.run_id} completed successfully.`,
|
|
190
|
-
``,
|
|
191
|
-
`The Clara Forms implementation run has finished all stages. The spec has been analyzed and decomposed into prioritized implementation stories.`,
|
|
192
|
-
``,
|
|
193
|
-
`Next actions: Review the generated stories and approve the next one to continue development.`,
|
|
194
|
-
].join("\n");
|
|
195
|
-
|
|
196
|
-
await injectChatPrompt({
|
|
197
|
-
ctx,
|
|
198
|
-
sessionId,
|
|
199
|
-
text: continueDirective,
|
|
200
|
-
agent: "Astro"
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
actions.push(`injected continuation directive for completed run ${next.run_id}`);
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
// Check for next approved story to start
|
|
207
|
-
const nextStory = db.prepare(
|
|
208
|
-
"SELECT story_key, title FROM stories WHERE state = 'approved' ORDER BY priority DESC, created_at ASC LIMIT 1"
|
|
209
|
-
).get() as { story_key: string; title: string } | undefined;
|
|
210
|
-
|
|
211
|
-
if (nextStory && sessionId) {
|
|
212
|
-
const nextDirective = [
|
|
213
|
-
`[SYSTEM DIRECTIVE: ASTROCODE — START_NEXT_STORY]`,
|
|
214
|
-
``,
|
|
215
|
-
`The previous run completed successfully. Start the next approved story.`,
|
|
216
|
-
``,
|
|
217
|
-
`Next Story: ${nextStory.story_key} — ${nextStory.title}`,
|
|
218
|
-
``,
|
|
219
|
-
`Action: Call astro_story_approve with story_key="${nextStory.story_key}" to start it, or select a different story.`,
|
|
220
|
-
].join("\n");
|
|
221
|
-
|
|
222
193
|
await injectChatPrompt({
|
|
223
194
|
ctx,
|
|
224
195
|
sessionId,
|
|
225
|
-
|
|
226
|
-
|
|
196
|
+
agent: "Astro",
|
|
197
|
+
text: [
|
|
198
|
+
`[SYSTEM DIRECTIVE: ASTROCODE — RUN_COMPLETED]`,
|
|
199
|
+
``,
|
|
200
|
+
`Run \`${next.run_id}\` completed.`,
|
|
201
|
+
``,
|
|
202
|
+
`Next: call **astro_workflow_proceed** (mode=step) to start the next approved story (if any).`,
|
|
203
|
+
].join("\n"),
|
|
227
204
|
});
|
|
228
|
-
|
|
229
|
-
actions.push(`injected directive to start next story ${nextStory.story_key}`);
|
|
205
|
+
actions.push(`injected run completed message for ${next.run_id}`);
|
|
230
206
|
}
|
|
231
207
|
|
|
232
208
|
if (mode === "step") break;
|
|
@@ -236,62 +212,66 @@ export function createAstroWorkflowProceedTool(opts: { ctx: any; config: Astroco
|
|
|
236
212
|
if (next.kind === "delegate_stage") {
|
|
237
213
|
const active = getActiveRun(db);
|
|
238
214
|
if (!active) throw new Error("Invariant: delegate_stage but no active run.");
|
|
215
|
+
|
|
239
216
|
const run = db.prepare("SELECT * FROM runs WHERE run_id=?").get(active.run_id) as any;
|
|
240
217
|
const story = db.prepare("SELECT * FROM stories WHERE story_key=?").get(run.story_key) as any;
|
|
241
218
|
|
|
242
|
-
// Mark stage started + set subagent_type to the stage agent.
|
|
243
219
|
let agentName = resolveAgentName(next.stage_key, config, agents, warnings);
|
|
244
220
|
|
|
245
|
-
// Validate agent availability with fallback chain
|
|
246
|
-
const systemConfig = config as any;
|
|
247
|
-
// Check both the system config agent map (if present) OR the local agents map passed to the tool
|
|
248
221
|
const agentExists = (name: string) => {
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
// Check system config agent map
|
|
254
|
-
if (systemConfig.agent && systemConfig.agent[name]) {
|
|
255
|
-
return true;
|
|
256
|
-
}
|
|
257
|
-
// For known stage agents, assume they exist (they are system-provided subagents)
|
|
258
|
-
const knownStageAgents = ["Frame", "Plan", "Spec", "Implement", "Review", "Verify", "Close"];
|
|
259
|
-
if (knownStageAgents.includes(name)) {
|
|
260
|
-
return true;
|
|
261
|
-
}
|
|
262
|
-
return false;
|
|
222
|
+
if (agents && agents[name]) return true;
|
|
223
|
+
const knownStageAgents = ["Frame", "Plan", "Spec", "Implement", "Review", "Verify", "Close", "General", "Astro", "general"];
|
|
224
|
+
if (knownStageAgents.includes(name)) return true;
|
|
225
|
+
return false;
|
|
263
226
|
};
|
|
264
227
|
|
|
228
|
+
if (!agentExists(agentName)) {
|
|
229
|
+
const originalAgent = agentName;
|
|
230
|
+
console.warn(`[Astrocode] Agent ${agentName} not found. Falling back to orchestrator.`);
|
|
231
|
+
agentName = config.agents?.orchestrator_name || "Astro";
|
|
265
232
|
if (!agentExists(agentName)) {
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
// First fallback: orchestrator
|
|
269
|
-
agentName = config.agents?.orchestrator_name || "Astro";
|
|
233
|
+
console.warn(`[Astrocode] Orchestrator ${agentName} not available. Falling back to General.`);
|
|
234
|
+
agentName = "General";
|
|
270
235
|
if (!agentExists(agentName)) {
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
if (!agentExists(agentName)) {
|
|
275
|
-
throw new Error(`Critical: No agents available for delegation. Primary: ${originalAgent}, Orchestrator: ${config.agents?.orchestrator_name || "Astro"}, General: unavailable`);
|
|
276
|
-
}
|
|
236
|
+
throw new Error(
|
|
237
|
+
`Critical: No agents available for delegation. Primary: ${originalAgent}, Orchestrator: ${config.agents?.orchestrator_name || "Astro"}, General: unavailable`
|
|
238
|
+
);
|
|
277
239
|
}
|
|
278
240
|
}
|
|
241
|
+
}
|
|
279
242
|
|
|
280
|
-
|
|
281
|
-
|
|
243
|
+
// NOTE: startStage owns its own tx (state-machine.ts).
|
|
244
|
+
startStage(db, active.run_id, next.stage_key, { subagent_type: agentName });
|
|
282
245
|
|
|
283
|
-
|
|
284
|
-
if (config.debug?.telemetry?.enabled) {
|
|
285
|
-
// eslint-disable-next-line no-console
|
|
286
|
-
console.log(`[Astrocode:delegate] run_id=${active.run_id} stage=${next.stage_key} agent=${agentName} fallback=${agentName !== resolveAgentName(next.stage_key, config, agents) ? 'yes' : 'no'}`);
|
|
287
|
-
}
|
|
288
|
-
});
|
|
246
|
+
actions.push(`stage started: ${next.stage_key}`);
|
|
289
247
|
|
|
290
248
|
if (config.ui.toasts.enabled && config.ui.toasts.show_stage_started) {
|
|
291
249
|
await toasts.show({ title: "Astrocode", message: `Stage started: ${next.stage_key}`, variant: "info" });
|
|
292
250
|
}
|
|
293
251
|
|
|
294
|
-
|
|
252
|
+
// ✅ explicit injection on startStage (requested)
|
|
253
|
+
if (sessionId) {
|
|
254
|
+
await injectChatPrompt({
|
|
255
|
+
ctx,
|
|
256
|
+
sessionId,
|
|
257
|
+
agent: "Astro",
|
|
258
|
+
text: [
|
|
259
|
+
`[SYSTEM DIRECTIVE: ASTROCODE — STAGE_STARTED]`,
|
|
260
|
+
``,
|
|
261
|
+
`Run: \`${active.run_id}\``,
|
|
262
|
+
`Stage: \`${next.stage_key}\``,
|
|
263
|
+
`Delegated to: \`${agentName}\``,
|
|
264
|
+
].join("\n"),
|
|
265
|
+
});
|
|
266
|
+
actions.push(`injected stage started message for ${next.stage_key}`);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const context = buildContextSnapshot({
|
|
270
|
+
db,
|
|
271
|
+
config,
|
|
272
|
+
run_id: active.run_id,
|
|
273
|
+
next_action: `delegate stage ${next.stage_key}`,
|
|
274
|
+
});
|
|
295
275
|
|
|
296
276
|
const stageDirective = buildStageDirective({
|
|
297
277
|
config,
|
|
@@ -312,35 +292,38 @@ export function createAstroWorkflowProceedTool(opts: { ctx: any; config: Astroco
|
|
|
312
292
|
stage_agent_name: agentName,
|
|
313
293
|
});
|
|
314
294
|
|
|
315
|
-
//
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
295
|
+
// Best-effort: continuations table may not exist on older DBs.
|
|
296
|
+
try {
|
|
297
|
+
const h = directiveHash(delegatePrompt);
|
|
298
|
+
const now = nowISO();
|
|
299
|
+
if (sessionId) {
|
|
300
|
+
db.prepare(
|
|
301
|
+
"INSERT INTO continuations (session_id, run_id, directive_hash, kind, reason, created_at) VALUES (?, ?, ?, 'stage', ?, ?)"
|
|
302
|
+
).run(sessionId, active.run_id, h, `delegate ${next.stage_key}`, now);
|
|
303
|
+
}
|
|
304
|
+
} catch (e) {
|
|
305
|
+
warnings.push(`continuations insert failed (non-fatal): ${String(e)}`);
|
|
322
306
|
}
|
|
323
307
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
308
|
+
// Visible injection so user can see state
|
|
309
|
+
if (sessionId) {
|
|
310
|
+
await injectChatPrompt({ ctx, sessionId, text: delegatePrompt, agent: "Astro" });
|
|
327
311
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
].join("\n");
|
|
312
|
+
const continueMessage = [
|
|
313
|
+
`[SYSTEM DIRECTIVE: ASTROCODE — AWAIT_STAGE_COMPLETION]`,
|
|
314
|
+
``,
|
|
315
|
+
`Stage \`${next.stage_key}\` delegated to \`${agentName}\`.`,
|
|
316
|
+
``,
|
|
317
|
+
`When \`${agentName}\` completes, call:`,
|
|
318
|
+
`astro_stage_complete(run_id="${active.run_id}", stage_key="${next.stage_key}", output_text="[paste subagent output here]")`,
|
|
319
|
+
``,
|
|
320
|
+
`Then run **astro_workflow_proceed** again.`,
|
|
321
|
+
].join("\n");
|
|
339
322
|
|
|
340
|
-
|
|
341
|
-
|
|
323
|
+
await injectChatPrompt({ ctx, sessionId, text: continueMessage, agent: "Astro" });
|
|
324
|
+
}
|
|
342
325
|
|
|
343
|
-
|
|
326
|
+
actions.push(`delegated stage ${next.stage_key} via ${agentName}`);
|
|
344
327
|
|
|
345
328
|
// Stop here; subagent needs to run.
|
|
346
329
|
break;
|
|
@@ -348,43 +331,79 @@ export function createAstroWorkflowProceedTool(opts: { ctx: any; config: Astroco
|
|
|
348
331
|
|
|
349
332
|
if (next.kind === "await_stage_completion") {
|
|
350
333
|
actions.push(`await stage completion: ${next.stage_key}`);
|
|
351
|
-
|
|
334
|
+
|
|
352
335
|
if (sessionId) {
|
|
353
|
-
const context = buildContextSnapshot({
|
|
336
|
+
const context = buildContextSnapshot({
|
|
337
|
+
db,
|
|
338
|
+
config,
|
|
339
|
+
run_id: next.run_id,
|
|
340
|
+
next_action: `complete stage ${next.stage_key}`,
|
|
341
|
+
});
|
|
342
|
+
|
|
354
343
|
const prompt = [
|
|
355
344
|
`[SYSTEM DIRECTIVE: ASTROCODE — AWAIT_STAGE_OUTPUT]`,
|
|
356
345
|
``,
|
|
357
|
-
`Run
|
|
346
|
+
`Run \`${next.run_id}\` is waiting for stage \`${next.stage_key}\` output.`,
|
|
358
347
|
`If you have the subagent output, call astro_stage_complete with output_text=the FULL output.`,
|
|
359
348
|
``,
|
|
360
349
|
`Context snapshot:`,
|
|
361
350
|
context,
|
|
362
351
|
].join("\n").trim();
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
352
|
+
|
|
353
|
+
try {
|
|
354
|
+
const h = directiveHash(prompt);
|
|
355
|
+
const now = nowISO();
|
|
356
|
+
db.prepare(
|
|
357
|
+
"INSERT INTO continuations (session_id, run_id, directive_hash, kind, reason, created_at) VALUES (?, ?, ?, 'continue', ?, ?)"
|
|
358
|
+
).run(sessionId, next.run_id, h, `await ${next.stage_key}`, now);
|
|
359
|
+
} catch (e) {
|
|
360
|
+
warnings.push(`continuations insert failed (non-fatal): ${String(e)}`);
|
|
361
|
+
}
|
|
368
362
|
|
|
369
363
|
await injectChatPrompt({ ctx, sessionId, text: prompt, agent: "Astro" });
|
|
370
364
|
}
|
|
365
|
+
|
|
371
366
|
break;
|
|
372
367
|
}
|
|
373
368
|
|
|
374
369
|
if (next.kind === "failed") {
|
|
375
370
|
actions.push(`failed: ${next.stage_key} — ${next.error_text}`);
|
|
371
|
+
|
|
372
|
+
if (sessionId) {
|
|
373
|
+
await injectChatPrompt({
|
|
374
|
+
ctx,
|
|
375
|
+
sessionId,
|
|
376
|
+
agent: "Astro",
|
|
377
|
+
text: [
|
|
378
|
+
`[SYSTEM DIRECTIVE: ASTROCODE — RUN_FAILED]`,
|
|
379
|
+
``,
|
|
380
|
+
`Run \`${next.run_id}\` failed at stage \`${next.stage_key}\`.`,
|
|
381
|
+
`Error: ${next.error_text}`,
|
|
382
|
+
].join("\n"),
|
|
383
|
+
});
|
|
384
|
+
actions.push(`injected run failed message for ${next.run_id}`);
|
|
385
|
+
}
|
|
386
|
+
|
|
376
387
|
break;
|
|
377
388
|
}
|
|
378
389
|
|
|
379
|
-
// safety
|
|
380
390
|
actions.push(`unhandled next action: ${(next as any).kind}`);
|
|
381
391
|
break;
|
|
382
392
|
}
|
|
383
393
|
|
|
384
|
-
// Housekeeping event
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
394
|
+
// Housekeeping event (best-effort)
|
|
395
|
+
try {
|
|
396
|
+
db.prepare(
|
|
397
|
+
"INSERT INTO events (event_id, run_id, stage_key, type, body_json, created_at) VALUES (?, NULL, NULL, ?, ?, ?)"
|
|
398
|
+
).run(
|
|
399
|
+
newEventId(),
|
|
400
|
+
EVENT_TYPES.WORKFLOW_PROCEED,
|
|
401
|
+
JSON.stringify({ started_at: startedAt, mode, max_steps: steps, actions }),
|
|
402
|
+
nowISO()
|
|
403
|
+
);
|
|
404
|
+
} catch (e) {
|
|
405
|
+
warnings.push(`workflow.proceed event insert failed (non-fatal): ${String(e)}`);
|
|
406
|
+
}
|
|
388
407
|
|
|
389
408
|
const active = getActiveRun(db);
|
|
390
409
|
const lines: string[] = [];
|