astrocode-workflow 0.4.4 → 0.4.6
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/src/hooks/continuation-enforcer.d.ts +1 -9
- package/dist/src/hooks/continuation-enforcer.js +12 -2
- package/dist/src/hooks/inject-provider.d.ts +1 -9
- package/dist/src/hooks/inject-provider.js +14 -5
- package/dist/src/state/types.d.ts +9 -0
- package/dist/src/tools/artifacts.d.ts +4 -4
- package/dist/src/tools/artifacts.js +12 -3
- package/dist/src/tools/health.d.ts +2 -2
- package/dist/src/tools/health.js +18 -11
- package/dist/src/tools/index.js +27 -34
- package/dist/src/tools/injects.d.ts +8 -8
- package/dist/src/tools/injects.js +24 -6
- package/dist/src/tools/metrics.d.ts +6 -5
- package/dist/src/tools/repair.d.ts +2 -2
- package/dist/src/tools/repair.js +5 -1
- package/dist/src/tools/reset.d.ts +2 -2
- package/dist/src/tools/reset.js +9 -1
- package/dist/src/tools/run.d.ts +3 -3
- package/dist/src/tools/run.js +8 -2
- package/dist/src/tools/spec.d.ts +2 -2
- package/dist/src/tools/spec.js +3 -2
- package/dist/src/tools/stage.d.ts +5 -5
- package/dist/src/tools/stage.js +16 -4
- package/dist/src/tools/status.d.ts +2 -2
- package/dist/src/tools/status.js +25 -2
- package/dist/src/tools/story.d.ts +5 -5
- package/dist/src/tools/story.js +16 -4
- package/dist/src/tools/workflow.d.ts +2 -2
- package/dist/src/tools/workflow.js +5 -1
- package/package.json +1 -1
- package/src/hooks/continuation-enforcer.ts +11 -9
- package/src/hooks/inject-provider.ts +16 -12
- package/src/state/types.ts +11 -0
- package/src/tools/artifacts.ts +16 -7
- package/src/tools/health.ts +22 -13
- package/src/tools/index.ts +32 -40
- package/src/tools/injects.ts +32 -14
- package/src/tools/metrics.ts +3 -6
- package/src/tools/repair.ts +8 -4
- package/src/tools/reset.ts +11 -3
- package/src/tools/run.ts +11 -5
- package/src/tools/spec.ts +5 -4
- package/src/tools/stage.ts +22 -10
- package/src/tools/status.ts +28 -5
- package/src/tools/story.ts +21 -9
- package/src/tools/workflow.ts +8 -3
package/dist/src/tools/reset.js
CHANGED
|
@@ -3,7 +3,7 @@ import { tool } from "@opencode-ai/plugin/tool";
|
|
|
3
3
|
import fs from "node:fs";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
export function createAstroResetTool(opts) {
|
|
6
|
-
const { ctx, config,
|
|
6
|
+
const { ctx, config, runtime } = opts;
|
|
7
7
|
return tool({
|
|
8
8
|
description: "Reset Astrocode database: safely delete all DB files and WAL/SHM after killing concurrent processes.",
|
|
9
9
|
args: {
|
|
@@ -23,6 +23,14 @@ export function createAstroResetTool(opts) {
|
|
|
23
23
|
"To confirm: astro_reset(confirm=\"RESET\")",
|
|
24
24
|
].join("\n");
|
|
25
25
|
}
|
|
26
|
+
// Close DB connection if open
|
|
27
|
+
if (runtime.db) {
|
|
28
|
+
try {
|
|
29
|
+
runtime.db.close();
|
|
30
|
+
}
|
|
31
|
+
catch { /* ignore */ }
|
|
32
|
+
runtime.db = null;
|
|
33
|
+
}
|
|
26
34
|
const repoRoot = ctx.directory || process.cwd();
|
|
27
35
|
const dbPath = config.db?.path || ".astro/astro.db";
|
|
28
36
|
const fullDbPath = path.resolve(repoRoot, dbPath);
|
package/dist/src/tools/run.d.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { type ToolDefinition } from "@opencode-ai/plugin/tool";
|
|
2
2
|
import type { AstrocodeConfig } from "../config/schema";
|
|
3
|
-
import type {
|
|
3
|
+
import type { RuntimeState } from "../state/types";
|
|
4
4
|
export declare function createAstroRunGetTool(opts: {
|
|
5
5
|
ctx: any;
|
|
6
6
|
config: AstrocodeConfig;
|
|
7
|
-
|
|
7
|
+
runtime: RuntimeState;
|
|
8
8
|
}): ToolDefinition;
|
|
9
9
|
export declare function createAstroRunAbortTool(opts: {
|
|
10
10
|
ctx: any;
|
|
11
11
|
config: AstrocodeConfig;
|
|
12
|
-
|
|
12
|
+
runtime: RuntimeState;
|
|
13
13
|
}): ToolDefinition;
|
package/dist/src/tools/run.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { tool } from "@opencode-ai/plugin/tool";
|
|
2
2
|
import { abortRun, getActiveRun, getStageRuns, getStory } from "../workflow/state-machine";
|
|
3
3
|
export function createAstroRunGetTool(opts) {
|
|
4
|
-
const {
|
|
4
|
+
const { runtime } = opts;
|
|
5
5
|
return tool({
|
|
6
6
|
description: "Get run details (and stage run statuses). Defaults to active run if run_id omitted.",
|
|
7
7
|
args: {
|
|
@@ -9,6 +9,9 @@ export function createAstroRunGetTool(opts) {
|
|
|
9
9
|
include_stage_summaries: tool.schema.boolean().default(false),
|
|
10
10
|
},
|
|
11
11
|
execute: async ({ run_id, include_stage_summaries }) => {
|
|
12
|
+
const { db } = runtime;
|
|
13
|
+
if (!db)
|
|
14
|
+
return "⚠️ Astrocode not initialized. Run **astro_init** first.";
|
|
12
15
|
const active = getActiveRun(db);
|
|
13
16
|
const rid = run_id ?? active?.run_id;
|
|
14
17
|
if (!rid)
|
|
@@ -35,7 +38,7 @@ export function createAstroRunGetTool(opts) {
|
|
|
35
38
|
});
|
|
36
39
|
}
|
|
37
40
|
export function createAstroRunAbortTool(opts) {
|
|
38
|
-
const {
|
|
41
|
+
const { runtime } = opts;
|
|
39
42
|
return tool({
|
|
40
43
|
description: "Abort a run and unlock its story (returns story to approved). Defaults to active run if run_id omitted.",
|
|
41
44
|
args: {
|
|
@@ -43,6 +46,9 @@ export function createAstroRunAbortTool(opts) {
|
|
|
43
46
|
reason: tool.schema.string().default("aborted by user"),
|
|
44
47
|
},
|
|
45
48
|
execute: async ({ run_id, reason }) => {
|
|
49
|
+
const { db } = runtime;
|
|
50
|
+
if (!db)
|
|
51
|
+
return "⚠️ Astrocode not initialized. Run **astro_init** first.";
|
|
46
52
|
const active = getActiveRun(db);
|
|
47
53
|
const rid = run_id ?? active?.run_id;
|
|
48
54
|
if (!rid)
|
package/dist/src/tools/spec.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type ToolDefinition } from "@opencode-ai/plugin/tool";
|
|
2
2
|
import type { AstrocodeConfig } from "../config/schema";
|
|
3
|
-
import type {
|
|
3
|
+
import type { RuntimeState } from "../state/types";
|
|
4
4
|
export declare function createAstroSpecGetTool(opts: {
|
|
5
5
|
ctx: any;
|
|
6
6
|
config: AstrocodeConfig;
|
|
@@ -8,5 +8,5 @@ export declare function createAstroSpecGetTool(opts: {
|
|
|
8
8
|
export declare function createAstroSpecSetTool(opts: {
|
|
9
9
|
ctx: any;
|
|
10
10
|
config: AstrocodeConfig;
|
|
11
|
-
|
|
11
|
+
runtime: RuntimeState;
|
|
12
12
|
}): ToolDefinition;
|
package/dist/src/tools/spec.js
CHANGED
|
@@ -21,15 +21,16 @@ export function createAstroSpecGetTool(opts) {
|
|
|
21
21
|
});
|
|
22
22
|
}
|
|
23
23
|
export function createAstroSpecSetTool(opts) {
|
|
24
|
-
const { ctx, config,
|
|
24
|
+
const { ctx, config, runtime } = opts;
|
|
25
25
|
return tool({
|
|
26
26
|
description: "Set/replace the project spec at .astro/spec.md and record its hash in the DB.",
|
|
27
27
|
args: {
|
|
28
28
|
spec_md: tool.schema.string().min(1),
|
|
29
29
|
},
|
|
30
30
|
execute: async ({ spec_md }) => {
|
|
31
|
+
const { db } = runtime;
|
|
31
32
|
if (!db) {
|
|
32
|
-
return "❌ Database not available. Cannot track spec hash. Astrocode is running in limited mode.";
|
|
33
|
+
return "❌ Database not available. Cannot track spec hash. Astrocode is running in limited mode. Run **astro_init** first.";
|
|
33
34
|
}
|
|
34
35
|
const repoRoot = ctx.directory;
|
|
35
36
|
const paths = getAstroPaths(repoRoot, config.db.path);
|
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
import { type ToolDefinition } from "@opencode-ai/plugin/tool";
|
|
2
2
|
import type { AstrocodeConfig } from "../config/schema";
|
|
3
|
-
import type {
|
|
3
|
+
import type { RuntimeState } from "../state/types";
|
|
4
4
|
export declare function createAstroStageStartTool(opts: {
|
|
5
5
|
ctx: any;
|
|
6
6
|
config: AstrocodeConfig;
|
|
7
|
-
|
|
7
|
+
runtime: RuntimeState;
|
|
8
8
|
}): ToolDefinition;
|
|
9
9
|
export declare function createAstroStageCompleteTool(opts: {
|
|
10
10
|
ctx: any;
|
|
11
11
|
config: AstrocodeConfig;
|
|
12
|
-
|
|
12
|
+
runtime: RuntimeState;
|
|
13
13
|
}): ToolDefinition;
|
|
14
14
|
export declare function createAstroStageFailTool(opts: {
|
|
15
15
|
ctx: any;
|
|
16
16
|
config: AstrocodeConfig;
|
|
17
|
-
|
|
17
|
+
runtime: RuntimeState;
|
|
18
18
|
}): ToolDefinition;
|
|
19
19
|
export declare function createAstroStageResetTool(opts: {
|
|
20
20
|
ctx: any;
|
|
21
21
|
config: AstrocodeConfig;
|
|
22
|
-
|
|
22
|
+
runtime: RuntimeState;
|
|
23
23
|
}): ToolDefinition;
|
package/dist/src/tools/stage.js
CHANGED
|
@@ -54,7 +54,7 @@ function splitTasksIntoStories(db, tasks, run, now, newStoryKeys, relationReason
|
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
56
|
export function createAstroStageStartTool(opts) {
|
|
57
|
-
const {
|
|
57
|
+
const { runtime } = opts;
|
|
58
58
|
return tool({
|
|
59
59
|
description: "Start a stage for a run (sets stage_run.status=running). Usually called by astro_workflow_proceed.",
|
|
60
60
|
args: {
|
|
@@ -64,6 +64,9 @@ export function createAstroStageStartTool(opts) {
|
|
|
64
64
|
subagent_session_id: tool.schema.string().optional(),
|
|
65
65
|
},
|
|
66
66
|
execute: async ({ run_id, stage_key, subagent_type, subagent_session_id }) => {
|
|
67
|
+
const { db } = runtime;
|
|
68
|
+
if (!db)
|
|
69
|
+
return "⚠️ Astrocode not initialized. Run **astro_init** first.";
|
|
67
70
|
const active = getActiveRun(db);
|
|
68
71
|
const rid = run_id ?? active?.run_id;
|
|
69
72
|
if (!rid)
|
|
@@ -80,7 +83,7 @@ export function createAstroStageStartTool(opts) {
|
|
|
80
83
|
});
|
|
81
84
|
}
|
|
82
85
|
export function createAstroStageCompleteTool(opts) {
|
|
83
|
-
const { ctx, config,
|
|
86
|
+
const { ctx, config, runtime } = opts;
|
|
84
87
|
return tool({
|
|
85
88
|
description: "Complete a stage from stage-agent output text. Writes baton artifacts, updates stage_runs, advances pipeline, and can auto-queue split stories.",
|
|
86
89
|
args: {
|
|
@@ -92,6 +95,9 @@ export function createAstroStageCompleteTool(opts) {
|
|
|
92
95
|
relation_reason: tool.schema.string().default("split from stage output"),
|
|
93
96
|
},
|
|
94
97
|
execute: async ({ run_id, stage_key, output_text, allow_new_stories, relation_reason }) => {
|
|
98
|
+
const { db } = runtime;
|
|
99
|
+
if (!db)
|
|
100
|
+
return "⚠️ Astrocode not initialized. Run **astro_init** first.";
|
|
95
101
|
const repoRoot = ctx.directory;
|
|
96
102
|
const paths = getAstroPaths(repoRoot, config.db.path);
|
|
97
103
|
ensureAstroDirs(paths);
|
|
@@ -305,7 +311,7 @@ Ensure JSON has required fields (stage_key, status) and valid syntax.`;
|
|
|
305
311
|
});
|
|
306
312
|
}
|
|
307
313
|
export function createAstroStageFailTool(opts) {
|
|
308
|
-
const {
|
|
314
|
+
const { runtime } = opts;
|
|
309
315
|
return tool({
|
|
310
316
|
description: "Manually fail a stage and mark run failed.",
|
|
311
317
|
args: {
|
|
@@ -314,6 +320,9 @@ export function createAstroStageFailTool(opts) {
|
|
|
314
320
|
error_text: tool.schema.string().min(1),
|
|
315
321
|
},
|
|
316
322
|
execute: async ({ run_id, stage_key, error_text }) => {
|
|
323
|
+
const { db } = runtime;
|
|
324
|
+
if (!db)
|
|
325
|
+
return "⚠️ Astrocode not initialized. Run **astro_init** first.";
|
|
317
326
|
const active = getActiveRun(db);
|
|
318
327
|
const rid = run_id ?? active?.run_id;
|
|
319
328
|
if (!rid)
|
|
@@ -339,7 +348,7 @@ export function createAstroStageFailTool(opts) {
|
|
|
339
348
|
});
|
|
340
349
|
}
|
|
341
350
|
export function createAstroStageResetTool(opts) {
|
|
342
|
-
const {
|
|
351
|
+
const { runtime } = opts;
|
|
343
352
|
return tool({
|
|
344
353
|
description: "Admin: reset a stage (and later stages) back to pending for a run. Re-opens run as running. Use carefully.",
|
|
345
354
|
args: {
|
|
@@ -348,6 +357,9 @@ export function createAstroStageResetTool(opts) {
|
|
|
348
357
|
note: tool.schema.string().default("reset by user"),
|
|
349
358
|
},
|
|
350
359
|
execute: async ({ run_id, stage_key, note }) => {
|
|
360
|
+
const { db } = runtime;
|
|
361
|
+
if (!db)
|
|
362
|
+
return "⚠️ Astrocode not initialized. Run **astro_init** first.";
|
|
351
363
|
const run = db.prepare("SELECT * FROM runs WHERE run_id=?").get(run_id);
|
|
352
364
|
if (!run)
|
|
353
365
|
throw new Error(`Run not found: ${run_id}`);
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { type ToolDefinition } from "@opencode-ai/plugin/tool";
|
|
2
2
|
import type { AstrocodeConfig } from "../config/schema";
|
|
3
|
-
import type {
|
|
3
|
+
import type { RuntimeState } from "../state/types";
|
|
4
4
|
export declare function createAstroStatusTool(opts: {
|
|
5
5
|
ctx: any;
|
|
6
6
|
config: AstrocodeConfig;
|
|
7
|
-
|
|
7
|
+
runtime: RuntimeState;
|
|
8
8
|
}): ToolDefinition;
|
package/dist/src/tools/status.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { tool } from "@opencode-ai/plugin/tool";
|
|
2
2
|
import { decideNextAction, getActiveRun, getStageRuns, getStory } from "../workflow/state-machine";
|
|
3
|
+
import { openSqlite, configurePragmas, ensureSchema } from "../state/db";
|
|
4
|
+
import { getAstroPaths } from "../shared/paths";
|
|
5
|
+
import fs from "node:fs";
|
|
3
6
|
function statusIcon(status) {
|
|
4
7
|
switch (status) {
|
|
5
8
|
case "running":
|
|
@@ -31,7 +34,7 @@ function stageIcon(status) {
|
|
|
31
34
|
}
|
|
32
35
|
}
|
|
33
36
|
export function createAstroStatusTool(opts) {
|
|
34
|
-
const { ctx, config,
|
|
37
|
+
const { ctx, config, runtime } = opts;
|
|
35
38
|
return tool({
|
|
36
39
|
description: "Show a compact Astrocode status dashboard: active run/stage, pipeline, story board counts, and next action.",
|
|
37
40
|
args: {
|
|
@@ -39,17 +42,37 @@ export function createAstroStatusTool(opts) {
|
|
|
39
42
|
include_recent_events: tool.schema.boolean().default(false),
|
|
40
43
|
},
|
|
41
44
|
execute: async ({ include_board, include_recent_events }) => {
|
|
45
|
+
// Lazy initialization: if DB is missing but file exists, try to connect
|
|
46
|
+
if (!runtime.db) {
|
|
47
|
+
const repoRoot = ctx.directory || process.cwd();
|
|
48
|
+
const paths = getAstroPaths(repoRoot, config.db.path);
|
|
49
|
+
if (fs.existsSync(paths.dbPath)) {
|
|
50
|
+
try {
|
|
51
|
+
const db = openSqlite(paths.dbPath, { busyTimeoutMs: config.db.busy_timeout_ms });
|
|
52
|
+
configurePragmas(db, config.db.pragmas);
|
|
53
|
+
ensureSchema(db, { allowAutoMigrate: config.db.allow_auto_migrate, silent: true });
|
|
54
|
+
runtime.db = db;
|
|
55
|
+
runtime.limitedMode = false;
|
|
56
|
+
runtime.limitedModeReason = null;
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
// Ignore lazy init failures, will fall through to "not initialized" message
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
const { db } = runtime;
|
|
42
64
|
if (!db) {
|
|
43
65
|
return [
|
|
44
66
|
`⚠️ Astrocode not initialized.`,
|
|
45
67
|
``,
|
|
46
68
|
`- Reason: Database not available`,
|
|
47
69
|
``,
|
|
48
|
-
`Next: run **astro_init**, then
|
|
70
|
+
`Next: run **astro_init**, then run /astro-status again.`,
|
|
49
71
|
].join("\n");
|
|
50
72
|
}
|
|
51
73
|
try {
|
|
52
74
|
const active = getActiveRun(db);
|
|
75
|
+
// ... rest of existing logic ...
|
|
53
76
|
const lines = [];
|
|
54
77
|
lines.push(`# Astrocode Status`);
|
|
55
78
|
if (!active) {
|
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
import { type ToolDefinition } from "@opencode-ai/plugin/tool";
|
|
2
2
|
import type { AstrocodeConfig } from "../config/schema";
|
|
3
|
-
import type {
|
|
3
|
+
import type { RuntimeState } from "../state/types";
|
|
4
4
|
export declare function createAstroStoryQueueTool(opts: {
|
|
5
5
|
ctx: any;
|
|
6
6
|
config: AstrocodeConfig;
|
|
7
|
-
|
|
7
|
+
runtime: RuntimeState;
|
|
8
8
|
}): ToolDefinition;
|
|
9
9
|
export declare function createAstroStoryApproveTool(opts: {
|
|
10
10
|
ctx: any;
|
|
11
11
|
config: AstrocodeConfig;
|
|
12
|
-
|
|
12
|
+
runtime: RuntimeState;
|
|
13
13
|
}): ToolDefinition;
|
|
14
14
|
export declare function createAstroStoryBoardTool(opts: {
|
|
15
15
|
ctx: any;
|
|
16
16
|
config: AstrocodeConfig;
|
|
17
|
-
|
|
17
|
+
runtime: RuntimeState;
|
|
18
18
|
}): ToolDefinition;
|
|
19
19
|
export declare function createAstroStorySetStateTool(opts: {
|
|
20
20
|
ctx: any;
|
|
21
21
|
config: AstrocodeConfig;
|
|
22
|
-
|
|
22
|
+
runtime: RuntimeState;
|
|
23
23
|
}): ToolDefinition;
|
package/dist/src/tools/story.js
CHANGED
|
@@ -3,7 +3,7 @@ import { withTx } from "../state/db";
|
|
|
3
3
|
import { nowISO } from "../shared/time";
|
|
4
4
|
import { insertStory } from "../workflow/story-helpers";
|
|
5
5
|
export function createAstroStoryQueueTool(opts) {
|
|
6
|
-
const {
|
|
6
|
+
const { runtime } = opts;
|
|
7
7
|
return tool({
|
|
8
8
|
description: "Create a queued story (ticket) in Astrocode. Returns story_key.",
|
|
9
9
|
args: {
|
|
@@ -13,6 +13,9 @@ export function createAstroStoryQueueTool(opts) {
|
|
|
13
13
|
priority: tool.schema.number().int().default(0),
|
|
14
14
|
},
|
|
15
15
|
execute: async ({ title, body_md, epic_key, priority }) => {
|
|
16
|
+
const { db } = runtime;
|
|
17
|
+
if (!db)
|
|
18
|
+
return "⚠️ Astrocode not initialized. Run **astro_init** first.";
|
|
16
19
|
const story_key = withTx(db, () => {
|
|
17
20
|
const key = insertStory(db, { title, body_md, epic_key: epic_key ?? null, priority: priority ?? 0, state: 'queued' });
|
|
18
21
|
return key;
|
|
@@ -22,13 +25,16 @@ export function createAstroStoryQueueTool(opts) {
|
|
|
22
25
|
});
|
|
23
26
|
}
|
|
24
27
|
export function createAstroStoryApproveTool(opts) {
|
|
25
|
-
const {
|
|
28
|
+
const { runtime } = opts;
|
|
26
29
|
return tool({
|
|
27
30
|
description: "Approve a story so it becomes eligible to run.",
|
|
28
31
|
args: {
|
|
29
32
|
story_key: tool.schema.string().min(1),
|
|
30
33
|
},
|
|
31
34
|
execute: async ({ story_key }) => {
|
|
35
|
+
const { db } = runtime;
|
|
36
|
+
if (!db)
|
|
37
|
+
return "⚠️ Astrocode not initialized. Run **astro_init** first.";
|
|
32
38
|
const now = nowISO();
|
|
33
39
|
const row = db.prepare("SELECT story_key, state, title FROM stories WHERE story_key=?").get(story_key);
|
|
34
40
|
if (!row)
|
|
@@ -41,13 +47,16 @@ export function createAstroStoryApproveTool(opts) {
|
|
|
41
47
|
});
|
|
42
48
|
}
|
|
43
49
|
export function createAstroStoryBoardTool(opts) {
|
|
44
|
-
const {
|
|
50
|
+
const { runtime } = opts;
|
|
45
51
|
return tool({
|
|
46
52
|
description: "Show stories grouped by state (a compact board).",
|
|
47
53
|
args: {
|
|
48
54
|
limit_per_state: tool.schema.number().int().positive().default(20),
|
|
49
55
|
},
|
|
50
56
|
execute: async ({ limit_per_state }) => {
|
|
57
|
+
const { db } = runtime;
|
|
58
|
+
if (!db)
|
|
59
|
+
return "⚠️ Astrocode not initialized. Run **astro_init** first.";
|
|
51
60
|
const states = ["queued", "approved", "in_progress", "blocked", "done", "archived"];
|
|
52
61
|
const lines = [];
|
|
53
62
|
lines.push("# Story board");
|
|
@@ -64,7 +73,7 @@ export function createAstroStoryBoardTool(opts) {
|
|
|
64
73
|
});
|
|
65
74
|
}
|
|
66
75
|
export function createAstroStorySetStateTool(opts) {
|
|
67
|
-
const {
|
|
76
|
+
const { runtime } = opts;
|
|
68
77
|
return tool({
|
|
69
78
|
description: "Admin: set story state manually (queued|approved|in_progress|done|blocked|archived). Use carefully.",
|
|
70
79
|
args: {
|
|
@@ -73,6 +82,9 @@ export function createAstroStorySetStateTool(opts) {
|
|
|
73
82
|
note: tool.schema.string().default(""),
|
|
74
83
|
},
|
|
75
84
|
execute: async ({ story_key, state, note }) => {
|
|
85
|
+
const { db } = runtime;
|
|
86
|
+
if (!db)
|
|
87
|
+
return "⚠️ Astrocode not initialized. Run **astro_init** first.";
|
|
76
88
|
const now = nowISO();
|
|
77
89
|
const row = db.prepare("SELECT story_key, title, state FROM stories WHERE story_key=?").get(story_key);
|
|
78
90
|
if (!row)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type ToolDefinition } from "@opencode-ai/plugin/tool";
|
|
2
2
|
import type { AstrocodeConfig } from "../config/schema";
|
|
3
|
-
import type {
|
|
3
|
+
import type { RuntimeState } from "../state/types";
|
|
4
4
|
import type { StageKey } from "../state/types";
|
|
5
5
|
import type { AgentConfig } from "@opencode-ai/sdk";
|
|
6
6
|
export declare const STAGE_TO_AGENT_MAP: Record<string, string>;
|
|
@@ -8,6 +8,6 @@ export declare function resolveAgentName(stageKey: StageKey, config: AstrocodeCo
|
|
|
8
8
|
export declare function createAstroWorkflowProceedTool(opts: {
|
|
9
9
|
ctx: any;
|
|
10
10
|
config: AstrocodeConfig;
|
|
11
|
-
|
|
11
|
+
runtime: RuntimeState;
|
|
12
12
|
agents?: Record<string, AgentConfig>;
|
|
13
13
|
}): ToolDefinition;
|
|
@@ -143,7 +143,7 @@ function buildUiMessage(e) {
|
|
|
143
143
|
}
|
|
144
144
|
}
|
|
145
145
|
export function createAstroWorkflowProceedTool(opts) {
|
|
146
|
-
const { ctx, config,
|
|
146
|
+
const { ctx, config, runtime, agents } = opts;
|
|
147
147
|
const toasts = createToastManager({ ctx, throttleMs: config.ui.toasts.throttle_ms });
|
|
148
148
|
return tool({
|
|
149
149
|
description: "Deterministic harness: advances the DB-driven pipeline by one step (or loops bounded). Stops when LLM work is required (delegation/await).",
|
|
@@ -152,6 +152,10 @@ export function createAstroWorkflowProceedTool(opts) {
|
|
|
152
152
|
max_steps: tool.schema.number().int().positive().default(config.workflow.default_max_steps),
|
|
153
153
|
},
|
|
154
154
|
execute: async ({ mode, max_steps }) => {
|
|
155
|
+
const { db } = runtime;
|
|
156
|
+
if (!db) {
|
|
157
|
+
return "⚠️ Cannot proceed: Astrocode is not initialized. Run **astro_init** first.";
|
|
158
|
+
}
|
|
155
159
|
const sessionId = ctx.sessionID;
|
|
156
160
|
const steps = Math.min(max_steps, config.workflow.loop_max_steps_hard_cap);
|
|
157
161
|
const actions = [];
|
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { AstrocodeConfig } from "../config/schema";
|
|
2
|
-
import type {
|
|
2
|
+
import type { RuntimeState } from "../state/types";
|
|
3
3
|
import { buildContextSnapshot } from "../workflow/context";
|
|
4
4
|
import { decideNextAction, getActiveRun } from "../workflow/state-machine";
|
|
5
5
|
import { buildContinueDirective, type BuiltDirective } from "../workflow/directives";
|
|
@@ -35,19 +35,12 @@ function msFromIso(iso: string): number {
|
|
|
35
35
|
return Number.isFinite(t) ? t : 0;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
type RuntimeState = {
|
|
39
|
-
db: SqliteDb | null;
|
|
40
|
-
limitedMode: boolean;
|
|
41
|
-
limitedModeReason: null | { code: "db_init_failed"|"schema_too_old"|"schema_downgrade"|"schema_migration_failed"; details: any };
|
|
42
|
-
};
|
|
43
|
-
|
|
44
38
|
export function createContinuationEnforcer(opts: {
|
|
45
39
|
ctx: any;
|
|
46
40
|
config: AstrocodeConfig;
|
|
47
41
|
runtime: RuntimeState;
|
|
48
42
|
}) {
|
|
49
43
|
const { ctx, config, runtime } = opts;
|
|
50
|
-
const { db } = runtime;
|
|
51
44
|
|
|
52
45
|
const toasts = createToastManager({ ctx, throttleMs: config.ui.toasts.throttle_ms });
|
|
53
46
|
|
|
@@ -106,6 +99,9 @@ export function createContinuationEnforcer(opts: {
|
|
|
106
99
|
}
|
|
107
100
|
|
|
108
101
|
function shouldDedupe(sessionId: string, directive: BuiltDirective): boolean {
|
|
102
|
+
const { db } = runtime;
|
|
103
|
+
if (!db) return false;
|
|
104
|
+
|
|
109
105
|
const s = getState(sessionId);
|
|
110
106
|
const now = Date.now();
|
|
111
107
|
|
|
@@ -128,6 +124,9 @@ export function createContinuationEnforcer(opts: {
|
|
|
128
124
|
}
|
|
129
125
|
|
|
130
126
|
async function recordContinuation(sessionId: string, runId: string | null, directive: BuiltDirective, reason: string) {
|
|
127
|
+
const { db } = runtime;
|
|
128
|
+
if (!db) return;
|
|
129
|
+
|
|
131
130
|
db.prepare(
|
|
132
131
|
"INSERT INTO continuations (session_id, run_id, directive_hash, kind, reason, created_at) VALUES (?, ?, ?, ?, ?, ?)"
|
|
133
132
|
).run(sessionId, runId, directive.hash, directive.kind, reason, nowISO());
|
|
@@ -163,7 +162,8 @@ export function createContinuationEnforcer(opts: {
|
|
|
163
162
|
}
|
|
164
163
|
|
|
165
164
|
async function maybeInjectContinue(sessionId: string, reason: string) {
|
|
166
|
-
|
|
165
|
+
const { db } = runtime;
|
|
166
|
+
if (!config.continuation.enabled || !db) return;
|
|
167
167
|
|
|
168
168
|
// Require active run
|
|
169
169
|
const active = getActiveRun(db);
|
|
@@ -213,6 +213,7 @@ export function createContinuationEnforcer(opts: {
|
|
|
213
213
|
async onToolAfter(input: ToolExecuteAfterInput) {
|
|
214
214
|
const sessionId = input.sessionID ?? (ctx as any).sessionID;
|
|
215
215
|
if (!sessionId) return;
|
|
216
|
+
if (!config.continuation.enabled) return;
|
|
216
217
|
if (!config.continuation.inject_on_tool_done_if_run_active) return;
|
|
217
218
|
|
|
218
219
|
// Inject continuation immediately after any tool execution
|
|
@@ -222,6 +223,7 @@ export function createContinuationEnforcer(opts: {
|
|
|
222
223
|
},
|
|
223
224
|
|
|
224
225
|
async onChatMessage(_input: ChatMessageInput) {
|
|
226
|
+
if (!config.continuation.enabled) return;
|
|
225
227
|
if (!config.continuation.inject_on_message_done_if_run_active) return;
|
|
226
228
|
scheduleIdleInjection(_input.sessionID);
|
|
227
229
|
},
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { AstrocodeConfig } from "../config/schema";
|
|
2
|
-
import type {
|
|
2
|
+
import type { RuntimeState } from "../state/types";
|
|
3
3
|
import { selectEligibleInjects } from "../tools/injects";
|
|
4
4
|
import { injectChatPrompt } from "../ui/inject";
|
|
5
5
|
import { nowISO } from "../shared/time";
|
|
@@ -15,19 +15,12 @@ type ToolExecuteAfterInput = {
|
|
|
15
15
|
sessionID?: string;
|
|
16
16
|
};
|
|
17
17
|
|
|
18
|
-
type RuntimeState = {
|
|
19
|
-
db: SqliteDb | null;
|
|
20
|
-
limitedMode: boolean;
|
|
21
|
-
limitedModeReason: null | { code: "db_init_failed"|"schema_too_old"|"schema_downgrade"|"schema_migration_failed"; details: any };
|
|
22
|
-
};
|
|
23
|
-
|
|
24
18
|
export function createInjectProvider(opts: {
|
|
25
19
|
ctx: any;
|
|
26
20
|
config: AstrocodeConfig;
|
|
27
21
|
runtime: RuntimeState;
|
|
28
22
|
}) {
|
|
29
23
|
const { ctx, config, runtime } = opts;
|
|
30
|
-
const { db } = runtime;
|
|
31
24
|
|
|
32
25
|
// Cache to avoid re-injecting the same injects repeatedly
|
|
33
26
|
// Map of inject_id -> last injected timestamp
|
|
@@ -57,6 +50,9 @@ export function createInjectProvider(opts: {
|
|
|
57
50
|
}
|
|
58
51
|
|
|
59
52
|
function getInjectionDiagnostics(nowIso: string, scopeAllowlist: string[], typeAllowlist: string[]): any {
|
|
53
|
+
const { db } = runtime;
|
|
54
|
+
if (!db) return null;
|
|
55
|
+
|
|
60
56
|
// Get ALL injects to analyze filtering
|
|
61
57
|
const allInjects = db.prepare("SELECT * FROM injects").all() as any[];
|
|
62
58
|
|
|
@@ -107,6 +103,9 @@ export function createInjectProvider(opts: {
|
|
|
107
103
|
}
|
|
108
104
|
|
|
109
105
|
async function injectEligibleInjects(sessionId: string, context?: string) {
|
|
106
|
+
const { db } = runtime;
|
|
107
|
+
if (!db) return;
|
|
108
|
+
|
|
110
109
|
const now = nowISO();
|
|
111
110
|
const nowMs = Date.now();
|
|
112
111
|
|
|
@@ -130,7 +129,7 @@ export function createInjectProvider(opts: {
|
|
|
130
129
|
|
|
131
130
|
if (eligibleInjects.length === 0) {
|
|
132
131
|
// Log when no injects are eligible
|
|
133
|
-
if (EMIT_TELEMETRY) {
|
|
132
|
+
if (EMIT_TELEMETRY && diagnostics) {
|
|
134
133
|
// eslint-disable-next-line no-console
|
|
135
134
|
console.log(`[Astrocode:inject] ${now} context=${context ?? 'unknown'} selected=0 injected=0 skipped={expired:${diagnostics.skipped.expired} scope:${diagnostics.skipped.scope} type:${diagnostics.skipped.type} deduped:0}`);
|
|
136
135
|
}
|
|
@@ -145,7 +144,7 @@ export function createInjectProvider(opts: {
|
|
|
145
144
|
}
|
|
146
145
|
|
|
147
146
|
// Format as injection message
|
|
148
|
-
const formattedText =
|
|
147
|
+
const formattedText = `### 🔖 ASTROCODE: INJECTED NOTE\n**Title:** ${inject.title}\n\n${inject.body_md}`;
|
|
149
148
|
|
|
150
149
|
try {
|
|
151
150
|
await injectChatPrompt({
|
|
@@ -165,7 +164,7 @@ export function createInjectProvider(opts: {
|
|
|
165
164
|
}
|
|
166
165
|
|
|
167
166
|
// Log diagnostic summary
|
|
168
|
-
if (EMIT_TELEMETRY || injected > 0) {
|
|
167
|
+
if ((EMIT_TELEMETRY || injected > 0) && diagnostics) {
|
|
169
168
|
// eslint-disable-next-line no-console
|
|
170
169
|
console.log(`[Astrocode:inject] ${now} context=${context ?? 'unknown'} selected=${diagnostics.selected_eligible} injected=${injected} skipped={expired:${diagnostics.skipped.expired} scope:${diagnostics.skipped.scope} type:${diagnostics.skipped.type} deduped:${skippedDeduped}}`);
|
|
171
170
|
}
|
|
@@ -184,7 +183,8 @@ export function createInjectProvider(opts: {
|
|
|
184
183
|
|
|
185
184
|
// Auto-approve queued stories if enabled
|
|
186
185
|
async function maybeAutoApprove(sessionId: string) {
|
|
187
|
-
|
|
186
|
+
const { db } = runtime;
|
|
187
|
+
if (!config.inject?.auto_approve_queued_stories || !db) return;
|
|
188
188
|
|
|
189
189
|
try {
|
|
190
190
|
// Get all queued stories
|
|
@@ -219,6 +219,9 @@ export function createInjectProvider(opts: {
|
|
|
219
219
|
|
|
220
220
|
// Inject eligible injects before processing the user's message
|
|
221
221
|
await injectEligibleInjects(input.sessionID, 'chat_message');
|
|
222
|
+
|
|
223
|
+
// Also inject Workflow Pulse on chat messages if a run is active
|
|
224
|
+
await maybeInjectWorkflowPulse(input.sessionID);
|
|
222
225
|
},
|
|
223
226
|
|
|
224
227
|
async onToolAfter(input: ToolExecuteAfterInput) {
|
|
@@ -243,6 +246,7 @@ export function createInjectProvider(opts: {
|
|
|
243
246
|
};
|
|
244
247
|
|
|
245
248
|
async function maybeInjectWorkflowPulse(sessionId: string) {
|
|
249
|
+
const { db } = runtime;
|
|
246
250
|
if (!db) return;
|
|
247
251
|
try {
|
|
248
252
|
const active = getActiveRun(db);
|
package/src/state/types.ts
CHANGED
|
@@ -75,3 +75,14 @@ export type InjectRow = {
|
|
|
75
75
|
created_at: string;
|
|
76
76
|
updated_at: string;
|
|
77
77
|
};
|
|
78
|
+
|
|
79
|
+
import { SqliteDb } from "./db";
|
|
80
|
+
|
|
81
|
+
export type RuntimeState = {
|
|
82
|
+
db: SqliteDb | null;
|
|
83
|
+
limitedMode: boolean;
|
|
84
|
+
limitedModeReason: null | {
|
|
85
|
+
code: "db_init_failed" | "schema_too_old" | "schema_downgrade" | "schema_migration_failed";
|
|
86
|
+
details: any;
|
|
87
|
+
};
|
|
88
|
+
};
|