astrocode-workflow 0.1.57 → 0.1.59
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/README.md +243 -11
- package/dist/agents/prompts.d.ts +1 -0
- package/dist/agents/prompts.js +159 -0
- package/dist/agents/registry.js +11 -1
- package/dist/config/loader.js +34 -0
- package/dist/config/schema.d.ts +12 -1
- package/dist/config/schema.js +14 -0
- package/dist/hooks/continuation-enforcer.d.ts +9 -1
- package/dist/hooks/continuation-enforcer.js +2 -1
- package/dist/hooks/inject-provider.d.ts +9 -1
- package/dist/hooks/inject-provider.js +11 -3
- package/dist/hooks/tool-output-truncator.d.ts +9 -1
- package/dist/hooks/tool-output-truncator.js +2 -1
- package/dist/index.js +228 -45
- package/dist/state/adapters/index.d.ts +4 -2
- package/dist/state/adapters/index.js +23 -27
- package/dist/state/db.d.ts +6 -8
- package/dist/state/db.js +106 -45
- package/dist/tools/index.d.ts +13 -3
- package/dist/tools/index.js +14 -31
- package/dist/tools/init.d.ts +10 -1
- package/dist/tools/init.js +73 -18
- package/dist/tools/spec.d.ts +0 -1
- package/dist/tools/spec.js +4 -1
- package/dist/tools/status.d.ts +1 -1
- package/dist/tools/status.js +70 -52
- package/dist/tools/workflow.js +6 -3
- package/dist/workflow/directives.d.ts +2 -0
- package/dist/workflow/directives.js +34 -19
- package/dist/workflow/state-machine.d.ts +27 -0
- package/dist/workflow/state-machine.js +167 -86
- package/package.json +1 -1
- package/src/agents/prompts.ts +160 -0
- package/src/agents/registry.ts +16 -1
- package/src/config/loader.ts +39 -4
- package/src/config/schema.ts +16 -0
- package/src/hooks/continuation-enforcer.ts +9 -2
- package/src/hooks/inject-provider.ts +18 -4
- package/src/hooks/tool-output-truncator.ts +9 -2
- package/src/index.ts +260 -56
- package/src/state/adapters/index.ts +21 -26
- package/src/state/db.ts +114 -58
- package/src/tools/index.ts +29 -31
- package/src/tools/init.ts +91 -22
- package/src/tools/spec.ts +6 -2
- package/src/tools/status.ts +71 -55
- package/src/tools/workflow.ts +7 -4
- package/src/workflow/directives.ts +103 -75
- package/src/workflow/state-machine.ts +229 -105
|
@@ -2,7 +2,8 @@ import { selectEligibleInjects } from "../tools/injects";
|
|
|
2
2
|
import { injectChatPrompt } from "../ui/inject";
|
|
3
3
|
import { nowISO } from "../shared/time";
|
|
4
4
|
export function createInjectProvider(opts) {
|
|
5
|
-
const { ctx, config,
|
|
5
|
+
const { ctx, config, runtime } = opts;
|
|
6
|
+
const { db } = runtime;
|
|
6
7
|
// Cache to avoid re-injecting the same injects repeatedly
|
|
7
8
|
const injectedCache = new Map();
|
|
8
9
|
function shouldSkipInject(injectId, nowMs) {
|
|
@@ -65,6 +66,7 @@ export function createInjectProvider(opts) {
|
|
|
65
66
|
// Get allowlists from config or defaults
|
|
66
67
|
const scopeAllowlist = config.inject?.scope_allowlist ?? ["repo", "global"];
|
|
67
68
|
const typeAllowlist = config.inject?.type_allowlist ?? ["note", "policy"];
|
|
69
|
+
const EMIT_TELEMETRY = config.debug?.telemetry?.enabled ?? false;
|
|
68
70
|
// Get diagnostic data
|
|
69
71
|
const diagnostics = getInjectionDiagnostics(now, scopeAllowlist, typeAllowlist);
|
|
70
72
|
const eligibleInjects = selectEligibleInjects(db, {
|
|
@@ -77,7 +79,10 @@ export function createInjectProvider(opts) {
|
|
|
77
79
|
let skippedDeduped = 0;
|
|
78
80
|
if (eligibleInjects.length === 0) {
|
|
79
81
|
// Log when no injects are eligible
|
|
80
|
-
|
|
82
|
+
if (EMIT_TELEMETRY) {
|
|
83
|
+
// eslint-disable-next-line no-console
|
|
84
|
+
console.log(`[Astrocode:inject] ${now} selected=${diagnostics.selected_eligible} injected=0 skipped={expired:${diagnostics.skipped.expired} scope:${diagnostics.skipped.scope} type:${diagnostics.skipped.type} deduped:0}`);
|
|
85
|
+
}
|
|
81
86
|
return;
|
|
82
87
|
}
|
|
83
88
|
// Inject each eligible inject, skipping recently injected ones
|
|
@@ -98,7 +103,10 @@ export function createInjectProvider(opts) {
|
|
|
98
103
|
markInjected(inject.inject_id, nowMs);
|
|
99
104
|
}
|
|
100
105
|
// Log diagnostic summary
|
|
101
|
-
|
|
106
|
+
if (EMIT_TELEMETRY) {
|
|
107
|
+
// eslint-disable-next-line no-console
|
|
108
|
+
console.log(`[Astrocode:inject] ${now} selected=${diagnostics.selected_eligible} injected=${injected} skipped={expired:${diagnostics.skipped.expired} scope:${diagnostics.skipped.scope} type:${diagnostics.skipped.type} deduped:${skippedDeduped}}`);
|
|
109
|
+
}
|
|
102
110
|
}
|
|
103
111
|
// Public hook handlers
|
|
104
112
|
return {
|
|
@@ -9,9 +9,17 @@ type ToolExecuteAfterOutput = {
|
|
|
9
9
|
output?: string;
|
|
10
10
|
metadata?: Record<string, any>;
|
|
11
11
|
};
|
|
12
|
+
type RuntimeState = {
|
|
13
|
+
db: SqliteDb | null;
|
|
14
|
+
limitedMode: boolean;
|
|
15
|
+
limitedModeReason: null | {
|
|
16
|
+
code: "db_init_failed" | "schema_too_old" | "schema_downgrade" | "schema_migration_failed";
|
|
17
|
+
details: any;
|
|
18
|
+
};
|
|
19
|
+
};
|
|
12
20
|
export declare function createToolOutputTruncatorHook(opts: {
|
|
13
21
|
ctx: any;
|
|
14
22
|
config: AstrocodeConfig;
|
|
15
|
-
|
|
23
|
+
runtime: RuntimeState;
|
|
16
24
|
}): (input: ToolExecuteAfterInput, output: ToolExecuteAfterOutput) => Promise<void>;
|
|
17
25
|
export {};
|
|
@@ -13,7 +13,8 @@ function pickMaxChars(toolName, cfg) {
|
|
|
13
13
|
return cfg.truncation.max_chars_default;
|
|
14
14
|
}
|
|
15
15
|
export function createToolOutputTruncatorHook(opts) {
|
|
16
|
-
const { ctx, config,
|
|
16
|
+
const { ctx, config, runtime } = opts;
|
|
17
|
+
const { db } = runtime;
|
|
17
18
|
return async function toolExecuteAfter(input, output) {
|
|
18
19
|
if (!config.truncation.enabled)
|
|
19
20
|
return;
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { loadAstrocodeConfig } from "./config/loader";
|
|
2
2
|
import { createConfigHandler } from "./config/config-handler";
|
|
3
|
-
import { openSqlite, configurePragmas, ensureSchema } from "./state/db";
|
|
3
|
+
import { openSqlite, configurePragmas, ensureSchema, getSchemaVersion } from "./state/db";
|
|
4
4
|
import { getAstroPaths, ensureAstroDirs } from "./shared/paths";
|
|
5
5
|
import { createAstroTools } from "./tools";
|
|
6
6
|
import { createContinuationEnforcer } from "./hooks/continuation-enforcer";
|
|
@@ -8,106 +8,289 @@ import { createToolOutputTruncatorHook } from "./hooks/tool-output-truncator";
|
|
|
8
8
|
import { createInjectProvider } from "./hooks/inject-provider";
|
|
9
9
|
import { createToastManager } from "./ui/toasts";
|
|
10
10
|
import { createAstroAgents } from "./agents/registry";
|
|
11
|
+
import { info, warn } from "./shared/log";
|
|
12
|
+
// Safe config cloning with structuredClone preference (fallback for older Node versions)
|
|
13
|
+
// CONTRACT: Config is guaranteed JSON-serializable (enforced by loadAstrocodeConfig validation)
|
|
14
|
+
const cloneConfig = (v) => {
|
|
15
|
+
// Prefer structuredClone when available (keeps more types)
|
|
16
|
+
if (typeof globalThis.structuredClone === "function") {
|
|
17
|
+
return globalThis.structuredClone(v);
|
|
18
|
+
}
|
|
19
|
+
// Fallback: JSON clone (safe since config is validated as JSON-serializable)
|
|
20
|
+
return JSON.parse(JSON.stringify(v));
|
|
21
|
+
};
|
|
22
|
+
// Minimal config defaults for limited-mode fallback
|
|
23
|
+
const applyMinimalConfigDefaults = (cfg) => {
|
|
24
|
+
if (!cfg || typeof cfg !== "object")
|
|
25
|
+
return;
|
|
26
|
+
if (!Array.isArray(cfg.disabled_hooks))
|
|
27
|
+
cfg.disabled_hooks = [];
|
|
28
|
+
if (!cfg.ui || typeof cfg.ui !== "object")
|
|
29
|
+
cfg.ui = {};
|
|
30
|
+
if (!cfg.ui.toasts || typeof cfg.ui.toasts !== "object")
|
|
31
|
+
cfg.ui.toasts = { enabled: false };
|
|
32
|
+
if (typeof cfg.ui.toasts.enabled !== "boolean")
|
|
33
|
+
cfg.ui.toasts.enabled = false;
|
|
34
|
+
};
|
|
11
35
|
const Astrocode = async (ctx) => {
|
|
36
|
+
if (!ctx.directory || typeof ctx.directory !== "string") {
|
|
37
|
+
throw new Error("Astrocode requires ctx.directory to be a string repo root.");
|
|
38
|
+
}
|
|
12
39
|
const repoRoot = ctx.directory;
|
|
13
40
|
// Always load config first - this provides defaults even in limited mode
|
|
14
|
-
let pluginConfig
|
|
15
|
-
|
|
16
|
-
|
|
41
|
+
let pluginConfig;
|
|
42
|
+
try {
|
|
43
|
+
pluginConfig = loadAstrocodeConfig(repoRoot);
|
|
44
|
+
}
|
|
45
|
+
catch (e) {
|
|
46
|
+
warn(`Config loading failed, using minimal defaults.`, { err: e });
|
|
47
|
+
// Fallback to minimal safe defaults when config loading fails
|
|
48
|
+
pluginConfig = {
|
|
49
|
+
disabled_hooks: [],
|
|
50
|
+
disabled_agents: [],
|
|
51
|
+
determinism: { mode: "on", strict_stage_order: true },
|
|
52
|
+
db: {
|
|
53
|
+
path: ".astro/astro.db",
|
|
54
|
+
busy_timeout_ms: 5000,
|
|
55
|
+
pragmas: {},
|
|
56
|
+
schema_version_required: 2,
|
|
57
|
+
allow_auto_migrate: true,
|
|
58
|
+
fail_on_downgrade: true,
|
|
59
|
+
},
|
|
60
|
+
ui: {
|
|
61
|
+
toasts: { enabled: true, throttle_ms: 1500, show_run_started: true, show_stage_started: true, show_stage_completed: true, show_stage_failed: true, show_run_completed: true, show_auto_continue: true },
|
|
62
|
+
},
|
|
63
|
+
agents: {
|
|
64
|
+
orchestrator_name: "Astro",
|
|
65
|
+
stage_agent_names: { frame: "Frame", plan: "Plan", spec: "Spec", implement: "Implement", review: "Review", verify: "Verify", close: "Close" },
|
|
66
|
+
librarian_name: "Librarian",
|
|
67
|
+
explore_name: "Explore",
|
|
68
|
+
qa_name: "QA",
|
|
69
|
+
agent_variant_overrides: {},
|
|
70
|
+
},
|
|
71
|
+
workflow: {
|
|
72
|
+
default_mode: "step",
|
|
73
|
+
default_max_steps: 10,
|
|
74
|
+
loop_max_steps_hard_cap: 100,
|
|
75
|
+
evidence_required: { verify: false },
|
|
76
|
+
},
|
|
77
|
+
permissions: { enforce_task_tool_restrictions: false, deny_delegate_task_in_subagents: false },
|
|
78
|
+
};
|
|
79
|
+
}
|
|
17
80
|
// Always ensure .astro directories exist, even in limited mode
|
|
18
81
|
const paths = getAstroPaths(repoRoot, pluginConfig.db.path);
|
|
19
82
|
ensureAstroDirs(paths);
|
|
20
83
|
let db = null;
|
|
21
|
-
let
|
|
22
|
-
let configHandler =
|
|
84
|
+
let agents = undefined;
|
|
85
|
+
let configHandler = async () => {
|
|
86
|
+
throw new Error("ConfigHandler called before initialization");
|
|
87
|
+
};
|
|
23
88
|
let continuation = null;
|
|
24
89
|
let truncatorHook = null;
|
|
25
90
|
let injectProvider = null;
|
|
26
|
-
let
|
|
91
|
+
let toastManager = null;
|
|
92
|
+
let limitedModeReason = null;
|
|
93
|
+
let limitedMode = false;
|
|
94
|
+
// Phase 1: Database initialization (only DB-related operations)
|
|
27
95
|
try {
|
|
28
96
|
db = openSqlite(paths.dbPath, { busyTimeoutMs: pluginConfig.db.busy_timeout_ms });
|
|
29
97
|
configurePragmas(db, pluginConfig.db.pragmas);
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
98
|
+
// Policy enforced in harness; db layer is mechanics only (migration + structural schema creation)
|
|
99
|
+
ensureSchema(db, { allowAutoMigrate: pluginConfig.db.allow_auto_migrate });
|
|
100
|
+
// Harness-exclusive: enforce schema version requirement
|
|
101
|
+
// Explicitly enforce schema version requirement (harness-level policy)
|
|
102
|
+
const required = pluginConfig.db.schema_version_required;
|
|
103
|
+
let current = getSchemaVersion(db);
|
|
104
|
+
if (current < required) {
|
|
105
|
+
if (!pluginConfig.db.allow_auto_migrate) {
|
|
106
|
+
// Migration disabled - enter limited mode
|
|
107
|
+
limitedModeReason = { code: "schema_too_old", details: { current, required } };
|
|
108
|
+
warn("Entering limited mode (schema too old, migration disabled).", {
|
|
109
|
+
dbPath: paths.dbPath,
|
|
110
|
+
schemaCurrent: current,
|
|
111
|
+
schemaRequired: required,
|
|
112
|
+
failOnDowngrade: pluginConfig.db.fail_on_downgrade,
|
|
113
|
+
allowAutoMigrate: pluginConfig.db.allow_auto_migrate,
|
|
114
|
+
});
|
|
115
|
+
db.close?.();
|
|
116
|
+
db = null;
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
// Migration allowed - re-run ensureSchema (idempotent) in case first call failed
|
|
120
|
+
ensureSchema(db, { allowAutoMigrate: true });
|
|
121
|
+
current = getSchemaVersion(db);
|
|
122
|
+
if (current < required) {
|
|
123
|
+
// Migration failed - enter limited mode
|
|
124
|
+
limitedModeReason = { code: "schema_migration_failed", details: { current, required } };
|
|
125
|
+
warn("Entering limited mode (schema migration failed).", {
|
|
126
|
+
dbPath: paths.dbPath,
|
|
127
|
+
schemaCurrent: current,
|
|
128
|
+
schemaRequired: required,
|
|
129
|
+
failOnDowngrade: pluginConfig.db.fail_on_downgrade,
|
|
130
|
+
allowAutoMigrate: pluginConfig.db.allow_auto_migrate,
|
|
131
|
+
});
|
|
132
|
+
db.close?.();
|
|
133
|
+
db = null;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
else if (current > required && pluginConfig.db.fail_on_downgrade) {
|
|
138
|
+
limitedModeReason = { code: "schema_downgrade", details: { current, required } };
|
|
139
|
+
warn("Entering limited mode (schema downgrade).", {
|
|
140
|
+
dbPath: paths.dbPath,
|
|
141
|
+
schemaCurrent: current,
|
|
142
|
+
schemaRequired: required,
|
|
143
|
+
failOnDowngrade: pluginConfig.db.fail_on_downgrade,
|
|
144
|
+
allowAutoMigrate: pluginConfig.db.allow_auto_migrate,
|
|
145
|
+
});
|
|
146
|
+
db.close?.();
|
|
147
|
+
db = null;
|
|
148
|
+
}
|
|
38
149
|
}
|
|
39
150
|
catch (e) {
|
|
40
|
-
//
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
151
|
+
// True database initialization failure - enter limited mode
|
|
152
|
+
warn(`Database initialization failed. Entering limited mode with reduced functionality.`, { err: e });
|
|
153
|
+
// Clean up partially-opened database connection
|
|
154
|
+
if (db && typeof db.close === "function") {
|
|
155
|
+
try {
|
|
156
|
+
db.close();
|
|
157
|
+
}
|
|
158
|
+
catch (closeErr) {
|
|
159
|
+
// Ignore close errors during cleanup
|
|
160
|
+
}
|
|
161
|
+
}
|
|
47
162
|
db = null;
|
|
48
|
-
|
|
49
|
-
|
|
163
|
+
limitedModeReason = { code: "db_init_failed", details: e };
|
|
164
|
+
}
|
|
165
|
+
// Derive limited mode flag once for single source of truth
|
|
166
|
+
limitedMode = db === null;
|
|
167
|
+
// Single runtime state object for downstream components
|
|
168
|
+
const runtime = { db, limitedMode, limitedModeReason };
|
|
169
|
+
// Phase 2: Component initialization (based on DB availability)
|
|
170
|
+
if (!limitedMode) {
|
|
171
|
+
// Normal mode: DB available, create all components
|
|
172
|
+
try {
|
|
173
|
+
agents = createAstroAgents({ pluginConfig });
|
|
174
|
+
// Database initialized successfully
|
|
175
|
+
info("Database initialized successfully. Plugin ready.");
|
|
176
|
+
configHandler = createConfigHandler({ pluginConfig });
|
|
177
|
+
continuation = createContinuationEnforcer({ ctx, config: pluginConfig, runtime });
|
|
178
|
+
truncatorHook = createToolOutputTruncatorHook({ ctx, config: pluginConfig, runtime });
|
|
179
|
+
injectProvider = createInjectProvider({ ctx, config: pluginConfig, runtime });
|
|
180
|
+
toastManager = createToastManager({ ctx, throttleMs: pluginConfig.ui.toasts.throttle_ms });
|
|
181
|
+
}
|
|
182
|
+
catch (e) {
|
|
183
|
+
// Component failure with working DB → fail plugin boot
|
|
184
|
+
const err = e instanceof Error ? e : new Error(String(e));
|
|
185
|
+
throw new Error("Plugin initialization failed after successful database setup", { cause: err });
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
// Limited mode: DB unavailable, create minimal components
|
|
190
|
+
// Clone the already-loaded config to avoid mutating shared state, then apply limited mode overrides
|
|
191
|
+
pluginConfig = cloneConfig(pluginConfig);
|
|
192
|
+
// Apply limited-mode config overrides BEFORE creating any components
|
|
193
|
+
pluginConfig.disabled_hooks = Array.from(new Set([...(pluginConfig.disabled_hooks || []), "continuation-enforcer", "tool-output-truncator", "inject-provider"]));
|
|
194
|
+
pluginConfig.ui.toasts.enabled = false;
|
|
195
|
+
// Show one-shot limited mode notification via logging (keeps toast policy consistent)
|
|
196
|
+
const reasonMessage = limitedModeReason ?
|
|
197
|
+
(limitedModeReason.code === "db_init_failed" ? "Database initialization failed. Check native deps or run astro_init." :
|
|
198
|
+
limitedModeReason.code === "schema_too_old" ? "Database schema too old and migration disabled." :
|
|
199
|
+
limitedModeReason.code === "schema_downgrade" ? "Database schema newer than plugin (downgrade detected)." :
|
|
200
|
+
limitedModeReason.code === "schema_migration_failed" ? "Database schema migration failed." :
|
|
201
|
+
"Database unavailable.") :
|
|
202
|
+
"Database unavailable.";
|
|
203
|
+
warn(`Astrocode: Limited Mode - ${reasonMessage}`, { dbPath: paths.dbPath });
|
|
204
|
+
// Create limited functionality (no global toast manager since toasts are disabled)
|
|
205
|
+
try {
|
|
206
|
+
configHandler = createConfigHandler({ pluginConfig });
|
|
207
|
+
}
|
|
208
|
+
catch (e) {
|
|
209
|
+
warn("Limited Mode: config handler unavailable", { err: e });
|
|
210
|
+
configHandler = async (config) => {
|
|
211
|
+
applyMinimalConfigDefaults(config);
|
|
212
|
+
};
|
|
213
|
+
}
|
|
50
214
|
continuation = null;
|
|
51
215
|
truncatorHook = null;
|
|
52
216
|
injectProvider = null;
|
|
53
|
-
|
|
217
|
+
toastManager = null; // No global toast manager in limited mode
|
|
54
218
|
}
|
|
219
|
+
// Helper for hook enablement checks (config is now finalized)
|
|
220
|
+
const hookEnabled = (key) => !pluginConfig.disabled_hooks?.includes(key);
|
|
221
|
+
// Create tools once with final state (limited mode gets DB-safe tool implementations)
|
|
222
|
+
const tools = createAstroTools({ ctx, config: pluginConfig, agents, runtime });
|
|
55
223
|
return {
|
|
56
|
-
name: "
|
|
224
|
+
name: "astrocode-harness",
|
|
57
225
|
// Register agents
|
|
58
226
|
agents,
|
|
59
227
|
// Merge agents + slash commands into system config
|
|
60
228
|
config: configHandler,
|
|
61
229
|
// Register tools
|
|
62
|
-
tool:
|
|
230
|
+
tool: tools,
|
|
63
231
|
// Limit created subagents from spawning more subagents (OMO-style).
|
|
64
232
|
"tool.execute.before": async (input, output) => {
|
|
65
233
|
if (!pluginConfig.permissions.enforce_task_tool_restrictions)
|
|
66
234
|
return;
|
|
67
|
-
if (input
|
|
235
|
+
if (input?.tool !== "task")
|
|
68
236
|
return;
|
|
69
|
-
|
|
70
|
-
const
|
|
237
|
+
const baseArgs = (output && typeof output === "object" ? output.args : undefined) ?? {};
|
|
238
|
+
const baseTools = (baseArgs && typeof baseArgs === "object" && baseArgs.tools && typeof baseArgs.tools === "object")
|
|
239
|
+
? baseArgs.tools
|
|
240
|
+
: {};
|
|
241
|
+
const nextTools = { ...baseTools };
|
|
71
242
|
if (pluginConfig.permissions.deny_delegate_task_in_subagents) {
|
|
72
|
-
|
|
243
|
+
nextTools.delegate_task = false;
|
|
244
|
+
}
|
|
245
|
+
const nextArgs = { ...baseArgs, tools: nextTools };
|
|
246
|
+
// Back-compat: mutate if possible
|
|
247
|
+
if (output && typeof output === "object" && !Object.isFrozen(output)) {
|
|
248
|
+
output.args = nextArgs;
|
|
73
249
|
}
|
|
74
|
-
|
|
250
|
+
// Forward-compat: return conservative merge
|
|
251
|
+
if (output && typeof output === "object") {
|
|
252
|
+
return { ...output, args: nextArgs };
|
|
253
|
+
}
|
|
254
|
+
return { args: nextArgs };
|
|
75
255
|
},
|
|
76
256
|
"tool.execute.after": async (input, output) => {
|
|
77
257
|
// Truncate huge tool outputs to artifacts
|
|
78
|
-
if (truncatorHook &&
|
|
79
|
-
await truncatorHook(input, output);
|
|
258
|
+
if (truncatorHook && hookEnabled("tool-output-truncator")) {
|
|
259
|
+
await truncatorHook(input, output ?? null);
|
|
80
260
|
}
|
|
81
261
|
// Schedule continuation (do not immediately spam)
|
|
82
|
-
if (continuation &&
|
|
262
|
+
if (continuation && hookEnabled("continuation-enforcer")) {
|
|
83
263
|
await continuation.onToolAfter(input);
|
|
84
264
|
}
|
|
265
|
+
return output;
|
|
85
266
|
},
|
|
86
267
|
"chat.message": async (input, output) => {
|
|
87
|
-
if (injectProvider &&
|
|
268
|
+
if (injectProvider && hookEnabled("inject-provider")) {
|
|
88
269
|
await injectProvider.onChatMessage(input);
|
|
89
270
|
}
|
|
90
|
-
if (continuation &&
|
|
271
|
+
if (continuation && hookEnabled("continuation-enforcer")) {
|
|
91
272
|
await continuation.onChatMessage(input);
|
|
92
273
|
}
|
|
93
274
|
return output;
|
|
94
275
|
},
|
|
95
276
|
event: async (input) => {
|
|
96
|
-
if (continuation &&
|
|
277
|
+
if (continuation && hookEnabled("continuation-enforcer")) {
|
|
97
278
|
await continuation.onEvent(input);
|
|
98
279
|
}
|
|
99
280
|
},
|
|
100
281
|
// Best-effort cleanup
|
|
101
282
|
close: async () => {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
283
|
+
if (db && typeof db.close === "function") {
|
|
284
|
+
try {
|
|
285
|
+
db.close();
|
|
286
|
+
}
|
|
287
|
+
catch {
|
|
288
|
+
// ignore
|
|
289
|
+
}
|
|
107
290
|
}
|
|
108
|
-
if (
|
|
291
|
+
if (toastManager && pluginConfig.ui.toasts.enabled) {
|
|
109
292
|
try {
|
|
110
|
-
await
|
|
293
|
+
await toastManager.show({ title: "Astrocode", message: "Plugin closed", variant: "info" });
|
|
111
294
|
}
|
|
112
295
|
catch {
|
|
113
296
|
// ignore
|
|
@@ -18,9 +18,11 @@ export interface DatabaseAdapter {
|
|
|
18
18
|
busyTimeoutMs?: number;
|
|
19
19
|
}): DatabaseConnection;
|
|
20
20
|
}
|
|
21
|
-
export declare class
|
|
21
|
+
export declare class NullAdapter implements DatabaseAdapter {
|
|
22
22
|
isAvailable(): boolean;
|
|
23
|
-
open(
|
|
23
|
+
open(path: string, opts?: {
|
|
24
|
+
busyTimeoutMs?: number;
|
|
25
|
+
}): DatabaseConnection;
|
|
24
26
|
}
|
|
25
27
|
export declare class BunSqliteAdapter implements DatabaseAdapter {
|
|
26
28
|
isAvailable(): boolean;
|
|
@@ -1,21 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
export class MockDatabaseAdapter {
|
|
1
|
+
// Null adapter for when no database driver is available
|
|
2
|
+
export class NullAdapter {
|
|
4
3
|
isAvailable() {
|
|
5
|
-
return
|
|
4
|
+
return false;
|
|
6
5
|
}
|
|
7
|
-
open() {
|
|
8
|
-
|
|
9
|
-
return {
|
|
10
|
-
pragma: () => { },
|
|
11
|
-
exec: () => { },
|
|
12
|
-
prepare: () => ({
|
|
13
|
-
run: () => ({ changes: 0, lastInsertRowid: null }),
|
|
14
|
-
get: () => null,
|
|
15
|
-
all: () => []
|
|
16
|
-
}),
|
|
17
|
-
close: () => { }
|
|
18
|
-
};
|
|
6
|
+
open(path, opts) {
|
|
7
|
+
throw new Error("No SQLite driver available");
|
|
19
8
|
}
|
|
20
9
|
}
|
|
21
10
|
// Bun SQLite adapter - singleton pattern to avoid multiple initializations
|
|
@@ -92,7 +81,7 @@ export class BetterSqliteAdapter {
|
|
|
92
81
|
db.pragma(`busy_timeout = ${opts.busyTimeoutMs}`);
|
|
93
82
|
}
|
|
94
83
|
return {
|
|
95
|
-
pragma: (sql) => db.pragma(sql
|
|
84
|
+
pragma: (sql) => db.pragma(sql),
|
|
96
85
|
exec: (sql) => db.exec(sql),
|
|
97
86
|
prepare: (sql) => db.prepare(sql),
|
|
98
87
|
close: () => db.close()
|
|
@@ -103,17 +92,24 @@ export class BetterSqliteAdapter {
|
|
|
103
92
|
export function createDatabaseAdapter() {
|
|
104
93
|
const isBun = typeof globalThis.Bun !== 'undefined';
|
|
105
94
|
if (isBun) {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
95
|
+
// Prefer bun:sqlite in Bun runtime
|
|
96
|
+
const bunAdapter = new BunSqliteAdapter();
|
|
97
|
+
if (bunAdapter.isAvailable())
|
|
98
|
+
return bunAdapter;
|
|
99
|
+
// Fallback to better-sqlite3
|
|
100
|
+
const betterAdapter = new BetterSqliteAdapter();
|
|
101
|
+
if (betterAdapter.isAvailable())
|
|
102
|
+
return betterAdapter;
|
|
111
103
|
}
|
|
112
104
|
else {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
105
|
+
// Prefer better-sqlite3 in Node.js
|
|
106
|
+
const betterAdapter = new BetterSqliteAdapter();
|
|
107
|
+
if (betterAdapter.isAvailable())
|
|
108
|
+
return betterAdapter;
|
|
109
|
+
// Fallback to bun:sqlite (rare but possible)
|
|
110
|
+
const bunAdapter = new BunSqliteAdapter();
|
|
111
|
+
if (bunAdapter.isAvailable())
|
|
112
|
+
return bunAdapter;
|
|
118
113
|
}
|
|
114
|
+
return new NullAdapter();
|
|
119
115
|
}
|
package/dist/state/db.d.ts
CHANGED
|
@@ -3,15 +3,13 @@ export type SqliteDb = DatabaseConnection;
|
|
|
3
3
|
export declare function openSqlite(dbPath: string, opts?: {
|
|
4
4
|
busyTimeoutMs?: number;
|
|
5
5
|
}): SqliteDb;
|
|
6
|
-
export declare function configurePragmas(db: SqliteDb, pragmas:
|
|
7
|
-
journal_mode?: "WAL" | "DELETE";
|
|
8
|
-
synchronous?: "NORMAL" | "FULL" | "OFF";
|
|
9
|
-
foreign_keys?: boolean;
|
|
10
|
-
temp_store?: "DEFAULT" | "MEMORY" | "FILE";
|
|
11
|
-
}): void;
|
|
6
|
+
export declare function configurePragmas(db: SqliteDb, pragmas: Record<string, any>): void;
|
|
12
7
|
/** BEGIN IMMEDIATE transaction helper. */
|
|
13
|
-
export declare function withTx<T>(db: SqliteDb, fn: () => T
|
|
8
|
+
export declare function withTx<T>(db: SqliteDb, fn: () => T, opts?: {
|
|
9
|
+
require?: boolean;
|
|
10
|
+
}): T;
|
|
11
|
+
export declare function getSchemaVersion(db: SqliteDb): number;
|
|
14
12
|
export declare function ensureSchema(db: SqliteDb, opts?: {
|
|
15
13
|
allowAutoMigrate?: boolean;
|
|
16
|
-
|
|
14
|
+
silent?: boolean;
|
|
17
15
|
}): void;
|