astrocode-workflow 0.4.1 → 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/src/astro/workflow-runner.d.ts +1 -5
- package/dist/src/astro/workflow-runner.js +6 -17
- package/dist/src/index.js +0 -6
- package/dist/src/tools/health.js +0 -31
- package/dist/src/tools/index.js +0 -3
- package/dist/src/tools/repair.js +4 -37
- package/dist/src/tools/workflow.js +178 -192
- package/package.json +1 -1
- package/src/astro/workflow-runner.ts +5 -25
- package/src/index.ts +0 -7
- package/src/tools/health.ts +0 -29
- package/src/tools/index.ts +2 -5
- package/src/tools/repair.ts +4 -38
- package/src/tools/workflow.ts +1 -17
- package/src/state/repo-lock.ts +0 -706
- package/src/state/workflow-repo-lock.ts +0 -111
- package/src/tools/lock.ts +0 -75
|
@@ -1,36 +1,16 @@
|
|
|
1
1
|
// src/astro/workflow-runner.ts
|
|
2
|
-
import { acquireRepoLock } from "../state/repo-lock";
|
|
3
|
-
import { workflowRepoLock } from "../state/workflow-repo-lock";
|
|
4
2
|
|
|
5
3
|
/**
|
|
6
|
-
*
|
|
4
|
+
* Executes the workflow loop.
|
|
7
5
|
* Everything that mutates the repo (tool calls, steps) runs inside this scope.
|
|
8
6
|
*
|
|
9
7
|
* Replace the internals with your actual astro/opencode driver loop.
|
|
10
8
|
*/
|
|
11
9
|
export async function runAstroWorkflow(opts: {
|
|
12
|
-
lockPath: string;
|
|
13
|
-
repoRoot: string;
|
|
14
|
-
sessionId: string;
|
|
15
|
-
owner?: string;
|
|
16
|
-
|
|
17
|
-
// Hook in your existing workflow engine
|
|
18
10
|
proceedOneStep: () => Promise<{ done: boolean }>;
|
|
19
11
|
}): Promise<void> {
|
|
20
|
-
|
|
21
|
-
{
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
repoRoot: opts.repoRoot,
|
|
25
|
-
sessionId: opts.sessionId,
|
|
26
|
-
owner: opts.owner,
|
|
27
|
-
fn: async () => {
|
|
28
|
-
// ✅ Lock is held ONCE for the entire run. Tool calls can "rattle through".
|
|
29
|
-
while (true) {
|
|
30
|
-
const { done } = await opts.proceedOneStep();
|
|
31
|
-
if (done) return;
|
|
32
|
-
}
|
|
33
|
-
},
|
|
34
|
-
}
|
|
35
|
-
);
|
|
12
|
+
while (true) {
|
|
13
|
+
const { done } = await opts.proceedOneStep();
|
|
14
|
+
if (done) return;
|
|
15
|
+
}
|
|
36
16
|
}
|
package/src/index.ts
CHANGED
|
@@ -59,13 +59,6 @@ const Astrocode: Plugin = async (ctx) => {
|
|
|
59
59
|
}
|
|
60
60
|
const repoRoot = ctx.directory;
|
|
61
61
|
|
|
62
|
-
// NOTE: Repo locking is handled at the workflow level via workflowRepoLock.
|
|
63
|
-
// The workflow tool correctly acquires and holds the lock for the entire workflow execution.
|
|
64
|
-
// Plugin-level locking is unnecessary and architecturally incorrect since:
|
|
65
|
-
// - The lock would be held for the entire session lifecycle (too long)
|
|
66
|
-
// - Individual tools are designed to be called within workflow context where lock is held
|
|
67
|
-
// - Workflow-level locking with refcounting prevents lock churn during tool execution
|
|
68
|
-
|
|
69
62
|
// Always load config first - this provides defaults even in limited mode
|
|
70
63
|
let pluginConfig: AstrocodeConfig;
|
|
71
64
|
try {
|
package/src/tools/health.ts
CHANGED
|
@@ -25,34 +25,6 @@ export function createAstroHealthTool(opts: { ctx: any; config: AstrocodeConfig;
|
|
|
25
25
|
lines.push(`- Repo: ${repoRoot}`);
|
|
26
26
|
lines.push(`- DB Path: ${fullDbPath}`);
|
|
27
27
|
|
|
28
|
-
// Lock status
|
|
29
|
-
const lockPath = `${repoRoot}/.astro/astro.lock`;
|
|
30
|
-
try {
|
|
31
|
-
if (fs.existsSync(lockPath)) {
|
|
32
|
-
const lockContent = fs.readFileSync(lockPath, "utf8").trim();
|
|
33
|
-
const parts = lockContent.split(" ");
|
|
34
|
-
if (parts.length >= 2) {
|
|
35
|
-
const pid = parseInt(parts[0]);
|
|
36
|
-
const startedAt = parts[1];
|
|
37
|
-
|
|
38
|
-
// Check if PID is still running
|
|
39
|
-
try {
|
|
40
|
-
(process as any).kill(pid, 0); // Signal 0 just checks if process exists
|
|
41
|
-
lines.push(`- Lock: HELD by PID ${pid} (started ${startedAt})`);
|
|
42
|
-
} catch {
|
|
43
|
-
lines.push(`- Lock: STALE (PID ${pid} not running, started ${startedAt})`);
|
|
44
|
-
lines.push(` → Run: rm "${lockPath}"`);
|
|
45
|
-
}
|
|
46
|
-
} else {
|
|
47
|
-
lines.push(`- Lock: MALFORMED (${lockContent})`);
|
|
48
|
-
}
|
|
49
|
-
} else {
|
|
50
|
-
lines.push(`- Lock: NONE (no lock file)`);
|
|
51
|
-
}
|
|
52
|
-
} catch (e) {
|
|
53
|
-
lines.push(`- Lock: ERROR (${String(e)})`);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
28
|
// DB file status
|
|
57
29
|
const dbExists = fs.existsSync(fullDbPath);
|
|
58
30
|
const walExists = fs.existsSync(`${fullDbPath}-wal`);
|
|
@@ -116,7 +88,6 @@ export function createAstroHealthTool(opts: { ctx: any; config: AstrocodeConfig;
|
|
|
116
88
|
lines.push(`## Status`);
|
|
117
89
|
lines.push(`✅ DB accessible`);
|
|
118
90
|
lines.push(`✅ Schema valid`);
|
|
119
|
-
lines.push(`✅ Lock file checked`);
|
|
120
91
|
|
|
121
92
|
if (walExists || shmExists) {
|
|
122
93
|
lines.push(`⚠️ WAL/SHM files present - indicates unclean shutdown or active transaction`);
|
package/src/tools/index.ts
CHANGED
|
@@ -15,7 +15,6 @@ import { createAstroRepairTool } from "./repair";
|
|
|
15
15
|
import { createAstroHealthTool } from "./health";
|
|
16
16
|
import { createAstroResetTool } from "./reset";
|
|
17
17
|
import { createAstroMetricsTool } from "./metrics";
|
|
18
|
-
import { createAstroLockStatusTool } from "./lock";
|
|
19
18
|
|
|
20
19
|
import { AgentConfig } from "@opencode-ai/sdk";
|
|
21
20
|
|
|
@@ -44,8 +43,7 @@ export function createAstroTools(opts: CreateAstroToolsOptions): Record<string,
|
|
|
44
43
|
tools.astro_spec_get = createAstroSpecGetTool({ ctx, config });
|
|
45
44
|
tools.astro_health = createAstroHealthTool({ ctx, config, db });
|
|
46
45
|
tools.astro_reset = createAstroResetTool({ ctx, config, db });
|
|
47
|
-
|
|
48
|
-
tools.astro_lock_status = createAstroLockStatusTool({ ctx });
|
|
46
|
+
tools.astro_metrics = createAstroMetricsTool({ ctx, config });
|
|
49
47
|
|
|
50
48
|
// Recovery tool - available even in limited mode to allow DB initialization
|
|
51
49
|
tools.astro_init = createAstroInitTool({ ctx, config, runtime });
|
|
@@ -110,8 +108,7 @@ export function createAstroTools(opts: CreateAstroToolsOptions): Record<string,
|
|
|
110
108
|
["_astro_repair", "astro_repair"],
|
|
111
109
|
["_astro_health", "astro_health"],
|
|
112
110
|
["_astro_reset", "astro_reset"],
|
|
113
|
-
|
|
114
|
-
["_astro_lock_status", "astro_lock_status"],
|
|
111
|
+
["_astro_metrics", "astro_metrics"],
|
|
115
112
|
];
|
|
116
113
|
|
|
117
114
|
// Only add aliases for tools that exist
|
package/src/tools/repair.ts
CHANGED
|
@@ -6,56 +6,22 @@ import { withTx } from "../state/db";
|
|
|
6
6
|
import { repairState, formatRepairReport } from "../workflow/repair";
|
|
7
7
|
import { putArtifact } from "../workflow/artifacts";
|
|
8
8
|
import { nowISO } from "../shared/time";
|
|
9
|
-
import { getLockStatus, tryRemoveStaleLock } from "../state/repo-lock";
|
|
10
9
|
|
|
11
10
|
|
|
12
11
|
export function createAstroRepairTool(opts: { ctx: any; config: AstrocodeConfig; db: SqliteDb }): ToolDefinition {
|
|
13
12
|
const { ctx, config, db } = opts;
|
|
14
13
|
|
|
15
14
|
return tool({
|
|
16
|
-
description: "Repair Astrocode invariants and recover from inconsistent DB state.
|
|
15
|
+
description: "Repair Astrocode invariants and recover from inconsistent DB state. Writes a repair report artifact.",
|
|
17
16
|
args: {
|
|
18
17
|
write_report_artifact: tool.schema.boolean().default(true),
|
|
19
|
-
repair_lock: tool.schema.boolean().default(true).describe("Attempt to remove stale/dead lock files"),
|
|
20
18
|
},
|
|
21
|
-
execute: async ({ write_report_artifact
|
|
19
|
+
execute: async ({ write_report_artifact }) => {
|
|
22
20
|
const repoRoot = ctx.directory as string;
|
|
23
|
-
const lockPath = path.join(repoRoot, ".astro", "astro.lock");
|
|
24
21
|
|
|
25
|
-
//
|
|
26
|
-
const lockLines: string[] = [];
|
|
27
|
-
const lockStatus = getLockStatus(lockPath);
|
|
28
|
-
|
|
29
|
-
if (lockStatus.exists) {
|
|
30
|
-
lockLines.push("## Lock Status");
|
|
31
|
-
lockLines.push(`- Lock found: ${lockPath}`);
|
|
32
|
-
lockLines.push(`- PID: ${lockStatus.pid} (${lockStatus.pidAlive ? 'alive' : 'dead'})`);
|
|
33
|
-
lockLines.push(`- Age: ${lockStatus.ageMs ? Math.floor(lockStatus.ageMs / 1000) : '?'}s`);
|
|
34
|
-
lockLines.push(`- Status: ${lockStatus.isStale ? 'stale' : 'fresh'}`);
|
|
35
|
-
|
|
36
|
-
if (repair_lock) {
|
|
37
|
-
const result = tryRemoveStaleLock(lockPath);
|
|
38
|
-
if (result.removed) {
|
|
39
|
-
lockLines.push(`- **Removed**: ${result.reason}`);
|
|
40
|
-
} else {
|
|
41
|
-
lockLines.push(`- **Not removed**: ${result.reason}`);
|
|
42
|
-
}
|
|
43
|
-
} else {
|
|
44
|
-
if (!lockStatus.pidAlive || lockStatus.isStale) {
|
|
45
|
-
lockLines.push(`- **Recommendation**: Run with repair_lock=true to remove this ${!lockStatus.pidAlive ? 'dead' : 'stale'} lock`);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
lockLines.push("");
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// Then repair database state
|
|
22
|
+
// Repair database state
|
|
52
23
|
const report = withTx(db, () => repairState(db, config));
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
// Combine lock and DB repair
|
|
56
|
-
const fullMd = lockLines.length > 0
|
|
57
|
-
? `# Astrocode Repair Report\n\n${lockLines.join("\n")}\n${dbMd.replace(/^# Astrocode repair report\n*/i, "")}`
|
|
58
|
-
: dbMd;
|
|
24
|
+
const fullMd = formatRepairReport(report);
|
|
59
25
|
|
|
60
26
|
if (write_report_artifact) {
|
|
61
27
|
const rel = `.astro/repair/repair_${nowISO().replace(/[:.]/g, "-")}.md`;
|
package/src/tools/workflow.ts
CHANGED
|
@@ -24,8 +24,6 @@ import { newEventId } from "../state/ids";
|
|
|
24
24
|
import { debug } from "../shared/log";
|
|
25
25
|
import { createToastManager } from "../ui/toasts";
|
|
26
26
|
import type { AgentConfig } from "@opencode-ai/sdk";
|
|
27
|
-
import { acquireRepoLock } from "../state/repo-lock";
|
|
28
|
-
import { workflowRepoLock } from "../state/workflow-repo-lock";
|
|
29
27
|
|
|
30
28
|
// Agent name mapping for case-sensitive resolution
|
|
31
29
|
export const STAGE_TO_AGENT_MAP: Record<string, string> = {
|
|
@@ -188,20 +186,8 @@ export function createAstroWorkflowProceedTool(opts: { ctx: any; config: Astroco
|
|
|
188
186
|
max_steps: tool.schema.number().int().positive().default(config.workflow.default_max_steps),
|
|
189
187
|
},
|
|
190
188
|
execute: async ({ mode, max_steps }) => {
|
|
191
|
-
const repoRoot = (ctx as any).directory as string;
|
|
192
|
-
const lockPath = path.join(repoRoot, ".astro", "astro.lock");
|
|
193
189
|
const sessionId = (ctx as any).sessionID as string | undefined;
|
|
194
|
-
|
|
195
|
-
return workflowRepoLock(
|
|
196
|
-
{ acquireRepoLock },
|
|
197
|
-
{
|
|
198
|
-
lockPath,
|
|
199
|
-
repoRoot,
|
|
200
|
-
sessionId,
|
|
201
|
-
owner: "astro_workflow_proceed",
|
|
202
|
-
advisory: true, // Advisory mode: warn instead of blocking on lock contention
|
|
203
|
-
fn: async () => {
|
|
204
|
-
const steps = Math.min(max_steps, config.workflow.loop_max_steps_hard_cap);
|
|
190
|
+
const steps = Math.min(max_steps, config.workflow.loop_max_steps_hard_cap);
|
|
205
191
|
|
|
206
192
|
const actions: string[] = [];
|
|
207
193
|
const warnings: string[] = [];
|
|
@@ -424,8 +410,6 @@ export function createAstroWorkflowProceedTool(opts: { ctx: any; config: Astroco
|
|
|
424
410
|
}
|
|
425
411
|
|
|
426
412
|
return lines.join("\n").trim();
|
|
427
|
-
},
|
|
428
|
-
});
|
|
429
413
|
},
|
|
430
414
|
});
|
|
431
415
|
}
|