refactor: low-regret cleanup across core, engine, dashboard
- core: extract ai-engine-loader.ts to share @fusion/engine dynamic-import boilerplate between ai-summarize and memory-compaction (incl. AgentMessage type); collapse getInbox/getOutbox, listInsights/countInsights, listRuns/countRuns, and three hasProjectDb* variants behind shared helpers. - core: drop unused pluginLoaderLog export; tighten two `any` casts (db.walCheckpoint row, plugin-loader error.code). - engine: extract resolveRoleFallback helper from buildSessionSkillContext/Sync; remove 22 stale `eslint-disable no-explicit-any` directives across project-engine, self-healing, triage, worktree-pool. - dashboard: apply ESLint autofix (let→const, empty `interface extends`→type). All three packages: typecheck clean, full test suites pass (14,414 tests), builds clean. Net lint: -31 warnings. No public behavior changes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
45
packages/core/src/ai-engine-loader.ts
Normal file
45
packages/core/src/ai-engine-loader.ts
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
/**
|
||||||
|
* Shared lazy loader for `@fusion/engine`'s `createKbAgent`.
|
||||||
|
*
|
||||||
|
* @fusion/engine must be imported dynamically (not statically) so that:
|
||||||
|
* - core can be consumed in test environments where engine isn't resolvable
|
||||||
|
* - a missing engine package fails soft instead of breaking module load
|
||||||
|
*
|
||||||
|
* Using a variable module specifier also prevents bundlers (Vite) from
|
||||||
|
* statically analysing and trying to resolve the import at build time.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Engine exports a function type we intentionally don't pull in here — importing
|
||||||
|
// the type would reintroduce the static resolution this module is designed to avoid.
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
type CreateKbAgent = any;
|
||||||
|
|
||||||
|
let createKbAgent: CreateKbAgent | undefined;
|
||||||
|
|
||||||
|
async function initEngine(): Promise<void> {
|
||||||
|
try {
|
||||||
|
const engineModule = "@fusion/engine";
|
||||||
|
const engine = await import(/* @vite-ignore */ engineModule);
|
||||||
|
createKbAgent = engine.createKbAgent;
|
||||||
|
} catch {
|
||||||
|
createKbAgent = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Shape of a message in an agent session's state. */
|
||||||
|
export interface AgentMessage {
|
||||||
|
role: string;
|
||||||
|
content?: string | Array<{ type: string; text: string }>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Promise that resolves once the initial load attempt has completed. */
|
||||||
|
const engineReady: Promise<void> = initEngine();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns `createKbAgent` from `@fusion/engine`, or `undefined` if the engine
|
||||||
|
* could not be loaded (typical in tests or when engine isn't installed).
|
||||||
|
*/
|
||||||
|
export async function getKbAgent(): Promise<CreateKbAgent> {
|
||||||
|
await engineReady;
|
||||||
|
return createKbAgent;
|
||||||
|
}
|
||||||
@@ -11,29 +11,7 @@
|
|||||||
* - Text length validation (201-2000 characters)
|
* - Text length validation (201-2000 characters)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Dynamic import for @fusion/engine to avoid resolution issues in test environment
|
import { getKbAgent, type AgentMessage } from "./ai-engine-loader.js";
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
type AgentResult = any;
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
let createKbAgent: any;
|
|
||||||
|
|
||||||
// Initialize the import (this runs in actual server, mocked in tests)
|
|
||||||
async function initEngine() {
|
|
||||||
if (!createKbAgent) {
|
|
||||||
try {
|
|
||||||
// Use dynamic import with variable to prevent static analysis
|
|
||||||
const engineModule = "@fusion/engine";
|
|
||||||
const engine = await import(/* @vite-ignore */ engineModule);
|
|
||||||
createKbAgent = engine.createKbAgent;
|
|
||||||
} catch {
|
|
||||||
// Allow failure in test environments - agent functionality will be stubbed
|
|
||||||
createKbAgent = undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize on module load (will be awaited in actual usage)
|
|
||||||
const engineReady = initEngine();
|
|
||||||
|
|
||||||
// ── Constants ───────────────────────────────────────────────────────────────
|
// ── Constants ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
@@ -231,9 +209,7 @@ export async function summarizeTitle(
|
|||||||
return null; // Too short for summarization
|
return null; // Too short for summarization
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure engine is loaded before using createKbAgent
|
const createKbAgent = await getKbAgent();
|
||||||
await engineReady;
|
|
||||||
|
|
||||||
if (!createKbAgent) {
|
if (!createKbAgent) {
|
||||||
if (DEBUG) console.log("[ai-summarize] AI engine not available");
|
if (DEBUG) console.log("[ai-summarize] AI engine not available");
|
||||||
throw new AiServiceError("AI engine not available");
|
throw new AiServiceError("AI engine not available");
|
||||||
@@ -280,12 +256,6 @@ export async function summarizeTitle(
|
|||||||
|
|
||||||
if (DEBUG) console.log("[ai-summarize] Prompt sent, extracting response from messages...");
|
if (DEBUG) console.log("[ai-summarize] Prompt sent, extracting response from messages...");
|
||||||
|
|
||||||
// Get the response text from the agent's state
|
|
||||||
interface AgentMessage {
|
|
||||||
role: string;
|
|
||||||
content?: string | Array<{ type: string; text: string }>;
|
|
||||||
}
|
|
||||||
|
|
||||||
const messages: AgentMessage[] = agentResult.session.state?.messages ?? [];
|
const messages: AgentMessage[] = agentResult.session.state?.messages ?? [];
|
||||||
const assistantMessages = messages.filter((m: AgentMessage) => m.role === "assistant");
|
const assistantMessages = messages.filter((m: AgentMessage) => m.role === "assistant");
|
||||||
|
|
||||||
|
|||||||
@@ -1672,7 +1672,9 @@ export class Database {
|
|||||||
* Safe to call periodically. Returns checkpoint stats.
|
* Safe to call periodically. Returns checkpoint stats.
|
||||||
*/
|
*/
|
||||||
walCheckpoint(): { busy: number; log: number; checkpointed: number } {
|
walCheckpoint(): { busy: number; log: number; checkpointed: number } {
|
||||||
const row = this.db.prepare("PRAGMA wal_checkpoint(TRUNCATE)").get() as any;
|
const row = this.db.prepare("PRAGMA wal_checkpoint(TRUNCATE)").get() as
|
||||||
|
| { busy?: number; log?: number; checkpointed?: number }
|
||||||
|
| undefined;
|
||||||
return { busy: row?.busy ?? 0, log: row?.log ?? 0, checkpointed: row?.checkpointed ?? 0 };
|
return { busy: row?.busy ?? 0, log: row?.log ?? 0, checkpointed: row?.checkpointed ?? 0 };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -185,27 +185,7 @@ export class InsightStore extends EventEmitter<InsightStoreEvents> {
|
|||||||
* @returns Matching insights, ordered ascending by createdAt then id
|
* @returns Matching insights, ordered ascending by createdAt then id
|
||||||
*/
|
*/
|
||||||
listInsights(options: InsightListOptions = {}): Insight[] {
|
listInsights(options: InsightListOptions = {}): Insight[] {
|
||||||
const conditions: string[] = [];
|
const { whereClause, params } = this.buildInsightFilter(options);
|
||||||
const params: (string | number)[] = [];
|
|
||||||
|
|
||||||
if (options.projectId !== undefined) {
|
|
||||||
conditions.push("projectId = ?");
|
|
||||||
params.push(options.projectId);
|
|
||||||
}
|
|
||||||
if (options.category !== undefined) {
|
|
||||||
conditions.push("category = ?");
|
|
||||||
params.push(options.category);
|
|
||||||
}
|
|
||||||
if (options.status !== undefined) {
|
|
||||||
conditions.push("status = ?");
|
|
||||||
params.push(options.status);
|
|
||||||
}
|
|
||||||
if (options.runId !== undefined) {
|
|
||||||
conditions.push("lastRunId = ?");
|
|
||||||
params.push(options.runId);
|
|
||||||
}
|
|
||||||
|
|
||||||
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
||||||
const limitClause = options.limit !== undefined ? `LIMIT ${options.limit}` : "";
|
const limitClause = options.limit !== undefined ? `LIMIT ${options.limit}` : "";
|
||||||
const offsetClause = options.offset !== undefined ? `OFFSET ${options.offset}` : "";
|
const offsetClause = options.offset !== undefined ? `OFFSET ${options.offset}` : "";
|
||||||
|
|
||||||
@@ -349,8 +329,19 @@ export class InsightStore extends EventEmitter<InsightStoreEvents> {
|
|||||||
* Get the count of insights matching the given filter.
|
* Get the count of insights matching the given filter.
|
||||||
*/
|
*/
|
||||||
countInsights(options: Omit<InsightListOptions, "limit" | "offset"> = {}): number {
|
countInsights(options: Omit<InsightListOptions, "limit" | "offset"> = {}): number {
|
||||||
|
const { whereClause, params } = this.buildInsightFilter(options);
|
||||||
|
const row = this.db.prepare(`
|
||||||
|
SELECT COUNT(*) as count FROM project_insights ${whereClause}
|
||||||
|
`).get(...params) as { count: number } | undefined;
|
||||||
|
|
||||||
|
return row?.count ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private buildInsightFilter(
|
||||||
|
options: Pick<InsightListOptions, "projectId" | "category" | "status" | "runId">,
|
||||||
|
): { whereClause: string; params: (string | number)[] } {
|
||||||
const conditions: string[] = [];
|
const conditions: string[] = [];
|
||||||
const params: string[] = [];
|
const params: (string | number)[] = [];
|
||||||
|
|
||||||
if (options.projectId !== undefined) {
|
if (options.projectId !== undefined) {
|
||||||
conditions.push("projectId = ?");
|
conditions.push("projectId = ?");
|
||||||
@@ -369,12 +360,35 @@ export class InsightStore extends EventEmitter<InsightStoreEvents> {
|
|||||||
params.push(options.runId);
|
params.push(options.runId);
|
||||||
}
|
}
|
||||||
|
|
||||||
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
return {
|
||||||
const row = this.db.prepare(`
|
whereClause: conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "",
|
||||||
SELECT COUNT(*) as count FROM project_insights ${whereClause}
|
params,
|
||||||
`).get(...params) as { count: number } | undefined;
|
};
|
||||||
|
}
|
||||||
|
|
||||||
return row?.count ?? 0;
|
private buildRunFilter(
|
||||||
|
options: Pick<InsightRunListOptions, "projectId" | "status" | "trigger">,
|
||||||
|
): { whereClause: string; params: (string | number)[] } {
|
||||||
|
const conditions: string[] = [];
|
||||||
|
const params: (string | number)[] = [];
|
||||||
|
|
||||||
|
if (options.projectId !== undefined) {
|
||||||
|
conditions.push("projectId = ?");
|
||||||
|
params.push(options.projectId);
|
||||||
|
}
|
||||||
|
if (options.status !== undefined) {
|
||||||
|
conditions.push("status = ?");
|
||||||
|
params.push(options.status);
|
||||||
|
}
|
||||||
|
if (options.trigger !== undefined) {
|
||||||
|
conditions.push("trigger = ?");
|
||||||
|
params.push(options.trigger);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
whereClause: conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "",
|
||||||
|
params,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Insight Run CRUD ────────────────────────────────────────────────
|
// ── Insight Run CRUD ────────────────────────────────────────────────
|
||||||
@@ -456,23 +470,7 @@ export class InsightStore extends EventEmitter<InsightStoreEvents> {
|
|||||||
* @returns Matching runs
|
* @returns Matching runs
|
||||||
*/
|
*/
|
||||||
listRuns(options: InsightRunListOptions = {}): InsightRun[] {
|
listRuns(options: InsightRunListOptions = {}): InsightRun[] {
|
||||||
const conditions: string[] = [];
|
const { whereClause, params } = this.buildRunFilter(options);
|
||||||
const params: (string | number)[] = [];
|
|
||||||
|
|
||||||
if (options.projectId !== undefined) {
|
|
||||||
conditions.push("projectId = ?");
|
|
||||||
params.push(options.projectId);
|
|
||||||
}
|
|
||||||
if (options.status !== undefined) {
|
|
||||||
conditions.push("status = ?");
|
|
||||||
params.push(options.status);
|
|
||||||
}
|
|
||||||
if (options.trigger !== undefined) {
|
|
||||||
conditions.push("trigger = ?");
|
|
||||||
params.push(options.trigger);
|
|
||||||
}
|
|
||||||
|
|
||||||
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
||||||
const limitClause = options.limit !== undefined ? `LIMIT ${options.limit}` : "";
|
const limitClause = options.limit !== undefined ? `LIMIT ${options.limit}` : "";
|
||||||
const offsetClause = options.offset !== undefined ? `OFFSET ${options.offset}` : "";
|
const offsetClause = options.offset !== undefined ? `OFFSET ${options.offset}` : "";
|
||||||
|
|
||||||
@@ -595,23 +593,7 @@ export class InsightStore extends EventEmitter<InsightStoreEvents> {
|
|||||||
* Get the count of runs matching the given filter.
|
* Get the count of runs matching the given filter.
|
||||||
*/
|
*/
|
||||||
countRuns(options: Omit<InsightRunListOptions, "limit" | "offset"> = {}): number {
|
countRuns(options: Omit<InsightRunListOptions, "limit" | "offset"> = {}): number {
|
||||||
const conditions: string[] = [];
|
const { whereClause, params } = this.buildRunFilter(options);
|
||||||
const params: string[] = [];
|
|
||||||
|
|
||||||
if (options.projectId !== undefined) {
|
|
||||||
conditions.push("projectId = ?");
|
|
||||||
params.push(options.projectId);
|
|
||||||
}
|
|
||||||
if (options.status !== undefined) {
|
|
||||||
conditions.push("status = ?");
|
|
||||||
params.push(options.status);
|
|
||||||
}
|
|
||||||
if (options.trigger !== undefined) {
|
|
||||||
conditions.push("trigger = ?");
|
|
||||||
params.push(options.trigger);
|
|
||||||
}
|
|
||||||
|
|
||||||
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
||||||
const row = this.db.prepare(`
|
const row = this.db.prepare(`
|
||||||
SELECT COUNT(*) as count FROM project_insight_runs ${whereClause}
|
SELECT COUNT(*) as count FROM project_insight_runs ${whereClause}
|
||||||
`).get(...params) as { count: number } | undefined;
|
`).get(...params) as { count: number } | undefined;
|
||||||
|
|||||||
@@ -42,6 +42,3 @@ export function createLogger(prefix: string): Logger {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Logger for the plugin loader subsystem. */
|
|
||||||
export const pluginLoaderLog = createLogger("plugin-loader");
|
|
||||||
|
|||||||
@@ -15,30 +15,7 @@
|
|||||||
|
|
||||||
import type { ProjectSettings } from "./types.js";
|
import type { ProjectSettings } from "./types.js";
|
||||||
import type { ScheduledTaskCreateInput } from "./automation.js";
|
import type { ScheduledTaskCreateInput } from "./automation.js";
|
||||||
|
import { getKbAgent, type AgentMessage } from "./ai-engine-loader.js";
|
||||||
// Dynamic import for @fusion/engine to avoid resolution issues in test environment
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
type AgentResult = any;
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
let createKbAgent: any;
|
|
||||||
|
|
||||||
// Initialize the import (this runs in actual server, mocked in tests)
|
|
||||||
async function initEngine() {
|
|
||||||
if (!createKbAgent) {
|
|
||||||
try {
|
|
||||||
// Use dynamic import with variable to prevent static analysis
|
|
||||||
const engineModule = "@fusion/engine";
|
|
||||||
const engine = await import(/* @vite-ignore */ engineModule);
|
|
||||||
createKbAgent = engine.createKbAgent;
|
|
||||||
} catch {
|
|
||||||
// Allow failure in test environments - agent functionality will be stubbed
|
|
||||||
createKbAgent = undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize on module load (will be awaited in actual usage)
|
|
||||||
const engineReady = initEngine();
|
|
||||||
|
|
||||||
// ── Constants ───────────────────────────────────────────────────────────────
|
// ── Constants ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
@@ -102,9 +79,7 @@ export async function compactMemoryWithAi(
|
|||||||
provider?: string,
|
provider?: string,
|
||||||
modelId?: string
|
modelId?: string
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
// Ensure engine is loaded before using createKbAgent
|
const createKbAgent = await getKbAgent();
|
||||||
await engineReady;
|
|
||||||
|
|
||||||
if (!createKbAgent) {
|
if (!createKbAgent) {
|
||||||
if (DEBUG) console.log("[memory-compaction] AI engine not available");
|
if (DEBUG) console.log("[memory-compaction] AI engine not available");
|
||||||
throw new AiServiceError("AI engine not available");
|
throw new AiServiceError("AI engine not available");
|
||||||
@@ -151,12 +126,6 @@ export async function compactMemoryWithAi(
|
|||||||
|
|
||||||
if (DEBUG) console.log("[memory-compaction] Prompt sent, extracting response from messages...");
|
if (DEBUG) console.log("[memory-compaction] Prompt sent, extracting response from messages...");
|
||||||
|
|
||||||
// Get the response text from the agent's state
|
|
||||||
interface AgentMessage {
|
|
||||||
role: string;
|
|
||||||
content?: string | Array<{ type: string; text: string }>;
|
|
||||||
}
|
|
||||||
|
|
||||||
const messages: AgentMessage[] = agentResult.session.state?.messages ?? [];
|
const messages: AgentMessage[] = agentResult.session.state?.messages ?? [];
|
||||||
const assistantMessages = messages.filter((m: AgentMessage) => m.role === "assistant");
|
const assistantMessages = messages.filter((m: AgentMessage) => m.role === "assistant");
|
||||||
|
|
||||||
|
|||||||
@@ -194,31 +194,7 @@ export class MessageStore extends EventEmitter<MessageStoreEvents> {
|
|||||||
ownerType: ParticipantType,
|
ownerType: ParticipantType,
|
||||||
filter?: MessageFilter,
|
filter?: MessageFilter,
|
||||||
): Message[] {
|
): Message[] {
|
||||||
const whereClauses: string[] = ["toId = ?", "toType = ?"];
|
return this.queryMessagesByParticipant("to", ownerId, ownerType, filter);
|
||||||
const params: (string | number)[] = [ownerId, ownerType];
|
|
||||||
|
|
||||||
if (filter?.type) {
|
|
||||||
whereClauses.push("type = ?");
|
|
||||||
params.push(filter.type);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (filter?.read !== undefined) {
|
|
||||||
whereClauses.push("read = ?");
|
|
||||||
params.push(filter.read ? 1 : 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
const whereSql = whereClauses.join(" AND ");
|
|
||||||
const limit = filter?.limit ?? 100;
|
|
||||||
const offset = filter?.offset ?? 0;
|
|
||||||
|
|
||||||
const rows = this.db.prepare(`
|
|
||||||
SELECT * FROM messages
|
|
||||||
WHERE ${whereSql}
|
|
||||||
ORDER BY createdAt DESC, rowid DESC
|
|
||||||
LIMIT ? OFFSET ?
|
|
||||||
`).all(...params, limit, offset);
|
|
||||||
|
|
||||||
return (rows as any[]).map((row) => this.rowToMessage(row));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -233,7 +209,18 @@ export class MessageStore extends EventEmitter<MessageStoreEvents> {
|
|||||||
ownerType: ParticipantType,
|
ownerType: ParticipantType,
|
||||||
filter?: MessageFilter,
|
filter?: MessageFilter,
|
||||||
): Message[] {
|
): Message[] {
|
||||||
const whereClauses: string[] = ["fromId = ?", "fromType = ?"];
|
return this.queryMessagesByParticipant("from", ownerId, ownerType, filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
private queryMessagesByParticipant(
|
||||||
|
direction: "to" | "from",
|
||||||
|
ownerId: string,
|
||||||
|
ownerType: ParticipantType,
|
||||||
|
filter?: MessageFilter,
|
||||||
|
): Message[] {
|
||||||
|
const idCol = direction === "to" ? "toId" : "fromId";
|
||||||
|
const typeCol = direction === "to" ? "toType" : "fromType";
|
||||||
|
const whereClauses: string[] = [`${idCol} = ?`, `${typeCol} = ?`];
|
||||||
const params: (string | number)[] = [ownerId, ownerType];
|
const params: (string | number)[] = [ownerId, ownerType];
|
||||||
|
|
||||||
if (filter?.type) {
|
if (filter?.type) {
|
||||||
|
|||||||
@@ -17,6 +17,26 @@ import { isAbsolute, join, resolve, basename, dirname } from "node:path";
|
|||||||
import type { CentralCore } from "./central-core.js";
|
import type { CentralCore } from "./central-core.js";
|
||||||
import { CentralCore as CentralCoreClass } from "./central-core.js";
|
import { CentralCore as CentralCoreClass } from "./central-core.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether `<dir>/<folderName>/<dbName>` exists as a non-empty regular file.
|
||||||
|
* Used to decide if a directory contains either a legacy (.kb/kb.db) or
|
||||||
|
* current (.fusion/fusion.db) project database.
|
||||||
|
*/
|
||||||
|
function hasProjectDbFile(dir: string, folderName: string, dbName: string): boolean {
|
||||||
|
const projectDir = join(dir, folderName);
|
||||||
|
const dbPath = join(projectDir, dbName);
|
||||||
|
|
||||||
|
if (!existsSync(projectDir)) return false;
|
||||||
|
if (!existsSync(dbPath)) return false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const stat = statSync(dbPath);
|
||||||
|
return stat.isFile() && stat.size > 0;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ── Types ────────────────────────────────────────────────────────────
|
// ── Types ────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
/** First-run state detection results */
|
/** First-run state detection results */
|
||||||
@@ -274,23 +294,8 @@ export class FirstRunDetector {
|
|||||||
*/
|
*/
|
||||||
private hasKbProject(dir: string): boolean {
|
private hasKbProject(dir: string): boolean {
|
||||||
// Check for current .fusion/fusion.db or legacy .kb/kb.db
|
// Check for current .fusion/fusion.db or legacy .kb/kb.db
|
||||||
return this.hasProjectDbFile(dir, ".fusion", "fusion.db") ||
|
return hasProjectDbFile(dir, ".fusion", "fusion.db") ||
|
||||||
this.hasProjectDbFile(dir, ".kb", "kb.db");
|
hasProjectDbFile(dir, ".kb", "kb.db");
|
||||||
}
|
|
||||||
|
|
||||||
private hasProjectDbFile(dir: string, folderName: string, dbName: string): boolean {
|
|
||||||
const projectDir = join(dir, folderName);
|
|
||||||
const dbPath = join(projectDir, dbName);
|
|
||||||
|
|
||||||
if (!existsSync(projectDir)) return false;
|
|
||||||
if (!existsSync(dbPath)) return false;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const stat = statSync(dbPath);
|
|
||||||
return stat.isFile() && stat.size > 0;
|
|
||||||
} catch {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private getDefaultGlobalDir(): string {
|
private getDefaultGlobalDir(): string {
|
||||||
@@ -532,21 +537,8 @@ export class MigrationCoordinator {
|
|||||||
* Check if a directory is a valid kb project (has .fusion/fusion.db or .kb/kb.db).
|
* Check if a directory is a valid kb project (has .fusion/fusion.db or .kb/kb.db).
|
||||||
*/
|
*/
|
||||||
private isValidKbProject(dir: string): boolean {
|
private isValidKbProject(dir: string): boolean {
|
||||||
return this.hasProjectDbInDir(dir, ".fusion", "fusion.db") ||
|
return hasProjectDbFile(dir, ".fusion", "fusion.db") ||
|
||||||
this.hasProjectDbInDir(dir, ".kb", "kb.db");
|
hasProjectDbFile(dir, ".kb", "kb.db");
|
||||||
}
|
|
||||||
|
|
||||||
private hasProjectDbInDir(dir: string, folderName: string, dbName: string): boolean {
|
|
||||||
const projectDir = join(dir, folderName);
|
|
||||||
const dbPath = join(projectDir, dbName);
|
|
||||||
if (!existsSync(projectDir)) return false;
|
|
||||||
if (!existsSync(dbPath)) return false;
|
|
||||||
try {
|
|
||||||
const stat = statSync(dbPath);
|
|
||||||
return stat.isFile() && stat.size > 0;
|
|
||||||
} catch {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -674,22 +666,7 @@ export class BackwardCompat {
|
|||||||
* Check if a directory contains a current .fusion project or legacy .kb project.
|
* Check if a directory contains a current .fusion project or legacy .kb project.
|
||||||
*/
|
*/
|
||||||
private hasProjectData(dir: string): boolean {
|
private hasProjectData(dir: string): boolean {
|
||||||
return this.hasProjectDb(dir, ".fusion") || this.hasProjectDb(dir, ".kb");
|
return hasProjectDbFile(dir, ".fusion", "fusion.db") ||
|
||||||
}
|
hasProjectDbFile(dir, ".kb", "kb.db");
|
||||||
|
|
||||||
private hasProjectDb(dir: string, folderName: ".fusion" | ".kb"): boolean {
|
|
||||||
const projectDir = join(dir, folderName);
|
|
||||||
const dbName = folderName === ".fusion" ? "fusion.db" : "kb.db";
|
|
||||||
const dbPath = join(projectDir, dbName);
|
|
||||||
|
|
||||||
if (!existsSync(projectDir)) return false;
|
|
||||||
if (!existsSync(dbPath)) return false;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const stat = statSync(dbPath);
|
|
||||||
return stat.isFile() && stat.size > 0;
|
|
||||||
} catch {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -520,7 +520,7 @@ export class PluginLoader extends EventEmitter<{
|
|||||||
await this.loadPlugin(installation.id);
|
await this.loadPlugin(installation.id);
|
||||||
loaded++;
|
loaded++;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if ((err as any).code !== "PLUGIN_DISABLED") {
|
if ((err as { code?: string }).code !== "PLUGIN_DISABLED") {
|
||||||
errors++;
|
errors++;
|
||||||
log.error(
|
log.error(
|
||||||
`Failed to load plugin ${installation.id}:`,
|
`Failed to load plugin ${installation.id}:`,
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ function formatModelTag(provider?: string | null, modelId?: string | null): stri
|
|||||||
|
|
||||||
// Gemini models: "gemini-2.5-pro" -> "Gemini 2.5 Pro"
|
// Gemini models: "gemini-2.5-pro" -> "Gemini 2.5 Pro"
|
||||||
if (normalizedModel.includes("gemini")) {
|
if (normalizedModel.includes("gemini")) {
|
||||||
let formatted = modelId
|
const formatted = modelId
|
||||||
.replace(/^gemini[- ]/i, "Gemini ")
|
.replace(/^gemini[- ]/i, "Gemini ")
|
||||||
.replace(/pro[- ](\d+)[- ](\d+)/i, "Pro $1.$2")
|
.replace(/pro[- ](\d+)[- ](\d+)/i, "Pro $1.$2")
|
||||||
.replace(/pro[- ](\d+)/i, "Pro $1")
|
.replace(/pro[- ](\d+)/i, "Pro $1")
|
||||||
@@ -100,7 +100,7 @@ function formatModelTag(provider?: string | null, modelId?: string | null): stri
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Generic fallback: capitalize first letter, replace hyphens with spaces
|
// Generic fallback: capitalize first letter, replace hyphens with spaces
|
||||||
let formatted = modelId
|
const formatted = modelId
|
||||||
.replace(/-/g, " ")
|
.replace(/-/g, " ")
|
||||||
.replace(/^\w/, (c) => c.toUpperCase())
|
.replace(/^\w/, (c) => c.toUpperCase())
|
||||||
.replace(/\s+/g, " ")
|
.replace(/\s+/g, " ")
|
||||||
|
|||||||
@@ -255,8 +255,8 @@ export interface MissionWithHierarchy extends Mission {
|
|||||||
export type MissionEventType = CoreMissionEventType;
|
export type MissionEventType = CoreMissionEventType;
|
||||||
|
|
||||||
/** Mission lifecycle event persisted in the mission event log. */
|
/** Mission lifecycle event persisted in the mission event log. */
|
||||||
export interface MissionEvent extends CoreMissionEvent {}
|
export type MissionEvent = CoreMissionEvent
|
||||||
|
|
||||||
/** Computed mission health snapshot returned by observability APIs. */
|
/** Computed mission health snapshot returned by observability APIs. */
|
||||||
export interface MissionHealth extends CoreMissionHealth {}
|
export type MissionHealth = CoreMissionHealth
|
||||||
|
|
||||||
|
|||||||
@@ -144,7 +144,7 @@ export function saveOnboardingState(
|
|||||||
|
|
||||||
// Determine completed and dismissed flags
|
// Determine completed and dismissed flags
|
||||||
// completed takes precedence over dismissed
|
// completed takes precedence over dismissed
|
||||||
let completed = options.completed ?? DEFAULT_COMPLETED;
|
const completed = options.completed ?? DEFAULT_COMPLETED;
|
||||||
let dismissed = options.dismissed ?? DEFAULT_DISMISSED;
|
let dismissed = options.dismissed ?? DEFAULT_DISMISSED;
|
||||||
|
|
||||||
// If completed is true, dismissed should be false
|
// If completed is true, dismissed should be false
|
||||||
|
|||||||
@@ -299,7 +299,7 @@ export function useInsights(projectId?: string): UseInsightsResult {
|
|||||||
}, [sections]);
|
}, [sections]);
|
||||||
|
|
||||||
// Initial load - intentionally runs once on mount
|
// Initial load - intentionally runs once on mount
|
||||||
// eslint-disable-next-line
|
|
||||||
useMemo(() => {
|
useMemo(() => {
|
||||||
void refresh();
|
void refresh();
|
||||||
}, []);
|
}, []);
|
||||||
|
|||||||
@@ -466,7 +466,7 @@ export class ProjectEngine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Intentional cast to access Task properties needed by merge validation
|
// Intentional cast to access Task properties needed by merge validation
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
if (!this.canMergeTask(task as any)) {
|
if (!this.canMergeTask(task as any)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -488,7 +488,7 @@ export class ProjectEngine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Auto-heal verification buffer failures by resetting retry counter
|
// Auto-heal verification buffer failures by resetting retry counter
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
if (this.hasAutoHealableVerificationBufferFailure(task as any)) {
|
if (this.hasAutoHealableVerificationBufferFailure(task as any)) {
|
||||||
await store.logEntry(
|
await store.logEntry(
|
||||||
taskId,
|
taskId,
|
||||||
@@ -497,7 +497,7 @@ export class ProjectEngine {
|
|||||||
await store.updateTask(taskId, { mergeRetries: 0, error: null, status: null });
|
await store.updateTask(taskId, { mergeRetries: 0, error: null, status: null });
|
||||||
} else if (
|
} else if (
|
||||||
(task.mergeRetries ?? 0) >= ProjectEngine.MAX_AUTO_MERGE_RETRIES &&
|
(task.mergeRetries ?? 0) >= ProjectEngine.MAX_AUTO_MERGE_RETRIES &&
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
this.isRetryCooldownElapsed(task as any)
|
this.isRetryCooldownElapsed(task as any)
|
||||||
) {
|
) {
|
||||||
await store.logEntry(
|
await store.logEntry(
|
||||||
@@ -565,13 +565,13 @@ export class ProjectEngine {
|
|||||||
} else {
|
} else {
|
||||||
// Direct merge via AI agent, gated by semaphore
|
// Direct merge via AI agent, gated by semaphore
|
||||||
runtimeLog.log(`${manualResolver ? "Manual" : "Auto"}-merge merging ${taskId}...`);
|
runtimeLog.log(`${manualResolver ? "Manual" : "Auto"}-merge merging ${taskId}...`);
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
const semaphore = (this.runtime as any).globalSemaphore;
|
const semaphore = (this.runtime as any).globalSemaphore;
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
const pool = (this.runtime as any).worktreePool;
|
const pool = (this.runtime as any).worktreePool;
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
const agentStore = (this.runtime as any).agentStore;
|
const agentStore = (this.runtime as any).agentStore;
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
const usageLimitPauser = (this.runtime as any).usageLimitPauser;
|
const usageLimitPauser = (this.runtime as any).usageLimitPauser;
|
||||||
|
|
||||||
const rawMerge = () =>
|
const rawMerge = () =>
|
||||||
@@ -772,7 +772,7 @@ export class ProjectEngine {
|
|||||||
runtimeLog.log(`Startup sweep: clearing stale '${t.status}' status on ${t.id}`);
|
runtimeLog.log(`Startup sweep: clearing stale '${t.status}' status on ${t.id}`);
|
||||||
await store.updateTask(t.id, { status: null });
|
await store.updateTask(t.id, { status: null });
|
||||||
// Update in-memory object so canMergeTask sees the cleared status
|
// Update in-memory object so canMergeTask sees the cleared status
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
(t as any).status = null;
|
(t as any).status = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -780,7 +780,7 @@ export class ProjectEngine {
|
|||||||
const settings = await store.getSettings();
|
const settings = await store.getSettings();
|
||||||
if (!settings.autoMerge) return;
|
if (!settings.autoMerge) return;
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
const eligible = tasks.filter((t) => this.canMergeTask(t as any));
|
const eligible = tasks.filter((t) => this.canMergeTask(t as any));
|
||||||
if (eligible.length > 0) {
|
if (eligible.length > 0) {
|
||||||
runtimeLog.log(`Auto-merge startup sweep: enqueueing ${eligible.length} task(s)`);
|
runtimeLog.log(`Auto-merge startup sweep: enqueueing ${eligible.length} task(s)`);
|
||||||
@@ -806,7 +806,7 @@ export class ProjectEngine {
|
|||||||
if (!settings.globalPause && !settings.enginePaused && settings.autoMerge) {
|
if (!settings.globalPause && !settings.enginePaused && settings.autoMerge) {
|
||||||
const tasks = await store.listTasks({ column: "in-review" });
|
const tasks = await store.listTasks({ column: "in-review" });
|
||||||
for (const t of tasks) {
|
for (const t of tasks) {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
if (this.canMergeTask(t as any)) {
|
if (this.canMergeTask(t as any)) {
|
||||||
this.internalEnqueueMerge(t.id);
|
this.internalEnqueueMerge(t.id);
|
||||||
}
|
}
|
||||||
@@ -864,7 +864,7 @@ export class ProjectEngine {
|
|||||||
runtimeLog.log("Global unpause — resuming agentic activity");
|
runtimeLog.log("Global unpause — resuming agentic activity");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
const executor = (this.runtime as any).executor;
|
const executor = (this.runtime as any).executor;
|
||||||
executor?.resumeOrphaned?.().catch((err: Error) =>
|
executor?.resumeOrphaned?.().catch((err: Error) =>
|
||||||
runtimeLog.error("Failed to resume orphaned tasks on unpause:", err),
|
runtimeLog.error("Failed to resume orphaned tasks on unpause:", err),
|
||||||
@@ -879,7 +879,7 @@ export class ProjectEngine {
|
|||||||
try {
|
try {
|
||||||
const tasks = await store.listTasks({ column: "in-review" });
|
const tasks = await store.listTasks({ column: "in-review" });
|
||||||
for (const t of tasks) {
|
for (const t of tasks) {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
if (this.canMergeTask(t as any)) {
|
if (this.canMergeTask(t as any)) {
|
||||||
this.internalEnqueueMerge(t.id);
|
this.internalEnqueueMerge(t.id);
|
||||||
}
|
}
|
||||||
@@ -907,7 +907,7 @@ export class ProjectEngine {
|
|||||||
runtimeLog.log("Engine unpaused — resuming agentic activity");
|
runtimeLog.log("Engine unpaused — resuming agentic activity");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
const executor = (this.runtime as any).executor;
|
const executor = (this.runtime as any).executor;
|
||||||
executor?.resumeOrphaned?.().catch((err: Error) =>
|
executor?.resumeOrphaned?.().catch((err: Error) =>
|
||||||
runtimeLog.error("Failed to resume orphaned tasks on engine unpause:", err),
|
runtimeLog.error("Failed to resume orphaned tasks on engine unpause:", err),
|
||||||
@@ -922,7 +922,7 @@ export class ProjectEngine {
|
|||||||
try {
|
try {
|
||||||
const tasks = await store.listTasks({ column: "in-review" });
|
const tasks = await store.listTasks({ column: "in-review" });
|
||||||
for (const t of tasks) {
|
for (const t of tasks) {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
if (this.canMergeTask(t as any)) {
|
if (this.canMergeTask(t as any)) {
|
||||||
this.internalEnqueueMerge(t.id);
|
this.internalEnqueueMerge(t.id);
|
||||||
}
|
}
|
||||||
@@ -951,7 +951,7 @@ export class ProjectEngine {
|
|||||||
`Stuck task timeout changed to ${s.taskStuckTimeoutMs}ms — running immediate check`,
|
`Stuck task timeout changed to ${s.taskStuckTimeoutMs}ms — running immediate check`,
|
||||||
);
|
);
|
||||||
try {
|
try {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
const detector = (this.runtime as any).stuckTaskDetector;
|
const detector = (this.runtime as any).stuckTaskDetector;
|
||||||
await detector?.checkNow?.();
|
await detector?.checkNow?.();
|
||||||
} catch (err: unknown) {
|
} catch (err: unknown) {
|
||||||
@@ -982,9 +982,9 @@ export class ProjectEngine {
|
|||||||
"memoryDreamsSchedule",
|
"memoryDreamsSchedule",
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
const changed = insightKeys.some((key) => (s as any)[key] !== (prev as any)[key]);
|
const changed = insightKeys.some((key) => (s as any)[key] !== (prev as any)[key]);
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
const dreamsChanged = dreamKeys.some((key) => (s as any)[key] !== (prev as any)[key]);
|
const dreamsChanged = dreamKeys.some((key) => (s as any)[key] !== (prev as any)[key]);
|
||||||
if ((!changed && !dreamsChanged) || !this.automationStore) return;
|
if ((!changed && !dreamsChanged) || !this.automationStore) return;
|
||||||
|
|
||||||
@@ -1022,7 +1022,7 @@ export class ProjectEngine {
|
|||||||
"memoryAutoSummarizeSchedule",
|
"memoryAutoSummarizeSchedule",
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
const changed = autoSummarizeKeys.some((key) => (s as any)[key] !== (prev as any)[key]);
|
const changed = autoSummarizeKeys.some((key) => (s as any)[key] !== (prev as any)[key]);
|
||||||
if (!changed || !this.automationStore) return;
|
if (!changed || !this.automationStore) return;
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,6 @@
|
|||||||
* by cleaning oldest idle worktrees when count exceeds 2× maxWorktrees.
|
* by cleaning oldest idle worktrees when count exceeds 2× maxWorktrees.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
||||||
import { exec } from "node:child_process";
|
import { exec } from "node:child_process";
|
||||||
import { promisify } from "node:util";
|
import { promisify } from "node:util";
|
||||||
import { existsSync, readdirSync, statSync } from "node:fs";
|
import { existsSync, readdirSync, statSync } from "node:fs";
|
||||||
|
|||||||
@@ -204,24 +204,27 @@ export async function buildSessionSkillContext(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rule 2: Use role fallback skills
|
return resolveRoleFallback(sessionPurpose, projectRootDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveRoleFallback(
|
||||||
|
sessionPurpose: SessionPurpose,
|
||||||
|
projectRootDir: string,
|
||||||
|
): SessionSkillContextResult {
|
||||||
const roleFallbackSkills = getRoleFallbackSkills(sessionPurpose);
|
const roleFallbackSkills = getRoleFallbackSkills(sessionPurpose);
|
||||||
|
|
||||||
if (roleFallbackSkills && roleFallbackSkills.length > 0) {
|
if (roleFallbackSkills && roleFallbackSkills.length > 0) {
|
||||||
const skillSelectionContext: SkillSelectionContext = {
|
|
||||||
projectRootDir,
|
|
||||||
requestedSkillNames: roleFallbackSkills,
|
|
||||||
sessionPurpose,
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
skillSelectionContext,
|
skillSelectionContext: {
|
||||||
|
projectRootDir,
|
||||||
|
requestedSkillNames: roleFallbackSkills,
|
||||||
|
sessionPurpose,
|
||||||
|
},
|
||||||
resolvedSkillNames: roleFallbackSkills,
|
resolvedSkillNames: roleFallbackSkills,
|
||||||
skillSource: "role-fallback",
|
skillSource: "role-fallback",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rule 3: No skills available
|
|
||||||
return {
|
return {
|
||||||
skillSelectionContext: undefined,
|
skillSelectionContext: undefined,
|
||||||
resolvedSkillNames: [],
|
resolvedSkillNames: [],
|
||||||
@@ -263,27 +266,5 @@ export function buildSessionSkillContextSync(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rule 2: Use role fallback skills
|
return resolveRoleFallback(sessionPurpose, projectRootDir);
|
||||||
const roleFallbackSkills = getRoleFallbackSkills(sessionPurpose);
|
|
||||||
|
|
||||||
if (roleFallbackSkills && roleFallbackSkills.length > 0) {
|
|
||||||
const skillSelectionContext: SkillSelectionContext = {
|
|
||||||
projectRootDir,
|
|
||||||
requestedSkillNames: roleFallbackSkills,
|
|
||||||
sessionPurpose,
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
skillSelectionContext,
|
|
||||||
resolvedSkillNames: roleFallbackSkills,
|
|
||||||
skillSource: "role-fallback",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rule 3: No skills available
|
|
||||||
return {
|
|
||||||
skillSelectionContext: undefined,
|
|
||||||
resolvedSkillNames: [],
|
|
||||||
skillSource: "none",
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1364,7 +1364,7 @@ export class TriageProcessor {
|
|||||||
// Re-read task detail to get latest user comments for the reviewer
|
// Re-read task detail to get latest user comments for the reviewer
|
||||||
const currentDetail = await store.getTask(taskId);
|
const currentDetail = await store.getTask(taskId);
|
||||||
const currentUserComments = (currentDetail.comments || []).filter(
|
const currentUserComments = (currentDetail.comments || []).filter(
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
(c: any) => c.author === "user",
|
(c: any) => c.author === "user",
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -1510,7 +1510,7 @@ export class TriageProcessor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const parsedDeps = await this.store.parseDependenciesFromPrompt(task.id);
|
const parsedDeps = await this.store.parseDependenciesFromPrompt(task.id);
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
const taskUpdates: Record<string, any> = { status: null, error: null };
|
const taskUpdates: Record<string, any> = { status: null, error: null };
|
||||||
|
|
||||||
if (parsedDeps.length > 0) {
|
if (parsedDeps.length > 0) {
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
||||||
import { exec } from "node:child_process";
|
import { exec } from "node:child_process";
|
||||||
import { promisify } from "node:util";
|
import { promisify } from "node:util";
|
||||||
import { existsSync, readdirSync, rmSync } from "node:fs";
|
import { existsSync, readdirSync, rmSync } from "node:fs";
|
||||||
|
|||||||
Reference in New Issue
Block a user