astrocode-workflow 0.4.0 → 0.4.2
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/index.js +6 -0
- package/dist/shared/metrics.d.ts +66 -0
- package/dist/shared/metrics.js +112 -0
- package/dist/src/agents/commands.d.ts +9 -0
- package/dist/src/agents/commands.js +121 -0
- package/dist/src/agents/prompts.d.ts +3 -0
- package/dist/src/agents/prompts.js +232 -0
- package/dist/src/agents/registry.d.ts +6 -0
- package/dist/src/agents/registry.js +242 -0
- package/dist/src/agents/types.d.ts +14 -0
- package/dist/src/agents/types.js +8 -0
- package/dist/src/astro/workflow-runner.d.ts +11 -0
- package/dist/src/astro/workflow-runner.js +14 -0
- package/dist/src/config/config-handler.d.ts +4 -0
- package/dist/src/config/config-handler.js +46 -0
- package/dist/src/config/defaults.d.ts +3 -0
- package/dist/src/config/defaults.js +3 -0
- package/dist/src/config/loader.d.ts +11 -0
- package/dist/src/config/loader.js +82 -0
- package/dist/src/config/schema.d.ts +195 -0
- package/dist/src/config/schema.js +224 -0
- package/dist/src/hooks/continuation-enforcer.d.ts +34 -0
- package/dist/src/hooks/continuation-enforcer.js +190 -0
- package/dist/src/hooks/inject-provider.d.ts +27 -0
- package/dist/src/hooks/inject-provider.js +189 -0
- package/dist/src/hooks/tool-output-truncator.d.ts +25 -0
- package/dist/src/hooks/tool-output-truncator.js +57 -0
- package/dist/src/index.d.ts +3 -0
- package/dist/src/index.js +307 -0
- package/dist/src/shared/deep-merge.d.ts +8 -0
- package/dist/src/shared/deep-merge.js +25 -0
- package/dist/src/shared/hash.d.ts +1 -0
- package/dist/src/shared/hash.js +4 -0
- package/dist/src/shared/log.d.ts +7 -0
- package/dist/src/shared/log.js +24 -0
- package/dist/src/shared/metrics.d.ts +66 -0
- package/dist/src/shared/metrics.js +112 -0
- package/dist/src/shared/model-tuning.d.ts +9 -0
- package/dist/src/shared/model-tuning.js +28 -0
- package/dist/src/shared/paths.d.ts +19 -0
- package/dist/src/shared/paths.js +64 -0
- package/dist/src/shared/text.d.ts +4 -0
- package/dist/src/shared/text.js +19 -0
- package/dist/src/shared/time.d.ts +1 -0
- package/dist/src/shared/time.js +3 -0
- package/dist/src/state/adapters/index.d.ts +41 -0
- package/dist/src/state/adapters/index.js +115 -0
- package/dist/src/state/db.d.ts +16 -0
- package/dist/src/state/db.js +225 -0
- package/dist/src/state/ids.d.ts +8 -0
- package/dist/src/state/ids.js +25 -0
- package/dist/src/state/repo-lock.d.ts +67 -0
- package/dist/src/state/repo-lock.js +580 -0
- package/dist/src/state/schema.d.ts +2 -0
- package/dist/src/state/schema.js +258 -0
- package/dist/src/state/types.d.ts +71 -0
- package/dist/src/state/types.js +1 -0
- package/dist/src/state/workflow-repo-lock.d.ts +23 -0
- package/dist/src/state/workflow-repo-lock.js +83 -0
- package/dist/src/tools/artifacts.d.ts +18 -0
- package/dist/src/tools/artifacts.js +71 -0
- package/dist/src/tools/health.d.ts +8 -0
- package/dist/src/tools/health.js +88 -0
- package/dist/src/tools/index.d.ts +20 -0
- package/dist/src/tools/index.js +94 -0
- package/dist/src/tools/init.d.ts +17 -0
- package/dist/src/tools/init.js +96 -0
- package/dist/src/tools/injects.d.ts +53 -0
- package/dist/src/tools/injects.js +325 -0
- package/dist/src/tools/lock.d.ts +4 -0
- package/dist/src/tools/lock.js +78 -0
- package/dist/src/tools/metrics.d.ts +7 -0
- package/dist/src/tools/metrics.js +61 -0
- package/dist/src/tools/repair.d.ts +8 -0
- package/dist/src/tools/repair.js +26 -0
- package/dist/src/tools/reset.d.ts +8 -0
- package/dist/src/tools/reset.js +92 -0
- package/dist/src/tools/run.d.ts +13 -0
- package/dist/src/tools/run.js +54 -0
- package/dist/src/tools/spec.d.ts +12 -0
- package/dist/src/tools/spec.js +44 -0
- package/dist/src/tools/stage.d.ts +23 -0
- package/dist/src/tools/stage.js +371 -0
- package/dist/src/tools/status.d.ts +8 -0
- package/dist/src/tools/status.js +125 -0
- package/dist/src/tools/story.d.ts +23 -0
- package/dist/src/tools/story.js +85 -0
- package/dist/src/tools/workflow.d.ts +13 -0
- package/dist/src/tools/workflow.js +345 -0
- package/dist/src/ui/inject.d.ts +12 -0
- package/dist/src/ui/inject.js +107 -0
- package/dist/src/ui/toasts.d.ts +13 -0
- package/dist/src/ui/toasts.js +39 -0
- package/dist/src/workflow/artifacts.d.ts +24 -0
- package/dist/src/workflow/artifacts.js +45 -0
- package/dist/src/workflow/baton.d.ts +72 -0
- package/dist/src/workflow/baton.js +166 -0
- package/dist/src/workflow/context.d.ts +20 -0
- package/dist/src/workflow/context.js +113 -0
- package/dist/src/workflow/directives.d.ts +39 -0
- package/dist/src/workflow/directives.js +137 -0
- package/dist/src/workflow/repair.d.ts +8 -0
- package/dist/src/workflow/repair.js +99 -0
- package/dist/src/workflow/state-machine.d.ts +86 -0
- package/dist/src/workflow/state-machine.js +216 -0
- package/dist/src/workflow/story-helpers.d.ts +9 -0
- package/dist/src/workflow/story-helpers.js +13 -0
- package/dist/state/db.d.ts +1 -0
- package/dist/state/db.js +9 -0
- package/dist/state/repo-lock.d.ts +3 -0
- package/dist/state/repo-lock.js +29 -0
- package/dist/test/integration/db-transactions.test.d.ts +1 -0
- package/dist/test/integration/db-transactions.test.js +126 -0
- package/dist/test/integration/injection-metrics.test.d.ts +1 -0
- package/dist/test/integration/injection-metrics.test.js +129 -0
- package/dist/tools/health.d.ts +8 -0
- package/dist/tools/health.js +119 -0
- package/dist/tools/index.js +9 -0
- package/dist/tools/metrics.d.ts +7 -0
- package/dist/tools/metrics.js +61 -0
- package/dist/tools/reset.d.ts +8 -0
- package/dist/tools/reset.js +92 -0
- package/dist/tools/workflow.js +178 -168
- package/dist/ui/inject.js +21 -9
- package/package.json +6 -4
- package/src/astro/workflow-runner.ts +16 -0
- package/src/config/schema.ts +1 -0
- package/src/hooks/inject-provider.ts +94 -14
- package/src/index.ts +7 -0
- package/src/shared/metrics.ts +148 -0
- package/src/state/db.ts +10 -1
- package/src/state/schema.ts +8 -1
- package/src/tools/health.ts +99 -0
- package/src/tools/index.ts +12 -3
- package/src/tools/init.ts +7 -6
- package/src/tools/metrics.ts +71 -0
- package/src/tools/repair.ts +8 -4
- package/src/tools/reset.ts +100 -0
- package/src/tools/stage.ts +1 -0
- package/src/tools/status.ts +2 -1
- package/src/tools/story.ts +1 -0
- package/src/tools/workflow.ts +2 -0
- package/src/ui/inject.ts +21 -9
- package/src/workflow/repair.ts +2 -2
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { clampLines, normalizeNewlines, stripCodeFences } from "../shared/text";
|
|
2
|
+
import { z, ZodError } from "zod";
|
|
3
|
+
export const ASTRO_JSON_BEGIN = "<!-- ASTRO_JSON_BEGIN -->";
|
|
4
|
+
export const ASTRO_JSON_END = "<!-- ASTRO_JSON_END -->";
|
|
5
|
+
export const StageKeySchema = z.enum(["frame", "plan", "spec", "implement", "review", "verify", "close"]);
|
|
6
|
+
export const AstroJsonSchema = z.object({
|
|
7
|
+
schema_version: z.number().int().default(1),
|
|
8
|
+
run_id: z.string().optional(),
|
|
9
|
+
story_key: z.string().optional(),
|
|
10
|
+
stage_key: StageKeySchema,
|
|
11
|
+
status: z.enum(["ok", "blocked", "failed"]).default("ok"),
|
|
12
|
+
summary: z.string().default(""),
|
|
13
|
+
decisions: z.array(z.string()).default([]),
|
|
14
|
+
next_actions: z.array(z.string()).default([]),
|
|
15
|
+
tasks: z.array(z.object({
|
|
16
|
+
title: z.string(),
|
|
17
|
+
description: z.string().optional(),
|
|
18
|
+
complexity: z.number().int().min(1).max(10).optional(),
|
|
19
|
+
subtasks: z.array(z.string()).optional(),
|
|
20
|
+
})).default([]),
|
|
21
|
+
files: z.array(z.object({
|
|
22
|
+
path: z.string(),
|
|
23
|
+
kind: z.string().default("file"),
|
|
24
|
+
notes: z.string().optional(),
|
|
25
|
+
})).default([]),
|
|
26
|
+
evidence: z.array(z.object({
|
|
27
|
+
path: z.string(),
|
|
28
|
+
kind: z.string().default("evidence"),
|
|
29
|
+
notes: z.string().optional(),
|
|
30
|
+
})).default([]),
|
|
31
|
+
new_stories: z.array(z.object({
|
|
32
|
+
title: z.string(),
|
|
33
|
+
body_md: z.string().optional(),
|
|
34
|
+
priority: z.number().int().optional(),
|
|
35
|
+
})).default([]),
|
|
36
|
+
questions: z.array(z.string()).default([]),
|
|
37
|
+
metrics: z.record(z.string(), z.union([z.number(), z.string()])).default({}),
|
|
38
|
+
});
|
|
39
|
+
export function parseStageOutputText(text) {
|
|
40
|
+
const norm = normalizeNewlines(text ?? "").trim();
|
|
41
|
+
const beginIdx = norm.indexOf(ASTRO_JSON_BEGIN);
|
|
42
|
+
const endIdx = norm.indexOf(ASTRO_JSON_END);
|
|
43
|
+
let baton_md = "";
|
|
44
|
+
let jsonRaw = "";
|
|
45
|
+
let fallbackUsed = false;
|
|
46
|
+
if (beginIdx === -1 || endIdx === -1 || endIdx <= beginIdx) {
|
|
47
|
+
// Enhanced fallback: scan for any JSON object in the text
|
|
48
|
+
const jsonMatch = norm.match(/\{[\s\S]*\}/);
|
|
49
|
+
if (jsonMatch) {
|
|
50
|
+
try {
|
|
51
|
+
const parsed = JSON.parse(jsonMatch[0]);
|
|
52
|
+
const astroJson = AstroJsonSchema.parse(parsed);
|
|
53
|
+
return {
|
|
54
|
+
baton_md: norm.replace(jsonMatch[0], "").trim(),
|
|
55
|
+
astro_json: astroJson,
|
|
56
|
+
astro_json_raw: jsonMatch[0],
|
|
57
|
+
error: null
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
catch (e) {
|
|
61
|
+
// JSON found but invalid, continue to marker error
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
// Fallback: if no markers, check if the entire text is JSON
|
|
65
|
+
try {
|
|
66
|
+
const parsed = JSON.parse(norm);
|
|
67
|
+
const astroJson = AstroJsonSchema.parse(parsed);
|
|
68
|
+
return {
|
|
69
|
+
baton_md: "",
|
|
70
|
+
astro_json: astroJson,
|
|
71
|
+
astro_json_raw: norm,
|
|
72
|
+
error: null
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
catch (e) {
|
|
76
|
+
// Not JSON, proceed with marker error
|
|
77
|
+
}
|
|
78
|
+
// If no JSON found, create a default structure from the text
|
|
79
|
+
const defaultJson = {
|
|
80
|
+
schema_version: 1,
|
|
81
|
+
stage_key: "frame", // Default, will be overridden by actual stage_key
|
|
82
|
+
status: "ok",
|
|
83
|
+
summary: norm.trim() || "Stage completed without structured output",
|
|
84
|
+
decisions: [],
|
|
85
|
+
next_actions: [],
|
|
86
|
+
tasks: [],
|
|
87
|
+
files: [],
|
|
88
|
+
evidence: [],
|
|
89
|
+
new_stories: [],
|
|
90
|
+
questions: [],
|
|
91
|
+
metrics: {}
|
|
92
|
+
};
|
|
93
|
+
return {
|
|
94
|
+
baton_md: "",
|
|
95
|
+
astro_json: defaultJson,
|
|
96
|
+
astro_json_raw: JSON.stringify(defaultJson, null, 2),
|
|
97
|
+
error: null
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
const before = norm.slice(0, beginIdx).trim();
|
|
101
|
+
jsonRaw = norm.slice(beginIdx + ASTRO_JSON_BEGIN.length, endIdx).trim();
|
|
102
|
+
const after = norm.slice(endIdx + ASTRO_JSON_END.length).trim();
|
|
103
|
+
baton_md = [before, after].filter(Boolean).join("\n\n").trim();
|
|
104
|
+
try {
|
|
105
|
+
const cleaned = stripCodeFences(jsonRaw).trim();
|
|
106
|
+
const parsed = JSON.parse(cleaned);
|
|
107
|
+
const astroJson = AstroJsonSchema.parse(parsed);
|
|
108
|
+
return { baton_md, astro_json: astroJson, astro_json_raw: cleaned, error: null };
|
|
109
|
+
}
|
|
110
|
+
catch (e) {
|
|
111
|
+
if (e instanceof ZodError) {
|
|
112
|
+
return {
|
|
113
|
+
baton_md,
|
|
114
|
+
astro_json: null,
|
|
115
|
+
astro_json_raw: jsonRaw,
|
|
116
|
+
error: `Schema validation failed: ${e.message}. Ensure JSON conforms to ASTRO schema with required fields like stage_key, status, etc.`,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
return {
|
|
121
|
+
baton_md: norm,
|
|
122
|
+
astro_json: null,
|
|
123
|
+
astro_json_raw: null,
|
|
124
|
+
error: `JSON parsing failed: ${String(e)}. Ensure valid JSON syntax between ${ASTRO_JSON_BEGIN} and ${ASTRO_JSON_END} markers.`,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
export function buildBatonSummary(opts) {
|
|
130
|
+
const { config, stage_key, astro_json, baton_md } = opts;
|
|
131
|
+
const maxLines = config.context_compaction.baton_summary_max_lines;
|
|
132
|
+
const lines = [];
|
|
133
|
+
lines.push(`# ${stage_key} — Summary`);
|
|
134
|
+
if (astro_json?.summary) {
|
|
135
|
+
lines.push("", astro_json.summary.trim());
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
// Fallback: first non-empty paragraph
|
|
139
|
+
const paras = baton_md.split(/\n\n+/).map((p) => p.trim()).filter(Boolean);
|
|
140
|
+
if (paras[0])
|
|
141
|
+
lines.push("", paras[0]);
|
|
142
|
+
}
|
|
143
|
+
const addList = (title, items) => {
|
|
144
|
+
if (!items.length)
|
|
145
|
+
return;
|
|
146
|
+
lines.push("", `## ${title}`);
|
|
147
|
+
for (const it of items.slice(0, 12))
|
|
148
|
+
lines.push(`- ${it}`);
|
|
149
|
+
};
|
|
150
|
+
addList("Decisions", astro_json?.decisions ?? []);
|
|
151
|
+
addList("Next actions", astro_json?.next_actions ?? []);
|
|
152
|
+
const files = astro_json?.files ?? [];
|
|
153
|
+
if (files.length) {
|
|
154
|
+
lines.push("", "## Files");
|
|
155
|
+
for (const f of files.slice(0, 15))
|
|
156
|
+
lines.push(`- \`${f.path}\` (${f.kind})${f.notes ? ` — ${f.notes}` : ""}`);
|
|
157
|
+
}
|
|
158
|
+
const evidence = astro_json?.evidence ?? [];
|
|
159
|
+
if (evidence.length) {
|
|
160
|
+
lines.push("", "## Evidence");
|
|
161
|
+
for (const ev of evidence.slice(0, 15))
|
|
162
|
+
lines.push(`- \`${ev.path}\` (${ev.kind})${ev.notes ? ` — ${ev.notes}` : ""}`);
|
|
163
|
+
}
|
|
164
|
+
const out = lines.join("\n").trim();
|
|
165
|
+
return clampLines(out, maxLines);
|
|
166
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { AstrocodeConfig } from "../config/schema";
|
|
2
|
+
import type { SqliteDb } from "../state/db";
|
|
3
|
+
import type { RunRow, StageRunRow, StoryRow } from "../state/types";
|
|
4
|
+
export declare function getRun(db: SqliteDb, runId: string): RunRow | null;
|
|
5
|
+
export declare function getStory(db: SqliteDb, storyKey: string): StoryRow | null;
|
|
6
|
+
export declare function listStageRuns(db: SqliteDb, runId: string): StageRunRow[];
|
|
7
|
+
/**
|
|
8
|
+
* Check if a context snapshot is stale by comparing DB timestamps
|
|
9
|
+
*/
|
|
10
|
+
export declare function isContextSnapshotStale(snapshotText: string, db: SqliteDb, maxAgeSeconds?: number): boolean;
|
|
11
|
+
/**
|
|
12
|
+
* Add staleness indicator to context snapshot if needed
|
|
13
|
+
*/
|
|
14
|
+
export declare function addStalenessIndicator(snapshotText: string, db: SqliteDb, maxAgeSeconds?: number): string;
|
|
15
|
+
export declare function buildContextSnapshot(opts: {
|
|
16
|
+
db: SqliteDb;
|
|
17
|
+
config: AstrocodeConfig;
|
|
18
|
+
run_id: string;
|
|
19
|
+
next_action?: string;
|
|
20
|
+
}): string;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { clampLines } from "../shared/text";
|
|
2
|
+
export function getRun(db, runId) {
|
|
3
|
+
const row = db.prepare("SELECT * FROM runs WHERE run_id = ?").get(runId);
|
|
4
|
+
return row ?? null;
|
|
5
|
+
}
|
|
6
|
+
export function getStory(db, storyKey) {
|
|
7
|
+
const row = db.prepare("SELECT * FROM stories WHERE story_key = ?").get(storyKey);
|
|
8
|
+
return row ?? null;
|
|
9
|
+
}
|
|
10
|
+
export function listStageRuns(db, runId) {
|
|
11
|
+
return db
|
|
12
|
+
.prepare("SELECT * FROM stage_runs WHERE run_id = ? ORDER BY stage_index ASC")
|
|
13
|
+
.all(runId);
|
|
14
|
+
}
|
|
15
|
+
function statusIcon(status) {
|
|
16
|
+
switch (status) {
|
|
17
|
+
case "completed":
|
|
18
|
+
return "✅";
|
|
19
|
+
case "running":
|
|
20
|
+
return "🟦";
|
|
21
|
+
case "failed":
|
|
22
|
+
return "⛔";
|
|
23
|
+
case "skipped":
|
|
24
|
+
return "⏭️";
|
|
25
|
+
case "pending":
|
|
26
|
+
default:
|
|
27
|
+
return "⬜";
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Check if a context snapshot is stale by comparing DB timestamps
|
|
32
|
+
*/
|
|
33
|
+
export function isContextSnapshotStale(snapshotText, db, maxAgeSeconds = 300) {
|
|
34
|
+
// Extract run_id from snapshot
|
|
35
|
+
const runIdMatch = snapshotText.match(/Run: `([^`]+)`/);
|
|
36
|
+
if (!runIdMatch)
|
|
37
|
+
return true; // Can't validate without run_id
|
|
38
|
+
const runId = runIdMatch[1];
|
|
39
|
+
// Extract snapshot's claimed updated_at
|
|
40
|
+
const snapshotUpdatedMatch = snapshotText.match(/updated: ([^\)]+)\)/);
|
|
41
|
+
if (!snapshotUpdatedMatch)
|
|
42
|
+
return true; // Fallback to age-based check
|
|
43
|
+
try {
|
|
44
|
+
const snapshotUpdatedAt = snapshotUpdatedMatch[1];
|
|
45
|
+
const currentRun = db.prepare("SELECT updated_at FROM runs WHERE run_id = ?").get(runId);
|
|
46
|
+
if (!currentRun?.updated_at)
|
|
47
|
+
return true; // Run doesn't exist
|
|
48
|
+
// Compare timestamps - if DB is newer than snapshot claims, snapshot is stale
|
|
49
|
+
const snapshotTime = new Date(snapshotUpdatedAt).getTime();
|
|
50
|
+
const currentTime = new Date(currentRun.updated_at).getTime();
|
|
51
|
+
return currentTime > snapshotTime;
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
// Fallback to age-based staleness if parsing fails
|
|
55
|
+
const timestampMatch = snapshotText.match(/generated: (\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z)/);
|
|
56
|
+
if (!timestampMatch)
|
|
57
|
+
return false;
|
|
58
|
+
const generatedAt = new Date(timestampMatch[1]);
|
|
59
|
+
const now = new Date();
|
|
60
|
+
const ageSeconds = (now.getTime() - generatedAt.getTime()) / 1000;
|
|
61
|
+
return ageSeconds > maxAgeSeconds;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Add staleness indicator to context snapshot if needed
|
|
66
|
+
*/
|
|
67
|
+
export function addStalenessIndicator(snapshotText, db, maxAgeSeconds = 300) {
|
|
68
|
+
if (isContextSnapshotStale(snapshotText, db, maxAgeSeconds)) {
|
|
69
|
+
return snapshotText.replace(/# Astrocode Context \(generated: ([^\)]+)\)/, "# Astrocode Context (generated: $1) ⚠️ STALE - DB state has changed");
|
|
70
|
+
}
|
|
71
|
+
return snapshotText;
|
|
72
|
+
}
|
|
73
|
+
export function buildContextSnapshot(opts) {
|
|
74
|
+
const { db, config, run_id, next_action } = opts;
|
|
75
|
+
const run = getRun(db, run_id);
|
|
76
|
+
if (!run)
|
|
77
|
+
return `Run not found: ${run_id}`;
|
|
78
|
+
const story = getStory(db, run.story_key);
|
|
79
|
+
const stageRuns = listStageRuns(db, run_id);
|
|
80
|
+
const lines = [];
|
|
81
|
+
// Add timestamps for staleness checking
|
|
82
|
+
const now = new Date();
|
|
83
|
+
const timestamp = now.toISOString();
|
|
84
|
+
lines.push(`# Astrocode Context (generated: ${timestamp})`);
|
|
85
|
+
lines.push(`- Run: \`${run.run_id}\` Status: **${run.status}** (updated: ${run.updated_at})`);
|
|
86
|
+
if (run.current_stage_key)
|
|
87
|
+
lines.push(`- Current stage: \`${run.current_stage_key}\``);
|
|
88
|
+
if (next_action)
|
|
89
|
+
lines.push(`- Next action: ${next_action}`);
|
|
90
|
+
if (story) {
|
|
91
|
+
lines.push(`- Story: \`${story.story_key}\` — ${story.title}`);
|
|
92
|
+
if (story.body_md?.trim()) {
|
|
93
|
+
const first = story.body_md.trim().split(/\n\n+/)[0]?.trim();
|
|
94
|
+
if (first)
|
|
95
|
+
lines.push(`- Story summary: ${first}`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
lines.push(``, `## Pipeline`);
|
|
99
|
+
for (const s of stageRuns) {
|
|
100
|
+
lines.push(`- ${statusIcon(s.status)} \`${s.stage_key}\` (${s.status})`);
|
|
101
|
+
}
|
|
102
|
+
const completed = stageRuns.filter((s) => s.status === "completed" && (s.summary_md ?? "").trim());
|
|
103
|
+
const lastSummaries = completed.slice(-config.context_compaction.snapshot_after_stage_count);
|
|
104
|
+
if (lastSummaries.length) {
|
|
105
|
+
lines.push(``, `## Recent stage summaries`);
|
|
106
|
+
for (const s of lastSummaries) {
|
|
107
|
+
lines.push(``, `### ${s.stage_key}`);
|
|
108
|
+
lines.push((s.summary_md ?? "").trim());
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
const out = lines.join("\n").trim();
|
|
112
|
+
return clampLines(out, config.context_compaction.snapshot_max_lines);
|
|
113
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { AstrocodeConfig } from "../config/schema";
|
|
2
|
+
import type { StageKey } from "../state/types";
|
|
3
|
+
export type DirectiveKind = "continue" | "stage" | "blocked" | "repair";
|
|
4
|
+
export type BuiltDirective = {
|
|
5
|
+
kind: DirectiveKind;
|
|
6
|
+
title: string;
|
|
7
|
+
body: string;
|
|
8
|
+
hash: string;
|
|
9
|
+
};
|
|
10
|
+
export declare function directiveHash(body: string): string;
|
|
11
|
+
export declare function buildContinueDirective(opts: {
|
|
12
|
+
config: AstrocodeConfig;
|
|
13
|
+
run_id: string;
|
|
14
|
+
stage_key: string | null;
|
|
15
|
+
next_action: string;
|
|
16
|
+
context_snapshot_md: string;
|
|
17
|
+
}): BuiltDirective;
|
|
18
|
+
export declare function buildBlockedDirective(opts: {
|
|
19
|
+
config?: AstrocodeConfig;
|
|
20
|
+
run_id: string;
|
|
21
|
+
stage_key: string;
|
|
22
|
+
question: string;
|
|
23
|
+
context_snapshot_md: string;
|
|
24
|
+
}): BuiltDirective;
|
|
25
|
+
export declare function buildRepairDirective(opts: {
|
|
26
|
+
config?: AstrocodeConfig;
|
|
27
|
+
report_md: string;
|
|
28
|
+
}): BuiltDirective;
|
|
29
|
+
export declare function buildStageDirective(opts: {
|
|
30
|
+
config: AstrocodeConfig;
|
|
31
|
+
stage_key: StageKey;
|
|
32
|
+
run_id: string;
|
|
33
|
+
story_key: string;
|
|
34
|
+
story_title: string;
|
|
35
|
+
stage_agent_name: string;
|
|
36
|
+
stage_goal: string;
|
|
37
|
+
stage_constraints: string[];
|
|
38
|
+
context_snapshot_md: string;
|
|
39
|
+
}): BuiltDirective;
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { sha256Hex } from "../shared/hash";
|
|
2
|
+
import { clampChars, normalizeNewlines } from "../shared/text";
|
|
3
|
+
function getInjectMaxChars(config) {
|
|
4
|
+
// Deterministic fallback for older configs.
|
|
5
|
+
const v = config?.context_compaction?.inject_max_chars;
|
|
6
|
+
return typeof v === "number" && Number.isFinite(v) && v > 0 ? v : 12000;
|
|
7
|
+
}
|
|
8
|
+
export function directiveHash(body) {
|
|
9
|
+
// Stable hash to dedupe: normalize newlines + trim
|
|
10
|
+
const norm = normalizeNewlines(body).trim();
|
|
11
|
+
return sha256Hex(norm);
|
|
12
|
+
}
|
|
13
|
+
function finalizeBody(body, maxChars) {
|
|
14
|
+
// Normalize first, clamp second, trim last => hash/body match exactly.
|
|
15
|
+
const norm = normalizeNewlines(body);
|
|
16
|
+
const clamped = clampChars(norm, maxChars);
|
|
17
|
+
return clamped.trim();
|
|
18
|
+
}
|
|
19
|
+
export function buildContinueDirective(opts) {
|
|
20
|
+
const { config, run_id, stage_key, next_action, context_snapshot_md } = opts;
|
|
21
|
+
const maxChars = getInjectMaxChars(config);
|
|
22
|
+
const body = finalizeBody([
|
|
23
|
+
`[SYSTEM DIRECTIVE: ASTROCODE — CONTINUE]`,
|
|
24
|
+
``,
|
|
25
|
+
`This directive is injected by the Astro agent to continue the workflow.`,
|
|
26
|
+
``,
|
|
27
|
+
`Run: \`${run_id}\`${stage_key ? ` Stage: \`${stage_key}\`` : ""}`,
|
|
28
|
+
``,
|
|
29
|
+
`Next action: ${next_action}`,
|
|
30
|
+
``,
|
|
31
|
+
`Rules:`,
|
|
32
|
+
`- Do not stop early. Keep going until the run is completed, failed, or blocked.`,
|
|
33
|
+
`- Prefer tools over prose.`,
|
|
34
|
+
`- If blocked, ask exactly ONE question and stop.`,
|
|
35
|
+
``,
|
|
36
|
+
`Context snapshot:`,
|
|
37
|
+
(context_snapshot_md ?? "").trim(),
|
|
38
|
+
].join("\n"), maxChars);
|
|
39
|
+
return {
|
|
40
|
+
kind: "continue",
|
|
41
|
+
title: "ASTROCODE — CONTINUE",
|
|
42
|
+
body,
|
|
43
|
+
hash: directiveHash(body),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
export function buildBlockedDirective(opts) {
|
|
47
|
+
const { config, run_id, stage_key, question, context_snapshot_md } = opts;
|
|
48
|
+
const maxChars = getInjectMaxChars(config);
|
|
49
|
+
const body = finalizeBody([
|
|
50
|
+
`[SYSTEM DIRECTIVE: ASTROCODE — BLOCKED]`,
|
|
51
|
+
``,
|
|
52
|
+
`This directive is injected by the Astro agent indicating the workflow is blocked.`,
|
|
53
|
+
``,
|
|
54
|
+
`Run: \`${run_id}\` Stage: \`${stage_key}\``,
|
|
55
|
+
``,
|
|
56
|
+
`You are blocked. Ask the user exactly ONE question (below), then stop.`,
|
|
57
|
+
``,
|
|
58
|
+
`Question: ${question}`,
|
|
59
|
+
``,
|
|
60
|
+
`Context snapshot:`,
|
|
61
|
+
(context_snapshot_md ?? "").trim(),
|
|
62
|
+
].join("\n"), maxChars);
|
|
63
|
+
return {
|
|
64
|
+
kind: "blocked",
|
|
65
|
+
title: "ASTROCODE — BLOCKED",
|
|
66
|
+
body,
|
|
67
|
+
hash: directiveHash(body),
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
export function buildRepairDirective(opts) {
|
|
71
|
+
const maxChars = getInjectMaxChars(opts.config);
|
|
72
|
+
const body = finalizeBody([
|
|
73
|
+
`[SYSTEM DIRECTIVE: ASTROCODE — REPAIR]`,
|
|
74
|
+
``,
|
|
75
|
+
`This directive is injected by the Astro agent after performing a repair pass.`,
|
|
76
|
+
``,
|
|
77
|
+
`Astrocode performed a repair pass. Summarize in <= 10 lines and continue.`,
|
|
78
|
+
``,
|
|
79
|
+
`Repair report:`,
|
|
80
|
+
(opts.report_md ?? "").trim(),
|
|
81
|
+
].join("\n"), maxChars);
|
|
82
|
+
return {
|
|
83
|
+
kind: "repair",
|
|
84
|
+
title: "ASTROCODE — REPAIR",
|
|
85
|
+
body,
|
|
86
|
+
hash: directiveHash(body),
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
export function buildStageDirective(opts) {
|
|
90
|
+
const { config, stage_key, run_id, story_key, story_title, stage_agent_name, stage_goal, stage_constraints, context_snapshot_md, } = opts;
|
|
91
|
+
const maxChars = getInjectMaxChars(config);
|
|
92
|
+
const stageKeyUpper = String(stage_key).toUpperCase();
|
|
93
|
+
const constraintsBlock = Array.isArray(stage_constraints) && stage_constraints.length
|
|
94
|
+
? ["", "Constraints:", ...stage_constraints.map((c) => `- ${c}`)].join("\n")
|
|
95
|
+
: "";
|
|
96
|
+
const body = finalizeBody([
|
|
97
|
+
`[SYSTEM DIRECTIVE: ASTROCODE — STAGE_${stageKeyUpper}]`,
|
|
98
|
+
``,
|
|
99
|
+
`This directive is injected by the Astro agent to delegate the stage task.`,
|
|
100
|
+
``,
|
|
101
|
+
`You are: \`${stage_agent_name}\``,
|
|
102
|
+
`Run: \`${run_id}\``,
|
|
103
|
+
`Story: \`${story_key}\` — ${story_title}`,
|
|
104
|
+
``,
|
|
105
|
+
`Stage goal: ${stage_goal}`,
|
|
106
|
+
constraintsBlock,
|
|
107
|
+
``,
|
|
108
|
+
`Output contract (strict):`,
|
|
109
|
+
`1) Baton markdown (short, structured)`,
|
|
110
|
+
`2) ASTRO JSON between markers:`,
|
|
111
|
+
` <!-- ASTRO_JSON_BEGIN -->`,
|
|
112
|
+
` {`,
|
|
113
|
+
` "schema_version": 1,`,
|
|
114
|
+
` "stage_key": "${stage_key}",`,
|
|
115
|
+
` "status": "ok",`,
|
|
116
|
+
` "...": "..."`,
|
|
117
|
+
` }`,
|
|
118
|
+
` <!-- ASTRO_JSON_END -->`,
|
|
119
|
+
``,
|
|
120
|
+
`ASTRO JSON requirements:`,
|
|
121
|
+
`- stage_key must be "${stage_key}"`,
|
|
122
|
+
`- status must be "ok" | "blocked" | "failed"`,
|
|
123
|
+
`- include summary + next_actions`,
|
|
124
|
+
`- include files/evidence paths when relevant`,
|
|
125
|
+
``,
|
|
126
|
+
`If blocked: ask exactly ONE question and stop.`,
|
|
127
|
+
``,
|
|
128
|
+
`Context snapshot:`,
|
|
129
|
+
(context_snapshot_md ?? "").trim(),
|
|
130
|
+
].join("\n"), maxChars);
|
|
131
|
+
return {
|
|
132
|
+
kind: "stage",
|
|
133
|
+
title: `ASTROCODE — STAGE_${stageKeyUpper}`,
|
|
134
|
+
body,
|
|
135
|
+
hash: directiveHash(body),
|
|
136
|
+
};
|
|
137
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { AstrocodeConfig } from "../config/schema";
|
|
2
|
+
import type { SqliteDb } from "../state/db";
|
|
3
|
+
export type RepairReport = {
|
|
4
|
+
actions: string[];
|
|
5
|
+
warnings: string[];
|
|
6
|
+
};
|
|
7
|
+
export declare function repairState(db: SqliteDb, config: AstrocodeConfig): RepairReport;
|
|
8
|
+
export declare function formatRepairReport(report: RepairReport): string;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { nowISO } from "../shared/time";
|
|
2
|
+
import { newEventId, newStageRunId } from "../state/ids";
|
|
3
|
+
function push(report, line) {
|
|
4
|
+
report.actions.push(line);
|
|
5
|
+
}
|
|
6
|
+
function warn(report, line) {
|
|
7
|
+
report.warnings.push(line);
|
|
8
|
+
}
|
|
9
|
+
export function repairState(db, config) {
|
|
10
|
+
const report = { actions: [], warnings: [] };
|
|
11
|
+
const now = nowISO();
|
|
12
|
+
// 1) Multiple running runs -> abort extras (keep most recent started_at)
|
|
13
|
+
const running = db
|
|
14
|
+
.prepare("SELECT * FROM runs WHERE status='running' ORDER BY started_at DESC, created_at DESC")
|
|
15
|
+
.all();
|
|
16
|
+
if (running.length > 1) {
|
|
17
|
+
const keep = running[0];
|
|
18
|
+
for (const r of running.slice(1)) {
|
|
19
|
+
db.prepare("UPDATE runs SET status='aborted', error_text=?, completed_at=?, updated_at=? WHERE run_id=?").run("repair: multiple running runs", now, now, r.run_id);
|
|
20
|
+
// unlock story
|
|
21
|
+
db.prepare("UPDATE stories SET state='approved', in_progress=0, locked_by_run_id=NULL, locked_at=NULL, updated_at=? WHERE story_key=?").run(now, r.story_key);
|
|
22
|
+
db.prepare("INSERT INTO events (event_id, run_id, stage_key, type, body_json, created_at) VALUES (?, ?, NULL, 'run.aborted', ?, ?)").run(newEventId(), r.run_id, JSON.stringify({ reason: "repair: multiple running runs" }), now);
|
|
23
|
+
push(report, `Aborted extra running run ${r.run_id} (kept ${keep.run_id})`);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
// 2) Fix story locks for the active run
|
|
27
|
+
const active = db.prepare("SELECT * FROM runs WHERE status='running' ORDER BY started_at DESC LIMIT 1").get();
|
|
28
|
+
if (active) {
|
|
29
|
+
const story = db.prepare("SELECT * FROM stories WHERE story_key=?").get(active.story_key);
|
|
30
|
+
if (!story) {
|
|
31
|
+
warn(report, `Active run ${active.run_id} references missing story ${active.story_key}`);
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
if (story.locked_by_run_id !== active.run_id || story.in_progress !== 1 || story.state !== "in_progress") {
|
|
35
|
+
db.prepare("UPDATE stories SET state='in_progress', in_progress=1, locked_by_run_id=?, locked_at=COALESCE(locked_at, ?), updated_at=? WHERE story_key=?").run(active.run_id, now, now, story.story_key);
|
|
36
|
+
push(report, `Repaired story lock for ${story.story_key} to run ${active.run_id}`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
// 3) Ensure stage_runs exist for pipeline
|
|
40
|
+
const pipeline = JSON.parse(active.pipeline_stages_json ?? "[]");
|
|
41
|
+
const stageRuns = db
|
|
42
|
+
.prepare("SELECT * FROM stage_runs WHERE run_id=? ORDER BY stage_index ASC")
|
|
43
|
+
.all(active.run_id);
|
|
44
|
+
if (stageRuns.length < pipeline.length) {
|
|
45
|
+
const existingKeys = new Set(stageRuns.map((s) => s.stage_key));
|
|
46
|
+
const insert = db.prepare("INSERT INTO stage_runs (stage_run_id, run_id, stage_key, stage_index, status, created_at, updated_at) VALUES (?, ?, ?, ?, 'pending', ?, ?)");
|
|
47
|
+
pipeline.forEach((key, idx) => {
|
|
48
|
+
if (!existingKeys.has(key)) {
|
|
49
|
+
insert.run(newStageRunId(), active.run_id, key, idx, now, now);
|
|
50
|
+
push(report, `Inserted missing stage_run ${key} for run ${active.run_id}`);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
// 4) If current_stage_key missing, set to first incomplete
|
|
55
|
+
const refreshed = db
|
|
56
|
+
.prepare("SELECT * FROM stage_runs WHERE run_id=? ORDER BY stage_index ASC")
|
|
57
|
+
.all(active.run_id);
|
|
58
|
+
const cur = refreshed.find((s) => s.status !== "completed" && s.status !== "skipped");
|
|
59
|
+
if (cur && active.current_stage_key !== cur.stage_key) {
|
|
60
|
+
db.prepare("UPDATE runs SET current_stage_key=?, updated_at=? WHERE run_id=?").run(cur.stage_key, now, active.run_id);
|
|
61
|
+
push(report, `Set run.current_stage_key to ${cur.stage_key} for ${active.run_id}`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
// 5) Orphaned story locks (in_progress=1 but no running run)
|
|
65
|
+
const lockedStories = db
|
|
66
|
+
.prepare("SELECT * FROM stories WHERE in_progress=1")
|
|
67
|
+
.all();
|
|
68
|
+
for (const s of lockedStories) {
|
|
69
|
+
const run = s.locked_by_run_id
|
|
70
|
+
? db.prepare("SELECT * FROM runs WHERE run_id=?").get(s.locked_by_run_id)
|
|
71
|
+
: undefined;
|
|
72
|
+
if (!run || run.status !== "running") {
|
|
73
|
+
db.prepare("UPDATE stories SET in_progress=0, locked_by_run_id=NULL, locked_at=NULL, updated_at=? WHERE story_key=?").run(now, s.story_key);
|
|
74
|
+
// Keep state conservative: if it was in_progress but no run, revert to approved.
|
|
75
|
+
db.prepare("UPDATE stories SET state='approved', updated_at=? WHERE story_key=? AND state='in_progress'").run(now, s.story_key);
|
|
76
|
+
push(report, `Cleared orphaned lock on story ${s.story_key}`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
if (!report.actions.length && !report.warnings.length) {
|
|
80
|
+
report.actions.push("No repairs needed.");
|
|
81
|
+
}
|
|
82
|
+
// Event marker
|
|
83
|
+
db.prepare("INSERT INTO events (event_id, run_id, stage_key, type, body_json, created_at) VALUES (?, NULL, NULL, 'repair.completed', ?, ?)").run(newEventId(), JSON.stringify({ actions: report.actions, warnings: report.warnings, schema: config.db.schema_version_required }), now);
|
|
84
|
+
return report;
|
|
85
|
+
}
|
|
86
|
+
export function formatRepairReport(report) {
|
|
87
|
+
const lines = [];
|
|
88
|
+
lines.push("# Astrocode repair report");
|
|
89
|
+
lines.push("");
|
|
90
|
+
lines.push("## Actions");
|
|
91
|
+
for (const a of report.actions)
|
|
92
|
+
lines.push(`- ${a}`);
|
|
93
|
+
if (report.warnings.length) {
|
|
94
|
+
lines.push("", "## Warnings");
|
|
95
|
+
for (const w of report.warnings)
|
|
96
|
+
lines.push(`- ${w}`);
|
|
97
|
+
}
|
|
98
|
+
return lines.join("\n").trim();
|
|
99
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import type { AstrocodeConfig } from "../config/schema";
|
|
2
|
+
import type { SqliteDb } from "../state/db";
|
|
3
|
+
import type { RunRow, StageKey, StageRunRow, StoryRow } from "../state/types";
|
|
4
|
+
export declare const EVENT_TYPES: {
|
|
5
|
+
readonly RUN_STARTED: "run.started";
|
|
6
|
+
readonly RUN_COMPLETED: "run.completed";
|
|
7
|
+
readonly RUN_FAILED: "run.failed";
|
|
8
|
+
readonly RUN_ABORTED: "run.aborted";
|
|
9
|
+
readonly RUN_GENESIS_PLANNING_ATTACHED: "run.genesis_planning_attached";
|
|
10
|
+
readonly STAGE_STARTED: "stage.started";
|
|
11
|
+
readonly WORKFLOW_PROCEED: "workflow.proceed";
|
|
12
|
+
};
|
|
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
|
+
};
|
|
29
|
+
export type UiEmit = (e: UiEmitEvent) => void;
|
|
30
|
+
/**
|
|
31
|
+
* PLANNING-FIRST REDESIGN
|
|
32
|
+
* ----------------------
|
|
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.
|
|
46
|
+
*/
|
|
47
|
+
export type NextAction = {
|
|
48
|
+
kind: "idle";
|
|
49
|
+
reason: "no_approved_stories";
|
|
50
|
+
} | {
|
|
51
|
+
kind: "start_run";
|
|
52
|
+
story_key: string;
|
|
53
|
+
} | {
|
|
54
|
+
kind: "delegate_stage";
|
|
55
|
+
run_id: string;
|
|
56
|
+
stage_key: StageKey;
|
|
57
|
+
stage_run_id: string;
|
|
58
|
+
} | {
|
|
59
|
+
kind: "await_stage_completion";
|
|
60
|
+
run_id: string;
|
|
61
|
+
stage_key: StageKey;
|
|
62
|
+
stage_run_id: string;
|
|
63
|
+
} | {
|
|
64
|
+
kind: "complete_run";
|
|
65
|
+
run_id: string;
|
|
66
|
+
} | {
|
|
67
|
+
kind: "failed";
|
|
68
|
+
run_id: string;
|
|
69
|
+
stage_key: StageKey;
|
|
70
|
+
error_text: string;
|
|
71
|
+
};
|
|
72
|
+
export declare function getActiveRun(db: SqliteDb): RunRow | null;
|
|
73
|
+
export declare function getStory(db: SqliteDb, storyKey: string): StoryRow | null;
|
|
74
|
+
export declare function getStageRuns(db: SqliteDb, runId: string): StageRunRow[];
|
|
75
|
+
export declare function getCurrentStageRun(stageRuns: StageRunRow[]): StageRunRow | null;
|
|
76
|
+
export declare function decideNextAction(db: SqliteDb, config: AstrocodeConfig): NextAction;
|
|
77
|
+
export declare function createRunForStory(db: SqliteDb, config: AstrocodeConfig, storyKey: string): {
|
|
78
|
+
run_id: string;
|
|
79
|
+
};
|
|
80
|
+
export declare function startStage(db: SqliteDb, runId: string, stageKey: StageKey, meta?: {
|
|
81
|
+
subagent_type?: string;
|
|
82
|
+
subagent_session_id?: string;
|
|
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
|
+
export declare function abortRun(db: SqliteDb, runId: string, reason: string): void;
|