@probelabs/visor 0.1.147 → 0.1.148
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/frontends/github-frontend.d.ts +2 -1
- package/dist/frontends/github-frontend.d.ts.map +1 -1
- package/dist/index.js +726 -113
- package/dist/output/traces/{run-2026-02-27T11-27-22-261Z.ndjson → run-2026-03-02T18-32-11-359Z.ndjson} +84 -84
- package/dist/{traces/run-2026-02-27T11-28-08-546Z.ndjson → output/traces/run-2026-03-02T18-32-55-702Z.ndjson} +1171 -1171
- package/dist/providers/ai-check-provider.d.ts.map +1 -1
- package/dist/scheduler/schedule-tool.d.ts.map +1 -1
- package/dist/scheduler/scheduler.d.ts +5 -0
- package/dist/scheduler/scheduler.d.ts.map +1 -1
- package/dist/sdk/{check-provider-registry-CTZA3EVE.mjs → check-provider-registry-35BPTY4W.mjs} +5 -6
- package/dist/sdk/{check-provider-registry-SCPM6DIT.mjs → check-provider-registry-DVQDGTOE.mjs} +5 -6
- package/dist/sdk/{check-provider-registry-CDL5AJSI.mjs → check-provider-registry-KHPY6LB4.mjs} +5 -6
- package/dist/sdk/{chunk-4F5UVWAN.mjs → chunk-62TNF5PJ.mjs} +2 -2
- package/dist/sdk/{chunk-4F5UVWAN.mjs.map → chunk-62TNF5PJ.mjs.map} +1 -1
- package/dist/sdk/{chunk-H23T7J6Y.mjs → chunk-6N6JRWCW.mjs} +2742 -276
- package/dist/sdk/chunk-6N6JRWCW.mjs.map +1 -0
- package/dist/sdk/{chunk-JKWLGLDR.mjs → chunk-AYQE4JCU.mjs} +3 -3
- package/dist/sdk/{chunk-FBJ7MC7R.mjs → chunk-CISJ6DJW.mjs} +3 -3
- package/dist/sdk/{chunk-YQZW3D2V.mjs → chunk-EGUHXVWS.mjs} +3 -3
- package/dist/sdk/{chunk-YQZW3D2V.mjs.map → chunk-EGUHXVWS.mjs.map} +1 -1
- package/dist/sdk/{chunk-EWGX7LI7.mjs → chunk-H4AYMOAT.mjs} +2742 -276
- package/dist/sdk/chunk-H4AYMOAT.mjs.map +1 -0
- package/dist/sdk/{chunk-R77LN3OE.mjs → chunk-IF2UD2KS.mjs} +2742 -276
- package/dist/sdk/chunk-IF2UD2KS.mjs.map +1 -0
- package/dist/sdk/{chunk-2NFKN6CY.mjs → chunk-RJLJUTSU.mjs} +2 -2
- package/dist/sdk/{chunk-V2QW6ECX.mjs → chunk-S2YO4ZE3.mjs} +2 -2
- package/dist/sdk/{failure-condition-evaluator-FHNZL2US.mjs → failure-condition-evaluator-I6QWFKV3.mjs} +3 -3
- package/dist/sdk/{failure-condition-evaluator-2B5WY7QN.mjs → failure-condition-evaluator-IVCTD4BZ.mjs} +3 -3
- package/dist/sdk/{github-frontend-V3WUHL6E.mjs → github-frontend-2MC77L7F.mjs} +16 -4
- package/dist/sdk/github-frontend-2MC77L7F.mjs.map +1 -0
- package/dist/sdk/{github-frontend-47EU2HBY.mjs → github-frontend-DFT5G32K.mjs} +16 -4
- package/dist/sdk/github-frontend-DFT5G32K.mjs.map +1 -0
- package/dist/sdk/{host-GVR4UGZ3.mjs → host-4F6I3ZXN.mjs} +2 -2
- package/dist/sdk/{host-UQUQIYFG.mjs → host-H7IX4GBK.mjs} +2 -2
- package/dist/sdk/{routing-CZ36LVVS.mjs → routing-LU5PAREW.mjs} +4 -4
- package/dist/sdk/{routing-THIWDEYY.mjs → routing-UT3BXBXH.mjs} +4 -4
- package/dist/sdk/schedule-tool-CONR4VW3.mjs +35 -0
- package/dist/sdk/schedule-tool-K3GQXCBN.mjs +35 -0
- package/dist/sdk/schedule-tool-SBXAEBDD.mjs +35 -0
- package/dist/sdk/{schedule-tool-handler-KFYNV7HL.mjs → schedule-tool-handler-GFQCJAVZ.mjs} +5 -6
- package/dist/sdk/{schedule-tool-handler-QUMAF2DJ.mjs → schedule-tool-handler-R7PG3VMR.mjs} +5 -6
- package/dist/sdk/{schedule-tool-handler-GEH62OUM.mjs → schedule-tool-handler-YUC6CAXX.mjs} +5 -6
- package/dist/sdk/sdk.js +1551 -349
- package/dist/sdk/sdk.js.map +1 -1
- package/dist/sdk/sdk.mjs +4 -5
- package/dist/sdk/sdk.mjs.map +1 -1
- package/dist/sdk/{trace-helpers-W7TF5ZKF.mjs → trace-helpers-6ROJR7N3.mjs} +2 -2
- package/dist/sdk/{trace-helpers-EHDZ42HH.mjs → trace-helpers-J463EU4B.mjs} +2 -2
- package/dist/sdk/{workflow-check-provider-5453TW65.mjs → workflow-check-provider-DYSO3PML.mjs} +5 -6
- package/dist/sdk/{workflow-check-provider-HMABCGB5.mjs → workflow-check-provider-FIFFQDQU.mjs} +5 -6
- package/dist/sdk/workflow-check-provider-FIFFQDQU.mjs.map +1 -0
- package/dist/sdk/{workflow-check-provider-3K7732MW.mjs → workflow-check-provider-GJNGTS3F.mjs} +5 -6
- package/dist/sdk/workflow-check-provider-GJNGTS3F.mjs.map +1 -0
- package/dist/state-machine/context/build-engine-context.d.ts.map +1 -1
- package/dist/traces/{run-2026-02-27T11-27-22-261Z.ndjson → run-2026-03-02T18-32-11-359Z.ndjson} +84 -84
- package/dist/{output/traces/run-2026-02-27T11-28-08-546Z.ndjson → traces/run-2026-03-02T18-32-55-702Z.ndjson} +1171 -1171
- package/dist/utils/tool-resolver.d.ts.map +1 -1
- package/dist/utils/workspace-manager.d.ts +31 -8
- package/dist/utils/workspace-manager.d.ts.map +1 -1
- package/dist/utils/worktree-manager.d.ts +6 -0
- package/dist/utils/worktree-manager.d.ts.map +1 -1
- package/package.json +2 -2
- package/dist/sdk/chunk-EWGX7LI7.mjs.map +0 -1
- package/dist/sdk/chunk-H23T7J6Y.mjs.map +0 -1
- package/dist/sdk/chunk-R77LN3OE.mjs.map +0 -1
- package/dist/sdk/chunk-XKCER23W.mjs +0 -1490
- package/dist/sdk/chunk-XKCER23W.mjs.map +0 -1
- package/dist/sdk/github-frontend-47EU2HBY.mjs.map +0 -1
- package/dist/sdk/github-frontend-V3WUHL6E.mjs.map +0 -1
- package/dist/sdk/schedule-tool-2COUUTF7.mjs +0 -18
- /package/dist/sdk/{check-provider-registry-CDL5AJSI.mjs.map → check-provider-registry-35BPTY4W.mjs.map} +0 -0
- /package/dist/sdk/{check-provider-registry-CTZA3EVE.mjs.map → check-provider-registry-DVQDGTOE.mjs.map} +0 -0
- /package/dist/sdk/{check-provider-registry-SCPM6DIT.mjs.map → check-provider-registry-KHPY6LB4.mjs.map} +0 -0
- /package/dist/sdk/{chunk-FBJ7MC7R.mjs.map → chunk-AYQE4JCU.mjs.map} +0 -0
- /package/dist/sdk/{chunk-JKWLGLDR.mjs.map → chunk-CISJ6DJW.mjs.map} +0 -0
- /package/dist/sdk/{chunk-2NFKN6CY.mjs.map → chunk-RJLJUTSU.mjs.map} +0 -0
- /package/dist/sdk/{chunk-V2QW6ECX.mjs.map → chunk-S2YO4ZE3.mjs.map} +0 -0
- /package/dist/sdk/{failure-condition-evaluator-2B5WY7QN.mjs.map → failure-condition-evaluator-I6QWFKV3.mjs.map} +0 -0
- /package/dist/sdk/{failure-condition-evaluator-FHNZL2US.mjs.map → failure-condition-evaluator-IVCTD4BZ.mjs.map} +0 -0
- /package/dist/sdk/{host-GVR4UGZ3.mjs.map → host-4F6I3ZXN.mjs.map} +0 -0
- /package/dist/sdk/{host-UQUQIYFG.mjs.map → host-H7IX4GBK.mjs.map} +0 -0
- /package/dist/sdk/{routing-CZ36LVVS.mjs.map → routing-LU5PAREW.mjs.map} +0 -0
- /package/dist/sdk/{routing-THIWDEYY.mjs.map → routing-UT3BXBXH.mjs.map} +0 -0
- /package/dist/sdk/{schedule-tool-2COUUTF7.mjs.map → schedule-tool-CONR4VW3.mjs.map} +0 -0
- /package/dist/sdk/{schedule-tool-handler-GEH62OUM.mjs.map → schedule-tool-K3GQXCBN.mjs.map} +0 -0
- /package/dist/sdk/{schedule-tool-handler-KFYNV7HL.mjs.map → schedule-tool-SBXAEBDD.mjs.map} +0 -0
- /package/dist/sdk/{schedule-tool-handler-QUMAF2DJ.mjs.map → schedule-tool-handler-GFQCJAVZ.mjs.map} +0 -0
- /package/dist/sdk/{trace-helpers-EHDZ42HH.mjs.map → schedule-tool-handler-R7PG3VMR.mjs.map} +0 -0
- /package/dist/sdk/{trace-helpers-W7TF5ZKF.mjs.map → schedule-tool-handler-YUC6CAXX.mjs.map} +0 -0
- /package/dist/sdk/{workflow-check-provider-3K7732MW.mjs.map → trace-helpers-6ROJR7N3.mjs.map} +0 -0
- /package/dist/sdk/{workflow-check-provider-5453TW65.mjs.map → trace-helpers-J463EU4B.mjs.map} +0 -0
- /package/dist/sdk/{workflow-check-provider-HMABCGB5.mjs.map → workflow-check-provider-DYSO3PML.mjs.map} +0 -0
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/scheduler/store/sqlite-store.ts","../../src/scheduler/store/index.ts","../../src/scheduler/store/json-migrator.ts","../../src/scheduler/schedule-store.ts","../../src/scheduler/schedule-parser.ts","../../src/scheduler/schedule-tool.ts"],"sourcesContent":["/**\n * SQLite-backed schedule store (OSS default)\n *\n * Uses better-sqlite3 loaded via createRequire to avoid ncc bundling the native addon.\n * Schema migration is inline (no external migration files — ncc-safe).\n * WAL mode is enabled for better concurrent read performance.\n *\n * HA locking is in-memory (Map-based) since SQLite is single-node only.\n */\nimport path from 'path';\nimport fs from 'fs';\nimport { v4 as uuidv4 } from 'uuid';\nimport { logger } from '../../logger';\nimport type { Schedule, ScheduleLimits } from '../schedule-store';\nimport type { ScheduleStoreBackend, ScheduleStoreStats } from './types';\n\n// Type declarations for better-sqlite3 (avoid importing the module at compile time)\ninterface BetterSqliteDatabase {\n pragma(source: string): unknown;\n exec(source: string): void;\n prepare(source: string): BetterSqliteStatement;\n close(): void;\n}\n\ninterface BetterSqliteStatement {\n run(...params: unknown[]): { changes: number };\n get(...params: unknown[]): unknown;\n all(...params: unknown[]): unknown[];\n}\n\n/**\n * Database row shape (snake_case)\n */\ninterface ScheduleRow {\n id: string;\n creator_id: string;\n creator_context: string | null;\n creator_name: string | null;\n timezone: string;\n schedule_expr: string;\n run_at: number | null;\n is_recurring: number; // SQLite boolean: 0/1\n original_expression: string;\n workflow: string | null;\n workflow_inputs: string | null; // JSON\n output_context: string | null; // JSON\n status: string;\n created_at: number;\n last_run_at: number | null;\n next_run_at: number | null;\n run_count: number;\n failure_count: number;\n last_error: string | null;\n previous_response: string | null;\n}\n\n/**\n * Convert a Schedule object to a database row\n */\nfunction toDbRow(schedule: Schedule): ScheduleRow {\n return {\n id: schedule.id,\n creator_id: schedule.creatorId,\n creator_context: schedule.creatorContext ?? null,\n creator_name: schedule.creatorName ?? null,\n timezone: schedule.timezone,\n schedule_expr: schedule.schedule,\n run_at: schedule.runAt ?? null,\n is_recurring: schedule.isRecurring ? 1 : 0,\n original_expression: schedule.originalExpression,\n workflow: schedule.workflow ?? null,\n workflow_inputs: schedule.workflowInputs ? JSON.stringify(schedule.workflowInputs) : null,\n output_context: schedule.outputContext ? JSON.stringify(schedule.outputContext) : null,\n status: schedule.status,\n created_at: schedule.createdAt,\n last_run_at: schedule.lastRunAt ?? null,\n next_run_at: schedule.nextRunAt ?? null,\n run_count: schedule.runCount,\n failure_count: schedule.failureCount,\n last_error: schedule.lastError ?? null,\n previous_response: schedule.previousResponse ?? null,\n };\n}\n\nfunction safeJsonParse<T = unknown>(value: string | null): T | undefined {\n if (!value) return undefined;\n try {\n return JSON.parse(value) as T;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Convert a database row to a Schedule object\n */\nfunction fromDbRow(row: ScheduleRow): Schedule {\n return {\n id: row.id,\n creatorId: row.creator_id,\n creatorContext: row.creator_context ?? undefined,\n creatorName: row.creator_name ?? undefined,\n timezone: row.timezone,\n schedule: row.schedule_expr,\n runAt: row.run_at ?? undefined,\n isRecurring: row.is_recurring === 1,\n originalExpression: row.original_expression,\n workflow: row.workflow ?? undefined,\n workflowInputs: safeJsonParse(row.workflow_inputs),\n outputContext: safeJsonParse(row.output_context),\n status: row.status as Schedule['status'],\n createdAt: row.created_at,\n lastRunAt: row.last_run_at ?? undefined,\n nextRunAt: row.next_run_at ?? undefined,\n runCount: row.run_count,\n failureCount: row.failure_count,\n lastError: row.last_error ?? undefined,\n previousResponse: row.previous_response ?? undefined,\n };\n}\n\n/**\n * SQLite implementation of ScheduleStoreBackend\n */\nexport class SqliteStoreBackend implements ScheduleStoreBackend {\n private db: BetterSqliteDatabase | null = null;\n private dbPath: string;\n\n // In-memory locks (single-node only; SQLite doesn't support distributed locking)\n private locks = new Map<string, { nodeId: string; token: string; expiresAt: number }>();\n\n constructor(filename?: string) {\n this.dbPath = filename || '.visor/schedules.db';\n }\n\n async initialize(): Promise<void> {\n // Resolve and ensure directory exists\n const resolvedPath = path.resolve(process.cwd(), this.dbPath);\n const dir = path.dirname(resolvedPath);\n fs.mkdirSync(dir, { recursive: true });\n\n // Load better-sqlite3 via createRequire (ncc-safe)\n const { createRequire } = require('module') as typeof import('module');\n const runtimeRequire = createRequire(__filename);\n let Database: new (filename: string) => BetterSqliteDatabase;\n try {\n Database = runtimeRequire('better-sqlite3');\n } catch (err: unknown) {\n const code = (err as { code?: string })?.code;\n if (code === 'MODULE_NOT_FOUND' || code === 'ERR_MODULE_NOT_FOUND') {\n throw new Error(\n 'better-sqlite3 is required for SQLite schedule storage. ' +\n 'Install it with: npm install better-sqlite3'\n );\n }\n throw err;\n }\n\n this.db = new Database(resolvedPath);\n\n // Enable WAL mode for better concurrent reads\n this.db.pragma('journal_mode = WAL');\n\n // Run schema migration (idempotent)\n this.migrateSchema();\n\n logger.info(`[SqliteStore] Initialized at ${this.dbPath}`);\n }\n\n async shutdown(): Promise<void> {\n if (this.db) {\n this.db.close();\n this.db = null;\n }\n this.locks.clear();\n }\n\n // --- Schema Migration ---\n\n private migrateSchema(): void {\n const db = this.getDb();\n\n db.exec(`\n CREATE TABLE IF NOT EXISTS schedules (\n id VARCHAR(36) PRIMARY KEY,\n creator_id VARCHAR(255) NOT NULL,\n creator_context VARCHAR(255),\n creator_name VARCHAR(255),\n timezone VARCHAR(64) NOT NULL DEFAULT 'UTC',\n schedule_expr VARCHAR(255),\n run_at BIGINT,\n is_recurring BOOLEAN NOT NULL,\n original_expression TEXT,\n workflow VARCHAR(255),\n workflow_inputs TEXT,\n output_context TEXT,\n status VARCHAR(20) NOT NULL,\n created_at BIGINT NOT NULL,\n last_run_at BIGINT,\n next_run_at BIGINT,\n run_count INTEGER NOT NULL DEFAULT 0,\n failure_count INTEGER NOT NULL DEFAULT 0,\n last_error TEXT,\n previous_response TEXT,\n claimed_by VARCHAR(255),\n claimed_at BIGINT,\n lock_token VARCHAR(36)\n );\n\n CREATE INDEX IF NOT EXISTS idx_schedules_creator_id\n ON schedules(creator_id);\n\n CREATE INDEX IF NOT EXISTS idx_schedules_status\n ON schedules(status);\n\n CREATE INDEX IF NOT EXISTS idx_schedules_status_next_run\n ON schedules(status, next_run_at);\n\n CREATE TABLE IF NOT EXISTS scheduler_locks (\n lock_id VARCHAR(255) PRIMARY KEY,\n node_id VARCHAR(255) NOT NULL,\n lock_token VARCHAR(36) NOT NULL,\n acquired_at BIGINT NOT NULL,\n expires_at BIGINT NOT NULL\n );\n `);\n }\n\n // --- Helpers ---\n\n private getDb(): BetterSqliteDatabase {\n if (!this.db) {\n throw new Error('[SqliteStore] Database not initialized. Call initialize() first.');\n }\n return this.db;\n }\n\n // --- CRUD ---\n\n async create(\n schedule: Omit<Schedule, 'id' | 'createdAt' | 'runCount' | 'failureCount' | 'status'>\n ): Promise<Schedule> {\n const db = this.getDb();\n\n const newSchedule: Schedule = {\n ...schedule,\n id: uuidv4(),\n createdAt: Date.now(),\n runCount: 0,\n failureCount: 0,\n status: 'active',\n };\n\n const row = toDbRow(newSchedule);\n\n db.prepare(\n `\n INSERT INTO schedules (\n id, creator_id, creator_context, creator_name, timezone,\n schedule_expr, run_at, is_recurring, original_expression,\n workflow, workflow_inputs, output_context,\n status, created_at, last_run_at, next_run_at,\n run_count, failure_count, last_error, previous_response\n ) VALUES (\n ?, ?, ?, ?, ?,\n ?, ?, ?, ?,\n ?, ?, ?,\n ?, ?, ?, ?,\n ?, ?, ?, ?\n )\n `\n ).run(\n row.id,\n row.creator_id,\n row.creator_context,\n row.creator_name,\n row.timezone,\n row.schedule_expr,\n row.run_at,\n row.is_recurring,\n row.original_expression,\n row.workflow,\n row.workflow_inputs,\n row.output_context,\n row.status,\n row.created_at,\n row.last_run_at,\n row.next_run_at,\n row.run_count,\n row.failure_count,\n row.last_error,\n row.previous_response\n );\n\n logger.info(\n `[SqliteStore] Created schedule ${newSchedule.id} for user ${newSchedule.creatorId}`\n );\n\n return newSchedule;\n }\n\n async importSchedule(schedule: Schedule): Promise<void> {\n const db = this.getDb();\n const row = toDbRow(schedule);\n\n db.prepare(\n `\n INSERT OR IGNORE INTO schedules (\n id, creator_id, creator_context, creator_name, timezone,\n schedule_expr, run_at, is_recurring, original_expression,\n workflow, workflow_inputs, output_context,\n status, created_at, last_run_at, next_run_at,\n run_count, failure_count, last_error, previous_response\n ) VALUES (\n ?, ?, ?, ?, ?,\n ?, ?, ?, ?,\n ?, ?, ?,\n ?, ?, ?, ?,\n ?, ?, ?, ?\n )\n `\n ).run(\n row.id,\n row.creator_id,\n row.creator_context,\n row.creator_name,\n row.timezone,\n row.schedule_expr,\n row.run_at,\n row.is_recurring,\n row.original_expression,\n row.workflow,\n row.workflow_inputs,\n row.output_context,\n row.status,\n row.created_at,\n row.last_run_at,\n row.next_run_at,\n row.run_count,\n row.failure_count,\n row.last_error,\n row.previous_response\n );\n }\n\n async get(id: string): Promise<Schedule | undefined> {\n const db = this.getDb();\n const row = db.prepare('SELECT * FROM schedules WHERE id = ?').get(id) as\n | ScheduleRow\n | undefined;\n return row ? fromDbRow(row) : undefined;\n }\n\n async update(id: string, patch: Partial<Schedule>): Promise<Schedule | undefined> {\n const db = this.getDb();\n\n // Get current row\n const existing = db.prepare('SELECT * FROM schedules WHERE id = ?').get(id) as\n | ScheduleRow\n | undefined;\n if (!existing) return undefined;\n\n const current = fromDbRow(existing);\n const updated: Schedule = { ...current, ...patch, id: current.id }; // ID cannot change\n const row = toDbRow(updated);\n\n db.prepare(\n `\n UPDATE schedules SET\n creator_id = ?, creator_context = ?, creator_name = ?, timezone = ?,\n schedule_expr = ?, run_at = ?, is_recurring = ?, original_expression = ?,\n workflow = ?, workflow_inputs = ?, output_context = ?,\n status = ?, last_run_at = ?, next_run_at = ?,\n run_count = ?, failure_count = ?, last_error = ?, previous_response = ?\n WHERE id = ?\n `\n ).run(\n row.creator_id,\n row.creator_context,\n row.creator_name,\n row.timezone,\n row.schedule_expr,\n row.run_at,\n row.is_recurring,\n row.original_expression,\n row.workflow,\n row.workflow_inputs,\n row.output_context,\n row.status,\n row.last_run_at,\n row.next_run_at,\n row.run_count,\n row.failure_count,\n row.last_error,\n row.previous_response,\n row.id\n );\n\n return updated;\n }\n\n async delete(id: string): Promise<boolean> {\n const db = this.getDb();\n const result = db.prepare('DELETE FROM schedules WHERE id = ?').run(id);\n if (result.changes > 0) {\n logger.info(`[SqliteStore] Deleted schedule ${id}`);\n return true;\n }\n return false;\n }\n\n // --- Queries ---\n\n async getByCreator(creatorId: string): Promise<Schedule[]> {\n const db = this.getDb();\n const rows = db\n .prepare('SELECT * FROM schedules WHERE creator_id = ?')\n .all(creatorId) as ScheduleRow[];\n return rows.map(fromDbRow);\n }\n\n async getActiveSchedules(): Promise<Schedule[]> {\n const db = this.getDb();\n const rows = db\n .prepare(\"SELECT * FROM schedules WHERE status = 'active'\")\n .all() as ScheduleRow[];\n return rows.map(fromDbRow);\n }\n\n async getDueSchedules(now?: number): Promise<Schedule[]> {\n const ts = now ?? Date.now();\n const db = this.getDb();\n const rows = db\n .prepare(\n `SELECT * FROM schedules\n WHERE status = 'active'\n AND (\n (is_recurring = 0 AND run_at IS NOT NULL AND run_at <= ?)\n OR\n (is_recurring = 1 AND next_run_at IS NOT NULL AND next_run_at <= ?)\n )`\n )\n .all(ts, ts) as ScheduleRow[];\n return rows.map(fromDbRow);\n }\n\n async findByWorkflow(creatorId: string, workflowName: string): Promise<Schedule[]> {\n const db = this.getDb();\n const escaped = workflowName.toLowerCase().replace(/[%_\\\\]/g, '\\\\$&');\n const pattern = `%${escaped}%`;\n const rows = db\n .prepare(\n `SELECT * FROM schedules\n WHERE creator_id = ? AND status = 'active'\n AND LOWER(workflow) LIKE ? ESCAPE '\\\\'`\n )\n .all(creatorId, pattern) as ScheduleRow[];\n return rows.map(fromDbRow);\n }\n\n async getAll(): Promise<Schedule[]> {\n const db = this.getDb();\n const rows = db.prepare('SELECT * FROM schedules').all() as ScheduleRow[];\n return rows.map(fromDbRow);\n }\n\n async getStats(): Promise<ScheduleStoreStats> {\n const db = this.getDb();\n\n const row = db\n .prepare(\n `SELECT\n COUNT(*) as total,\n SUM(CASE WHEN status = 'active' THEN 1 ELSE 0 END) as active,\n SUM(CASE WHEN status = 'paused' THEN 1 ELSE 0 END) as paused,\n SUM(CASE WHEN status = 'completed' THEN 1 ELSE 0 END) as completed,\n SUM(CASE WHEN status = 'failed' THEN 1 ELSE 0 END) as failed,\n SUM(CASE WHEN is_recurring = 1 THEN 1 ELSE 0 END) as recurring,\n SUM(CASE WHEN is_recurring = 0 THEN 1 ELSE 0 END) as one_time\n FROM schedules`\n )\n .get() as {\n total: number;\n active: number;\n paused: number;\n completed: number;\n failed: number;\n recurring: number;\n one_time: number;\n };\n\n return {\n total: row.total,\n active: row.active,\n paused: row.paused,\n completed: row.completed,\n failed: row.failed,\n recurring: row.recurring,\n oneTime: row.one_time,\n };\n }\n\n async validateLimits(\n creatorId: string,\n isRecurring: boolean,\n limits: ScheduleLimits\n ): Promise<void> {\n const db = this.getDb();\n\n // Global limit\n if (limits.maxGlobal) {\n const row = db.prepare('SELECT COUNT(*) as cnt FROM schedules').get() as { cnt: number };\n if (row.cnt >= limits.maxGlobal) {\n throw new Error(`Global schedule limit reached (${limits.maxGlobal})`);\n }\n }\n\n // Per-user limit\n if (limits.maxPerUser) {\n const row = db\n .prepare('SELECT COUNT(*) as cnt FROM schedules WHERE creator_id = ?')\n .get(creatorId) as { cnt: number };\n if (row.cnt >= limits.maxPerUser) {\n throw new Error(`You have reached the maximum number of schedules (${limits.maxPerUser})`);\n }\n }\n\n // Per-user recurring limit\n if (isRecurring && limits.maxRecurringPerUser) {\n const row = db\n .prepare('SELECT COUNT(*) as cnt FROM schedules WHERE creator_id = ? AND is_recurring = 1')\n .get(creatorId) as { cnt: number };\n if (row.cnt >= limits.maxRecurringPerUser) {\n throw new Error(\n `You have reached the maximum number of recurring schedules (${limits.maxRecurringPerUser})`\n );\n }\n }\n }\n\n // --- HA Locking (in-memory for SQLite — single-node only) ---\n\n async tryAcquireLock(\n scheduleId: string,\n nodeId: string,\n ttlSeconds: number\n ): Promise<string | null> {\n const now = Date.now();\n const existing = this.locks.get(scheduleId);\n\n // Check if an unexpired lock exists\n if (existing && existing.expiresAt > now) {\n if (existing.nodeId === nodeId) {\n // Same node: return existing token\n return existing.token;\n }\n // Another node holds the lock\n return null;\n }\n\n // Acquire lock\n const token = uuidv4();\n this.locks.set(scheduleId, {\n nodeId,\n token,\n expiresAt: now + ttlSeconds * 1000,\n });\n return token;\n }\n\n async releaseLock(scheduleId: string, lockToken: string): Promise<void> {\n const existing = this.locks.get(scheduleId);\n if (existing && existing.token === lockToken) {\n this.locks.delete(scheduleId);\n }\n }\n\n async renewLock(scheduleId: string, lockToken: string, ttlSeconds: number): Promise<boolean> {\n const existing = this.locks.get(scheduleId);\n if (!existing || existing.token !== lockToken) {\n return false;\n }\n existing.expiresAt = Date.now() + ttlSeconds * 1000;\n return true;\n }\n\n async flush(): Promise<void> {\n // No-op for SQLite — writes are synchronous\n }\n}\n","/**\n * Store backend factory\n *\n * Creates the appropriate ScheduleStoreBackend based on configuration:\n * - 'sqlite' (default) → SqliteStoreBackend (OSS, zero-config)\n * - 'postgresql' / 'mysql' → Enterprise KnexStoreBackend (requires license)\n */\nimport { logger } from '../../logger';\nimport type {\n ScheduleStoreBackend,\n StorageConfig,\n HAConfig,\n SqliteConnectionConfig,\n} from './types';\nimport { SqliteStoreBackend } from './sqlite-store';\n\nexport type { ScheduleStoreBackend, ScheduleStoreStats, StorageConfig, HAConfig } from './types';\n\n/**\n * Create a store backend based on configuration\n */\nexport async function createStoreBackend(\n storageConfig?: StorageConfig,\n haConfig?: HAConfig\n): Promise<ScheduleStoreBackend> {\n const driver = storageConfig?.driver || 'sqlite';\n\n switch (driver) {\n case 'sqlite': {\n const conn = storageConfig?.connection as SqliteConnectionConfig | undefined;\n return new SqliteStoreBackend(conn?.filename);\n }\n\n case 'postgresql':\n case 'mysql':\n case 'mssql': {\n // Enterprise-only: dynamic import to keep OSS code clean\n try {\n // Variable path prevents ncc from tracing into enterprise/ (stashed during OSS builds)\n const loaderPath = '../../enterprise/loader';\n // @ts-ignore — enterprise/ may not exist in OSS builds (caught at runtime)\n const { loadEnterpriseStoreBackend } = await import(loaderPath);\n return await loadEnterpriseStoreBackend(driver, storageConfig!, haConfig);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n logger.error(`[StoreFactory] Failed to load enterprise ${driver} backend: ${msg}`);\n throw new Error(\n `The ${driver} schedule storage driver requires a Visor Enterprise license. ` +\n `Install the enterprise package or use driver: 'sqlite' (default). ` +\n `Original error: ${msg}`\n );\n }\n }\n\n default:\n throw new Error(`Unknown schedule storage driver: ${driver}`);\n }\n}\n","/**\n * One-time migration from .visor/schedules.json to a SQL backend\n *\n * - Reads the JSON file, inserts each schedule into the backend\n * - Skips schedules whose ID already exists (idempotent)\n * - Renames the JSON file to .json.migrated as a backup\n * - Called automatically during ScheduleStore.initialize() when the JSON file exists\n */\nimport fs from 'fs/promises';\nimport path from 'path';\nimport { logger } from '../../logger';\nimport type { Schedule } from '../schedule-store';\nimport type { ScheduleStoreBackend } from './types';\n\n/**\n * Migrate schedules from a JSON file into a ScheduleStoreBackend.\n *\n * @param jsonPath Path to the JSON file (absolute or relative to cwd)\n * @param backend Already-initialized store backend to migrate into\n * @returns Number of schedules migrated (0 if file doesn't exist or already migrated)\n */\nexport async function migrateJsonToBackend(\n jsonPath: string,\n backend: ScheduleStoreBackend\n): Promise<number> {\n const resolvedPath = path.resolve(process.cwd(), jsonPath);\n\n // Check if JSON file exists\n let content: string;\n try {\n content = await fs.readFile(resolvedPath, 'utf-8');\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') {\n // No file to migrate — nothing to do\n return 0;\n }\n throw err;\n }\n\n // Parse the JSON\n let data: { schedules?: Schedule[] };\n try {\n data = JSON.parse(content);\n } catch {\n logger.warn(`[JsonMigrator] Failed to parse ${jsonPath}, skipping migration`);\n return 0;\n }\n\n const schedules = data.schedules;\n if (!Array.isArray(schedules) || schedules.length === 0) {\n logger.debug('[JsonMigrator] No schedules to migrate');\n // Still rename to mark as processed\n await renameToMigrated(resolvedPath);\n return 0;\n }\n\n let migrated = 0;\n\n for (const schedule of schedules) {\n if (!schedule.id) {\n logger.warn('[JsonMigrator] Skipping schedule without ID');\n continue;\n }\n\n // Check if already migrated (idempotent)\n const existing = await backend.get(schedule.id);\n if (existing) {\n logger.debug(`[JsonMigrator] Schedule ${schedule.id} already exists, skipping`);\n continue;\n }\n\n try {\n // Import preserving original ID (idempotent — skips if ID already exists)\n await backend.importSchedule(schedule);\n migrated++;\n } catch (err) {\n logger.warn(\n `[JsonMigrator] Failed to migrate schedule ${schedule.id}: ${\n err instanceof Error ? err.message : err\n }`\n );\n }\n }\n\n // Rename original file as backup\n await renameToMigrated(resolvedPath);\n\n logger.info(`[JsonMigrator] Migrated ${migrated}/${schedules.length} schedules from ${jsonPath}`);\n return migrated;\n}\n\n/**\n * Rename the JSON file to .migrated as a backup\n */\nasync function renameToMigrated(resolvedPath: string): Promise<void> {\n const migratedPath = `${resolvedPath}.migrated`;\n try {\n await fs.rename(resolvedPath, migratedPath);\n logger.info(`[JsonMigrator] Backed up ${resolvedPath} → ${migratedPath}`);\n } catch (err) {\n logger.warn(\n `[JsonMigrator] Failed to rename ${resolvedPath}: ${err instanceof Error ? err.message : err}`\n );\n }\n}\n","/**\n * Generic persistent storage for scheduled workflows\n *\n * Delegates all persistence to a ScheduleStoreBackend (default: SQLite).\n * The in-memory Map and JSON file I/O have been replaced by the backend abstraction\n * to support SQLite (OSS) and PostgreSQL/MySQL (Enterprise) storage.\n */\nimport { logger } from '../logger';\nimport type {\n ScheduleStoreBackend,\n StorageConfig,\n HAConfig,\n ScheduleStoreStats,\n} from './store/types';\nimport { createStoreBackend } from './store/index';\nimport { migrateJsonToBackend } from './store/json-migrator';\n\n/**\n * Output context for schedule execution results\n */\nexport interface ScheduleOutputContext {\n /** Output destination type */\n type: 'slack' | 'github' | 'webhook' | 'none';\n /** Target identifier (channel ID, repo name, webhook URL, etc.) */\n target?: string;\n /** Thread/issue/PR identifier for threaded outputs */\n threadId?: string;\n /** Additional destination-specific metadata */\n metadata?: Record<string, unknown>;\n}\n\n/**\n * Represents a scheduled workflow execution\n */\nexport interface Schedule {\n id: string; // UUID v4\n\n // Creator\n creatorId: string; // Generic user ID (e.g., Slack user ID, GitHub user, CLI user)\n creatorContext?: string; // Context identifier: \"slack:U123\", \"github:user\", \"cli\"\n creatorName?: string; // Display name (cached)\n timezone: string; // IANA timezone\n\n // Scheduling\n schedule: string; // Cron expression (for recurring)\n runAt?: number; // Unix timestamp (for one-time)\n isRecurring: boolean;\n originalExpression: string; // User's natural language input\n\n // What to execute\n workflow?: string; // Workflow/check ID to run (undefined for simple text reminders)\n workflowInputs?: Record<string, unknown>; // Input parameters for the workflow\n\n // Output routing (optional - workflows can handle their own output)\n outputContext?: ScheduleOutputContext;\n\n // State\n status: 'active' | 'paused' | 'completed' | 'failed';\n createdAt: number;\n lastRunAt?: number;\n nextRunAt?: number; // Computed next execution time\n runCount: number;\n failureCount: number;\n lastError?: string;\n\n // Previous execution response (for recurring reminders)\n previousResponse?: string; // The AI response from the last execution\n}\n\n/**\n * Configuration for the schedule store\n */\nexport interface ScheduleStoreConfig {\n /** Path to the JSON file for persistence (legacy, triggers auto-migration) */\n path?: string;\n /** Auto-save after changes (default: true) — ignored for SQL backends */\n autoSave?: boolean;\n /** Debounce save operations in ms (default: 1000) — ignored for SQL backends */\n saveDebounceMs?: number;\n /** SQL storage configuration */\n storage?: StorageConfig;\n /** High-availability configuration */\n ha?: HAConfig;\n}\n\n/**\n * Limits configuration for schedules\n */\nexport interface ScheduleLimits {\n maxPerUser?: number; // Default: 25\n maxRecurringPerUser?: number; // Default: 10\n maxGlobal?: number; // Default: 1000\n}\n\n/**\n * Storage class for scheduled workflows with pluggable backend persistence\n */\nexport class ScheduleStore {\n private static instance: ScheduleStore | undefined;\n private backend: ScheduleStoreBackend | null = null;\n private initialized = false;\n private limits: ScheduleLimits;\n private config: ScheduleStoreConfig;\n private externalBackend: ScheduleStoreBackend | null = null;\n\n private constructor(\n config?: ScheduleStoreConfig,\n limits?: ScheduleLimits,\n backend?: ScheduleStoreBackend\n ) {\n this.config = config || {};\n this.limits = {\n maxPerUser: limits?.maxPerUser ?? 25,\n maxRecurringPerUser: limits?.maxRecurringPerUser ?? 10,\n maxGlobal: limits?.maxGlobal ?? 1000,\n };\n if (backend) {\n this.externalBackend = backend;\n }\n }\n\n /**\n * Get singleton instance\n *\n * Note: Config and limits are only applied on first call. Subsequent calls\n * with different parameters will log a warning and return the existing instance.\n * Use createIsolated() for testing with different configurations.\n */\n static getInstance(config?: ScheduleStoreConfig, limits?: ScheduleLimits): ScheduleStore {\n if (!ScheduleStore.instance) {\n ScheduleStore.instance = new ScheduleStore(config, limits);\n } else if (config || limits) {\n logger.warn(\n '[ScheduleStore] getInstance() called with config/limits but instance already exists. ' +\n 'Parameters ignored. Use createIsolated() for testing or resetInstance() first.'\n );\n }\n return ScheduleStore.instance;\n }\n\n /**\n * Create a new isolated instance (for testing)\n */\n static createIsolated(\n config?: ScheduleStoreConfig,\n limits?: ScheduleLimits,\n backend?: ScheduleStoreBackend\n ): ScheduleStore {\n return new ScheduleStore(config, limits, backend);\n }\n\n /**\n * Reset singleton instance (for testing)\n */\n static resetInstance(): void {\n if (ScheduleStore.instance) {\n // Best-effort shutdown\n if (ScheduleStore.instance.backend) {\n ScheduleStore.instance.backend.shutdown().catch(() => {});\n }\n }\n ScheduleStore.instance = undefined;\n }\n\n /**\n * Initialize the store - creates backend and runs migrations\n */\n async initialize(): Promise<void> {\n if (this.initialized) {\n return;\n }\n\n // Create backend (or use the externally provided one)\n if (this.externalBackend) {\n this.backend = this.externalBackend;\n } else {\n this.backend = await createStoreBackend(this.config.storage, this.config.ha);\n }\n\n await this.backend.initialize();\n\n // Auto-migrate from JSON if a legacy path is configured or default JSON file exists\n const jsonPath = this.config.path || '.visor/schedules.json';\n try {\n await migrateJsonToBackend(jsonPath, this.backend);\n } catch (err) {\n logger.warn(\n `[ScheduleStore] JSON migration failed (non-fatal): ${\n err instanceof Error ? err.message : err\n }`\n );\n }\n\n this.initialized = true;\n }\n\n /**\n * Create a new schedule (async, persists immediately)\n */\n async createAsync(\n schedule: Omit<Schedule, 'id' | 'createdAt' | 'runCount' | 'failureCount' | 'status'>\n ): Promise<Schedule> {\n const backend = this.getBackend();\n await backend.validateLimits(schedule.creatorId, schedule.isRecurring, this.limits);\n return backend.create(schedule);\n }\n\n /**\n * Get a schedule by ID\n */\n async getAsync(id: string): Promise<Schedule | undefined> {\n return this.getBackend().get(id);\n }\n\n /**\n * Update a schedule\n */\n async updateAsync(id: string, patch: Partial<Schedule>): Promise<Schedule | undefined> {\n return this.getBackend().update(id, patch);\n }\n\n /**\n * Delete a schedule\n */\n async deleteAsync(id: string): Promise<boolean> {\n return this.getBackend().delete(id);\n }\n\n /**\n * Get all schedules for a specific creator\n */\n async getByCreatorAsync(creatorId: string): Promise<Schedule[]> {\n return this.getBackend().getByCreator(creatorId);\n }\n\n /**\n * Get all active schedules\n */\n async getActiveSchedulesAsync(): Promise<Schedule[]> {\n return this.getBackend().getActiveSchedules();\n }\n\n /**\n * Get all schedules due for execution\n * @param now Current timestamp in milliseconds\n */\n async getDueSchedulesAsync(now: number = Date.now()): Promise<Schedule[]> {\n return this.getBackend().getDueSchedules(now);\n }\n\n /**\n * Find schedules by workflow name\n */\n async findByWorkflowAsync(creatorId: string, workflowName: string): Promise<Schedule[]> {\n return this.getBackend().findByWorkflow(creatorId, workflowName);\n }\n\n /**\n * Get schedule count statistics\n */\n async getStatsAsync(): Promise<ScheduleStoreStats> {\n return this.getBackend().getStats();\n }\n\n /**\n * Force immediate save (useful for shutdown)\n */\n async flush(): Promise<void> {\n if (this.backend) {\n await this.backend.flush();\n }\n }\n\n /**\n * Check if initialized\n */\n isInitialized(): boolean {\n return this.initialized;\n }\n\n /**\n * Check if there are unsaved changes\n */\n hasPendingChanges(): boolean {\n return false; // SQL backends persist immediately\n }\n\n /**\n * Get all schedules\n */\n async getAllAsync(): Promise<Schedule[]> {\n return this.getBackend().getAll();\n }\n\n /**\n * Get the underlying backend (for HA lock operations)\n */\n getBackend(): ScheduleStoreBackend {\n if (!this.backend) {\n throw new Error('[ScheduleStore] Not initialized. Call initialize() first.');\n }\n return this.backend;\n }\n\n /**\n * Shut down the backend cleanly\n */\n async shutdown(): Promise<void> {\n if (this.backend) {\n await this.backend.shutdown();\n this.backend = null;\n }\n this.initialized = false;\n }\n}\n","/**\n * Schedule parsing utilities\n * Note: Natural language parsing is handled by the AI which generates\n * cron expressions or ISO timestamps directly. This module provides\n * validation and helper functions.\n */\n\n/**\n * Parsed schedule result\n */\nexport interface ParsedSchedule {\n type: 'one-time' | 'recurring';\n cronExpression?: string; // For recurring schedules\n runAt?: Date; // For one-time schedules\n timezone: string;\n description: string; // Human-readable description\n}\n\n/**\n * Day of week mapping for cron expressions\n */\nconst DAYS_OF_WEEK: Record<string, number> = {\n sunday: 0,\n sun: 0,\n monday: 1,\n mon: 1,\n tuesday: 2,\n tue: 2,\n wednesday: 3,\n wed: 3,\n thursday: 4,\n thu: 4,\n friday: 5,\n fri: 5,\n saturday: 6,\n sat: 6,\n};\n\n/**\n * Recurring pattern detection\n */\ninterface RecurringPattern {\n frequency:\n | 'minute'\n | 'hourly'\n | 'daily'\n | 'weekly'\n | 'monthly'\n | 'yearly'\n | 'weekday'\n | 'weekend';\n daysOfWeek?: number[];\n dayOfMonth?: number;\n hour?: number;\n minute?: number;\n interval?: number;\n}\n\n/**\n * Parse a natural language schedule expression\n *\n * @param expression Natural language time expression (e.g., \"every Monday at 9am\", \"in 2 hours\")\n * @param userTimezone IANA timezone string (e.g., \"America/New_York\")\n * @param referenceDate Optional reference date for parsing (defaults to now)\n * @returns Parsed schedule information\n */\nexport function parseScheduleExpression(\n expression: string,\n userTimezone: string = 'UTC',\n _referenceDate?: Date\n): ParsedSchedule {\n const trimmedExpr = expression.trim();\n const normalizedExpr = trimmedExpr.toLowerCase();\n\n // First check if it's a standard cron expression (5 space-separated parts)\n if (isValidCronExpression(trimmedExpr)) {\n return {\n type: 'recurring',\n cronExpression: trimmedExpr,\n timezone: userTimezone,\n description: describeCronExpression(trimmedExpr),\n };\n }\n\n // Check for natural language recurring patterns\n const recurring = detectRecurringPattern(normalizedExpr);\n if (recurring) {\n const cronExpr = buildCronExpression(recurring);\n return {\n type: 'recurring',\n cronExpression: cronExpr,\n timezone: userTimezone,\n description: buildRecurringDescription(recurring),\n };\n }\n\n // Try to parse as ISO 8601 timestamp for one-time schedules\n const isoDate = new Date(expression);\n if (!isNaN(isoDate.getTime())) {\n // Valid ISO date\n if (isoDate.getTime() <= Date.now()) {\n throw new Error(`Schedule time must be in the future: \"${expression}\"`);\n }\n return {\n type: 'one-time',\n runAt: isoDate,\n timezone: userTimezone,\n description: formatOneTimeDescription(isoDate),\n };\n }\n\n // Could not parse - provide helpful error\n throw new Error(\n `Could not parse schedule expression: \"${expression}\". ` +\n `Use a cron expression (e.g., \"0 9 * * 1\" for Monday 9am) or ISO timestamp (e.g., \"2026-02-08T15:00:00Z\").`\n );\n}\n\n/**\n * Detect recurring patterns in the expression\n */\nfunction detectRecurringPattern(expr: string): RecurringPattern | null {\n // Check for keywords that indicate recurring schedules\n if (\n !expr.includes('every') &&\n !expr.includes('daily') &&\n !expr.includes('weekly') &&\n !expr.includes('monthly')\n ) {\n return null;\n }\n\n // Extract time component\n const timeMatch = expr.match(/at\\s+(\\d{1,2})(?::(\\d{2}))?\\s*(am|pm)?/i);\n let hour: number | undefined;\n let minute: number | undefined;\n\n if (timeMatch) {\n hour = parseInt(timeMatch[1], 10);\n minute = timeMatch[2] ? parseInt(timeMatch[2], 10) : 0;\n\n // Convert to 24-hour format\n const ampm = timeMatch[3]?.toLowerCase();\n if (ampm === 'pm' && hour !== 12) {\n hour += 12;\n } else if (ampm === 'am' && hour === 12) {\n hour = 0;\n }\n } else {\n // Check for special times\n if (expr.includes('noon')) {\n hour = 12;\n minute = 0;\n } else if (expr.includes('midnight')) {\n hour = 0;\n minute = 0;\n } else if (expr.includes('morning')) {\n hour = 9;\n minute = 0;\n } else if (expr.includes('evening')) {\n hour = 18;\n minute = 0;\n }\n }\n\n // Every minute (singular, no number)\n if (expr.match(/every\\s+minute\\b/i) && !expr.match(/every\\s+\\d+\\s+minute/i)) {\n return {\n frequency: 'minute',\n interval: 1,\n minute: 0,\n };\n }\n\n // Every hour (singular, no number)\n if (expr.match(/every\\s+hour\\b/i) && !expr.match(/every\\s+\\d+\\s+hour/i)) {\n return {\n frequency: 'hourly',\n interval: 1,\n minute: minute ?? 0,\n };\n }\n\n // Every N minutes/hours\n const intervalMatch = expr.match(/every\\s+(\\d+)\\s+(minute|hour)s?/i);\n if (intervalMatch) {\n const interval = parseInt(intervalMatch[1], 10);\n const unit = intervalMatch[2].toLowerCase();\n\n if (unit === 'minute') {\n return {\n frequency: 'minute',\n interval,\n minute: 0,\n };\n } else {\n return {\n frequency: 'hourly',\n interval,\n minute: minute ?? 0,\n };\n }\n }\n\n // Every day / daily\n if (expr.includes('every day') || expr.includes('daily')) {\n return {\n frequency: 'daily',\n hour: hour ?? 9,\n minute: minute ?? 0,\n };\n }\n\n // Every weekday\n if (\n expr.includes('weekday') ||\n (expr.includes('monday') && expr.includes('friday') && expr.includes('through'))\n ) {\n return {\n frequency: 'weekday',\n hour: hour ?? 9,\n minute: minute ?? 0,\n };\n }\n\n // Every weekend\n if (expr.includes('weekend')) {\n return {\n frequency: 'weekend',\n hour: hour ?? 10,\n minute: minute ?? 0,\n };\n }\n\n // Monthly - check before day of week to avoid false matches\n if (expr.includes('monthly') || expr.includes('every month')) {\n const dayOfMonthMatch = expr.match(/(?:on the\\s+)?(\\d{1,2})(?:st|nd|rd|th)?/);\n const dayOfMonth = dayOfMonthMatch ? parseInt(dayOfMonthMatch[1], 10) : 1;\n return {\n frequency: 'monthly',\n dayOfMonth,\n hour: hour ?? 9,\n minute: minute ?? 0,\n };\n }\n\n // Weekly on specific day(s)\n if (expr.includes('weekly') || expr.includes('every week')) {\n // Default to Monday if no day specified\n const daysOfWeek = extractDaysOfWeek(expr);\n return {\n frequency: 'weekly',\n daysOfWeek: daysOfWeek.length > 0 ? daysOfWeek : [1],\n hour: hour ?? 9,\n minute: minute ?? 0,\n };\n }\n\n // Every [day of week]\n const daysOfWeek = extractDaysOfWeek(expr);\n if (daysOfWeek.length > 0) {\n return {\n frequency: 'weekly',\n daysOfWeek,\n hour: hour ?? 9,\n minute: minute ?? 0,\n };\n }\n\n // If we detected \"every\" but couldn't parse the pattern, default to daily\n if (expr.includes('every')) {\n return {\n frequency: 'daily',\n hour: hour ?? 9,\n minute: minute ?? 0,\n };\n }\n\n return null;\n}\n\n/**\n * Extract days of week from expression\n */\nfunction extractDaysOfWeek(expr: string): number[] {\n const days: Set<number> = new Set();\n\n for (const [dayName, dayNum] of Object.entries(DAYS_OF_WEEK)) {\n if (expr.includes(dayName)) {\n days.add(dayNum);\n }\n }\n\n return Array.from(days).sort((a, b) => a - b);\n}\n\n/**\n * Build a cron expression from a recurring pattern\n *\n * Cron format: minute hour day-of-month month day-of-week\n */\nfunction buildCronExpression(pattern: RecurringPattern): string {\n const minute = pattern.minute ?? 0;\n const hour = pattern.hour ?? 9;\n\n switch (pattern.frequency) {\n case 'minute':\n // Every N minutes\n if (pattern.interval && pattern.interval > 1) {\n return `*/${pattern.interval} * * * *`;\n }\n return '* * * * *';\n\n case 'hourly':\n // Every N hours\n if (pattern.interval && pattern.interval > 1) {\n return `${minute} */${pattern.interval} * * *`;\n }\n return `${minute} * * * *`;\n\n case 'daily':\n return `${minute} ${hour} * * *`;\n\n case 'weekday':\n return `${minute} ${hour} * * 1-5`;\n\n case 'weekend':\n return `${minute} ${hour} * * 0,6`;\n\n case 'weekly':\n const days = pattern.daysOfWeek?.join(',') ?? '1';\n return `${minute} ${hour} * * ${days}`;\n\n case 'monthly':\n const dayOfMonth = pattern.dayOfMonth ?? 1;\n return `${minute} ${hour} ${dayOfMonth} * *`;\n\n case 'yearly':\n return `${minute} ${hour} 1 1 *`;\n\n default:\n return `${minute} ${hour} * * *`;\n }\n}\n\n/**\n * Build a human-readable description for recurring schedules\n */\nfunction buildRecurringDescription(pattern: RecurringPattern): string {\n const timeStr = formatTime(pattern.hour ?? 9, pattern.minute ?? 0);\n\n switch (pattern.frequency) {\n case 'minute':\n if (pattern.interval && pattern.interval > 1) {\n return `Every ${pattern.interval} minutes`;\n }\n return 'Every minute';\n\n case 'hourly':\n if (pattern.interval && pattern.interval > 1) {\n return `Every ${pattern.interval} hours at :${String(pattern.minute ?? 0).padStart(2, '0')}`;\n }\n return `Every hour at :${String(pattern.minute ?? 0).padStart(2, '0')}`;\n\n case 'daily':\n return `Daily at ${timeStr}`;\n\n case 'weekday':\n return `Weekdays at ${timeStr}`;\n\n case 'weekend':\n return `Weekends at ${timeStr}`;\n\n case 'weekly':\n const dayNames = (pattern.daysOfWeek ?? [1])\n .map(d => Object.entries(DAYS_OF_WEEK).find(([, v]) => v === d)?.[0] ?? String(d))\n .map(n => n.charAt(0).toUpperCase() + n.slice(1, 3));\n return `Every ${dayNames.join(', ')} at ${timeStr}`;\n\n case 'monthly':\n return `Monthly on the ${ordinal(pattern.dayOfMonth ?? 1)} at ${timeStr}`;\n\n case 'yearly':\n return `Yearly on January 1st at ${timeStr}`;\n\n default:\n return `At ${timeStr}`;\n }\n}\n\n/**\n * Format time in 12-hour format\n */\nfunction formatTime(hour: number, minute: number): string {\n const h = hour % 12 || 12;\n const m = String(minute).padStart(2, '0');\n const ampm = hour >= 12 ? 'PM' : 'AM';\n return `${h}:${m} ${ampm}`;\n}\n\n/**\n * Format a one-time schedule description\n */\nfunction formatOneTimeDescription(date: Date): string {\n const now = new Date();\n const diffMs = date.getTime() - now.getTime();\n const diffMins = Math.round(diffMs / 60000);\n const diffHours = Math.round(diffMs / 3600000);\n const diffDays = Math.round(diffMs / 86400000);\n\n // Format the actual time\n const timeStr = date.toLocaleTimeString('en-US', {\n hour: 'numeric',\n minute: '2-digit',\n hour12: true,\n });\n\n // Format the date\n const dateOptions: Intl.DateTimeFormatOptions = {\n weekday: 'long',\n month: 'short',\n day: 'numeric',\n };\n\n if (date.getFullYear() !== now.getFullYear()) {\n dateOptions.year = 'numeric';\n }\n\n const dateStr = date.toLocaleDateString('en-US', dateOptions);\n\n // Add relative time if within 24 hours\n if (diffMins < 60) {\n return `In ${diffMins} minute${diffMins !== 1 ? 's' : ''} (${dateStr} at ${timeStr})`;\n } else if (diffHours < 24) {\n return `In ${diffHours} hour${diffHours !== 1 ? 's' : ''} (${dateStr} at ${timeStr})`;\n } else if (diffDays === 1) {\n return `Tomorrow at ${timeStr}`;\n } else if (diffDays < 7) {\n return `${dateStr} at ${timeStr}`;\n }\n\n return `${dateStr} at ${timeStr}`;\n}\n\n/**\n * Calculate the next run time for a cron expression\n */\nexport function getNextRunTime(cronExpression: string, _timezone: string = 'UTC'): Date {\n // Note: Full timezone support would require a library like luxon or date-fns-tz\n // For now, calculations are done in local time\n // Simple cron parser for next run calculation\n // For production, consider using a library like 'cron-parser'\n const parts = cronExpression.split(' ');\n if (parts.length !== 5) {\n throw new Error(`Invalid cron expression: ${cronExpression}`);\n }\n\n const [minute, hour, dayOfMonth, month, dayOfWeek] = parts;\n const now = new Date();\n\n // Start from the next minute\n const next = new Date(now);\n next.setSeconds(0, 0);\n next.setMinutes(next.getMinutes() + 1);\n\n // Try to find the next matching time (within 1 year)\n const maxAttempts = 365 * 24 * 60; // 1 year of minutes\n for (let i = 0; i < maxAttempts; i++) {\n if (\n matchesCronPart(next.getMinutes(), minute) &&\n matchesCronPart(next.getHours(), hour) &&\n matchesCronPart(next.getDate(), dayOfMonth) &&\n matchesCronPart(next.getMonth() + 1, month) &&\n matchesCronPart(next.getDay(), dayOfWeek)\n ) {\n return next;\n }\n next.setMinutes(next.getMinutes() + 1);\n }\n\n // Fallback to tomorrow at the specified time\n const fallback = new Date(now);\n fallback.setDate(fallback.getDate() + 1);\n fallback.setHours(parseInt(hour, 10) || 9);\n fallback.setMinutes(parseInt(minute, 10) || 0);\n fallback.setSeconds(0, 0);\n return fallback;\n}\n\n/**\n * Check if a value matches a cron part\n */\nfunction matchesCronPart(value: number, cronPart: string): boolean {\n if (cronPart === '*') return true;\n\n // Handle step values (*/n)\n if (cronPart.startsWith('*/')) {\n const step = parseInt(cronPart.slice(2), 10);\n return value % step === 0;\n }\n\n // Handle ranges (n-m)\n if (cronPart.includes('-')) {\n const [start, end] = cronPart.split('-').map(n => parseInt(n, 10));\n return value >= start && value <= end;\n }\n\n // Handle lists (n,m,o)\n if (cronPart.includes(',')) {\n return cronPart\n .split(',')\n .map(n => parseInt(n, 10))\n .includes(value);\n }\n\n // Handle exact value\n return parseInt(cronPart, 10) === value;\n}\n\n/**\n * Get ordinal suffix for a number\n */\nfunction ordinal(n: number): string {\n const s = ['th', 'st', 'nd', 'rd'];\n const v = n % 100;\n return n + (s[(v - 20) % 10] || s[v] || s[0]);\n}\n\n/**\n * Validate a cron expression\n */\nexport function isValidCronExpression(expr: string): boolean {\n if (!expr || typeof expr !== 'string') return false;\n\n const parts = expr.trim().split(/\\s+/);\n if (parts.length !== 5) return false;\n\n const ranges: [number, number][] = [\n [0, 59], // minute\n [0, 23], // hour\n [1, 31], // day of month\n [1, 12], // month\n [0, 7], // day of week (0 and 7 are Sunday)\n ];\n\n return parts.every((part, i) => {\n if (part === '*') return true;\n if (part.startsWith('*/')) {\n const step = parseInt(part.slice(2), 10);\n return !isNaN(step) && step > 0;\n }\n if (part.includes('-')) {\n const [start, end] = part.split('-').map(n => parseInt(n, 10));\n return !isNaN(start) && !isNaN(end) && start >= ranges[i][0] && end <= ranges[i][1];\n }\n if (part.includes(',')) {\n return part.split(',').every(n => {\n const val = parseInt(n, 10);\n return !isNaN(val) && val >= ranges[i][0] && val <= ranges[i][1];\n });\n }\n const val = parseInt(part, 10);\n return !isNaN(val) && val >= ranges[i][0] && val <= ranges[i][1];\n });\n}\n\n/**\n * Generate a human-readable description for a cron expression\n */\nfunction describeCronExpression(cronExpr: string): string {\n const parts = cronExpr.trim().split(/\\s+/);\n if (parts.length !== 5) return `Cron: ${cronExpr}`;\n\n const [minute, hour, dayOfMonth, month, dayOfWeek] = parts;\n const descriptions: string[] = [];\n\n // Time description\n if (minute === '*' && hour === '*') {\n descriptions.push('Every minute');\n } else if (minute.startsWith('*/')) {\n const interval = parseInt(minute.slice(2), 10);\n descriptions.push(`Every ${interval} minute${interval > 1 ? 's' : ''}`);\n } else if (hour.startsWith('*/')) {\n const interval = parseInt(hour.slice(2), 10);\n const m = minute === '*' ? 0 : parseInt(minute, 10);\n descriptions.push(\n `Every ${interval} hour${interval > 1 ? 's' : ''} at :${String(m).padStart(2, '0')}`\n );\n } else {\n const h = hour === '*' ? null : parseInt(hour, 10);\n const m = minute === '*' ? 0 : parseInt(minute, 10);\n if (h !== null) {\n descriptions.push(`At ${formatTime(h, m)}`);\n } else {\n descriptions.push(`At :${String(m).padStart(2, '0')}`);\n }\n }\n\n // Day of week description\n if (dayOfWeek !== '*') {\n const dayNames = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];\n if (dayOfWeek === '1-5') {\n descriptions.push('on weekdays');\n } else if (dayOfWeek === '0,6' || dayOfWeek === '6,0') {\n descriptions.push('on weekends');\n } else if (dayOfWeek.includes(',')) {\n const days = dayOfWeek.split(',').map(d => dayNames[parseInt(d, 10) % 7]);\n descriptions.push(`on ${days.join(', ')}`);\n } else if (dayOfWeek.includes('-')) {\n const [start, end] = dayOfWeek.split('-').map(d => parseInt(d, 10) % 7);\n descriptions.push(`on ${dayNames[start]}-${dayNames[end]}`);\n } else {\n const dayNum = parseInt(dayOfWeek, 10) % 7;\n descriptions.push(`on ${dayNames[dayNum]}`);\n }\n }\n\n // Day of month description\n if (dayOfMonth !== '*' && dayOfWeek === '*') {\n if (dayOfMonth.includes(',')) {\n const days = dayOfMonth.split(',');\n descriptions.push(`on day${days.length > 1 ? 's' : ''} ${days.join(', ')} of the month`);\n } else {\n descriptions.push(`on day ${dayOfMonth} of the month`);\n }\n }\n\n // Month description\n if (month !== '*') {\n const monthNames = [\n '',\n 'Jan',\n 'Feb',\n 'Mar',\n 'Apr',\n 'May',\n 'Jun',\n 'Jul',\n 'Aug',\n 'Sep',\n 'Oct',\n 'Nov',\n 'Dec',\n ];\n if (month.includes(',')) {\n const months = month.split(',').map(m => monthNames[parseInt(m, 10)]);\n descriptions.push(`in ${months.join(', ')}`);\n } else {\n descriptions.push(`in ${monthNames[parseInt(month, 10)]}`);\n }\n }\n\n return descriptions.join(' ');\n}\n","/**\n * Generic AI Tool for scheduling and managing workflow executions\n * This tool is frontend-agnostic and can work with any context (Slack, CLI, GitHub, etc.)\n */\nimport { CustomToolDefinition } from '../types/config';\nimport { ScheduleStore, Schedule, ScheduleOutputContext } from './schedule-store';\nimport { isValidCronExpression, getNextRunTime } from './schedule-parser';\nimport { logger } from '../logger';\n\n/**\n * Simple glob-style pattern matching for workflow names\n * Supports * (any characters) and ? (single character)\n */\nfunction matchGlobPattern(pattern: string, value: string): boolean {\n // Escape regex special chars except * and ?\n const regexPattern = pattern\n .replace(/[.+^${}()|[\\]\\\\]/g, '\\\\$&')\n .replace(/\\*/g, '.*')\n .replace(/\\?/g, '.');\n return new RegExp(`^${regexPattern}$`).test(value);\n}\n\n/**\n * Check if a workflow is allowed by patterns\n */\nfunction isWorkflowAllowedByPatterns(\n workflow: string,\n allowedPatterns?: string[],\n deniedPatterns?: string[]\n): { allowed: boolean; reason?: string } {\n // If denied patterns exist and workflow matches any, deny\n if (deniedPatterns && deniedPatterns.length > 0) {\n for (const pattern of deniedPatterns) {\n if (matchGlobPattern(pattern, workflow)) {\n return {\n allowed: false,\n reason: `Workflow \"${workflow}\" matches denied pattern \"${pattern}\"`,\n };\n }\n }\n }\n\n // If allowed patterns exist, workflow must match at least one\n if (allowedPatterns && allowedPatterns.length > 0) {\n for (const pattern of allowedPatterns) {\n if (matchGlobPattern(pattern, workflow)) {\n return { allowed: true };\n }\n }\n return {\n allowed: false,\n reason: `Workflow \"${workflow}\" does not match any allowed patterns: ${allowedPatterns.join(', ')}`,\n };\n }\n\n // No patterns defined - allow by default\n return { allowed: true };\n}\n\n/**\n * Check permissions for creating a schedule\n */\nfunction checkSchedulePermissions(\n context: ScheduleToolContext,\n workflow: string,\n requestedScheduleType?: ScheduleType\n): { allowed: boolean; reason?: string } {\n const permissions = context.permissions;\n const scheduleType = requestedScheduleType || context.scheduleType || 'personal';\n\n // Enforce context-based restrictions first\n // From a DM, can only create personal schedules\n // From a channel, can only create channel schedules\n // From a group DM, can only create dm/group schedules\n if (context.allowedScheduleType && scheduleType !== context.allowedScheduleType) {\n const contextNames: Record<ScheduleType, string> = {\n personal: 'a direct message (DM)',\n channel: 'a channel',\n dm: 'a group DM',\n };\n const targetNames: Record<ScheduleType, string> = {\n personal: 'personal',\n channel: 'channel',\n dm: 'group',\n };\n return {\n allowed: false,\n reason: `From ${contextNames[context.allowedScheduleType]}, you can only create ${targetNames[context.allowedScheduleType]} schedules. To create a ${targetNames[scheduleType]} schedule, please use the appropriate context.`,\n };\n }\n\n // No permissions configured - allow everything (backwards compatible)\n if (!permissions) {\n return { allowed: true };\n }\n\n // Check schedule type permission\n switch (scheduleType) {\n case 'personal':\n if (permissions.allowPersonal === false) {\n return {\n allowed: false,\n reason: 'Personal schedules are not allowed in this configuration',\n };\n }\n break;\n case 'channel':\n if (permissions.allowChannel === false) {\n return {\n allowed: false,\n reason: 'Channel schedules are not allowed in this configuration',\n };\n }\n break;\n case 'dm':\n if (permissions.allowDm === false) {\n return {\n allowed: false,\n reason: 'DM schedules are not allowed in this configuration',\n };\n }\n break;\n }\n\n // Check workflow patterns\n return isWorkflowAllowedByPatterns(\n workflow,\n permissions.allowedWorkflows,\n permissions.deniedWorkflows\n );\n}\n\n/**\n * Tool action types\n */\nexport type ScheduleAction = 'create' | 'list' | 'cancel' | 'pause' | 'resume';\n\n/**\n * Target type for where to send the reminder/output\n */\nexport type TargetType = 'channel' | 'dm' | 'thread' | 'user';\n\n/**\n * Tool input arguments - AI provides structured data, no parsing needed\n */\nexport interface ScheduleToolArgs {\n action: ScheduleAction;\n\n // For create action - AI extracts and structures all of this:\n /** What to say/do when the schedule fires */\n reminder_text?: string;\n /** Where to send: channel, dm (to self), thread, or user (DM to specific user) */\n target_type?: TargetType;\n /** The Slack channel ID (C... for channels, D... for DMs) */\n target_id?: string;\n /** For thread replies: the thread_ts to reply to */\n thread_ts?: string;\n /** Is this a recurring schedule? */\n is_recurring?: boolean;\n /** For recurring: cron expression (AI generates this, e.g., \"* * * * *\" for every minute) */\n cron?: string;\n /** For one-time: ISO 8601 timestamp when to run */\n run_at?: string;\n /** Original natural language expression (for display only) */\n original_expression?: string;\n /** Optional workflow to run instead of just sending reminder_text */\n workflow?: string;\n /** Optional workflow inputs */\n workflow_inputs?: Record<string, unknown>;\n\n // For cancel/pause/resume actions:\n schedule_id?: string;\n}\n\n/**\n * Schedule type for permission checking\n */\nexport type ScheduleType = 'personal' | 'channel' | 'dm';\n\n/**\n * Permissions configuration for dynamic schedules\n */\nexport interface SchedulePermissions {\n /** Allow personal schedules (via DM or CLI) */\n allowPersonal?: boolean;\n /** Allow channel schedules (in Slack channels) */\n allowChannel?: boolean;\n /** Allow DM schedules (to specific users) */\n allowDm?: boolean;\n /** List of allowed workflow patterns (glob-style, e.g., \"report-*\") */\n allowedWorkflows?: string[];\n /** List of denied workflow patterns */\n deniedWorkflows?: string[];\n}\n\n/**\n * Context passed to the tool handler\n * This is generic and works with any frontend\n */\nexport interface ScheduleToolContext {\n /** Generic user ID (can be Slack user, GitHub user, CLI user, etc.) */\n userId: string;\n /** User display name (optional) */\n userName?: string;\n /** Context identifier: \"slack:U123\", \"github:user\", \"cli\", etc. */\n contextType: string;\n /** User's timezone (IANA format) */\n timezone?: string;\n /** Available workflows in the current config */\n availableWorkflows?: string[];\n /** Schedule type being created (for permission checking) */\n scheduleType?: ScheduleType;\n /** Permissions for dynamic schedule creation */\n permissions?: SchedulePermissions;\n /**\n * Allowed schedule type based on originating context.\n * When set, only schedules of this type can be created/managed.\n * - From DM: only 'personal' allowed\n * - From channel: only 'channel' allowed\n * - From group DM: only 'dm' allowed (targeting the group)\n */\n allowedScheduleType?: ScheduleType;\n}\n\n/**\n * Tool execution result\n */\nexport interface ScheduleToolResult {\n success: boolean;\n message: string;\n schedule?: Schedule;\n schedules?: Schedule[];\n error?: string;\n}\n\n/**\n * Format a schedule for display\n */\nfunction formatSchedule(schedule: Schedule): string {\n const time = schedule.isRecurring\n ? schedule.originalExpression\n : new Date(schedule.runAt!).toLocaleString();\n const status = schedule.status !== 'active' ? ` (${schedule.status})` : '';\n // For simple reminders, show the reminder text; for workflows, show the workflow name\n const displayName =\n schedule.workflow || (schedule.workflowInputs?.text as string) || 'scheduled message';\n const truncatedName =\n displayName.length > 30 ? displayName.substring(0, 27) + '...' : displayName;\n const output = schedule.outputContext?.type || 'none';\n\n return `\\`${schedule.id.substring(0, 8)}\\` - \"${truncatedName}\" - ${time} (→ ${output})${status}`;\n}\n\n/**\n * Format confirmation message for a new schedule\n */\nfunction formatCreateConfirmation(schedule: Schedule): string {\n const outputDesc = schedule.outputContext?.type\n ? `${schedule.outputContext.type}${schedule.outputContext.target ? `:${schedule.outputContext.target}` : ''}`\n : 'none';\n\n // For simple reminders, show the reminder text; for workflows, show the workflow name\n const displayName =\n schedule.workflow || (schedule.workflowInputs?.text as string) || 'scheduled message';\n\n if (schedule.isRecurring) {\n const nextRun = schedule.nextRunAt\n ? new Date(schedule.nextRunAt).toLocaleString('en-US', {\n weekday: 'long',\n month: 'short',\n day: 'numeric',\n hour: 'numeric',\n minute: '2-digit',\n })\n : 'calculating...';\n\n return `**Schedule created!**\n\n**${schedule.workflow ? 'Workflow' : 'Reminder'}**: ${displayName}\n**When**: ${schedule.originalExpression}\n**Output**: ${outputDesc}\n**Next run**: ${nextRun}\n\nID: \\`${schedule.id.substring(0, 8)}\\``;\n } else {\n const when = new Date(schedule.runAt!).toLocaleString('en-US', {\n weekday: 'long',\n month: 'short',\n day: 'numeric',\n hour: 'numeric',\n minute: '2-digit',\n });\n\n return `**Schedule created!**\n\n**${schedule.workflow ? 'Workflow' : 'Reminder'}**: ${displayName}\n**When**: ${when}\n**Output**: ${outputDesc}\n\nID: \\`${schedule.id.substring(0, 8)}\\``;\n }\n}\n\n/**\n * Format the list of schedules\n */\nfunction formatScheduleList(schedules: Schedule[]): string {\n if (schedules.length === 0) {\n return `You don't have any active schedules.\n\nTo create one: \"remind me every Monday at 9am to check PRs\" or \"schedule %daily-report every Monday at 9am\"`;\n }\n\n const lines = schedules.map((s, i) => `${i + 1}. ${formatSchedule(s)}`);\n\n return `**Your active schedules:**\n\n${lines.join('\\n')}\n\nTo cancel: \"cancel schedule <id>\"\nTo pause: \"pause schedule <id>\"`;\n}\n\n/**\n * Handle schedule tool actions\n */\nexport async function handleScheduleAction(\n args: ScheduleToolArgs,\n context: ScheduleToolContext\n): Promise<ScheduleToolResult> {\n const store = ScheduleStore.getInstance();\n\n // Ensure store is initialized\n if (!store.isInitialized()) {\n await store.initialize();\n }\n\n switch (args.action) {\n case 'create':\n return handleCreate(args, context, store);\n\n case 'list':\n return handleList(context, store);\n\n case 'cancel':\n return handleCancel(args, context, store);\n\n case 'pause':\n return handlePauseResume(args, context, store, 'paused');\n\n case 'resume':\n return handlePauseResume(args, context, store, 'active');\n\n default:\n return {\n success: false,\n message: `Unknown action: ${args.action}`,\n error: `Supported actions: create, list, cancel, pause, resume`,\n };\n }\n}\n\n/**\n * Handle create action - AI provides structured data, minimal parsing needed\n */\nasync function handleCreate(\n args: ScheduleToolArgs,\n context: ScheduleToolContext,\n store: ScheduleStore\n): Promise<ScheduleToolResult> {\n // Validate: need either reminder_text or workflow\n if (!args.reminder_text && !args.workflow) {\n return {\n success: false,\n message: 'Missing reminder content',\n error: 'Please specify either reminder_text (what to say) or workflow (what to run)',\n };\n }\n\n // Validate: need either cron (recurring) or run_at (one-time)\n if (!args.cron && !args.run_at) {\n return {\n success: false,\n message: 'Missing schedule timing',\n error:\n 'Please specify either cron (for recurring, e.g., \"* * * * *\") or run_at (ISO timestamp for one-time)',\n };\n }\n\n // Validate cron format if provided\n if (args.cron && !isValidCronExpression(args.cron)) {\n return {\n success: false,\n message: 'Invalid cron expression',\n error: `\"${args.cron}\" is not a valid cron expression. Format: \"minute hour day-of-month month day-of-week\"`,\n };\n }\n\n // Validate run_at format if provided\n let runAtTimestamp: number | undefined;\n if (args.run_at) {\n const parsed = new Date(args.run_at);\n if (isNaN(parsed.getTime())) {\n return {\n success: false,\n message: 'Invalid run_at timestamp',\n error: `\"${args.run_at}\" is not a valid ISO 8601 timestamp`,\n };\n }\n if (parsed.getTime() <= Date.now()) {\n return {\n success: false,\n message: 'run_at must be in the future',\n error: 'Cannot schedule a reminder in the past',\n };\n }\n runAtTimestamp = parsed.getTime();\n }\n\n // Validate target_id is provided when target_type is specified\n if (args.target_type && !args.target_id) {\n return {\n success: false,\n message: 'Missing target_id',\n error: `target_type \"${args.target_type}\" requires a target_id (channel ID, user ID, or thread_ts)`,\n };\n }\n\n // Determine schedule type from target\n // 'channel' -> channel schedule, 'user' -> dm schedule, 'dm' or 'thread' -> personal\n let scheduleType: ScheduleType = 'personal';\n if (args.target_type === 'channel') {\n scheduleType = 'channel';\n } else if (args.target_type === 'user') {\n scheduleType = 'dm'; // Sending to a specific user is a DM schedule\n }\n\n // Check permissions\n const workflowName = args.workflow || 'reminder';\n const permissionCheck = checkSchedulePermissions(context, workflowName, scheduleType);\n if (!permissionCheck.allowed) {\n logger.warn(\n `[ScheduleTool] Permission denied for user ${context.userId}: ${permissionCheck.reason}`\n );\n return {\n success: false,\n message: 'Permission denied',\n error: permissionCheck.reason || 'You do not have permission to create this schedule',\n };\n }\n\n // Validate workflow exists if specified and we have available workflows\n if (\n args.workflow &&\n context.availableWorkflows &&\n !context.availableWorkflows.includes(args.workflow)\n ) {\n return {\n success: false,\n message: `Workflow \"${args.workflow}\" not found`,\n error: `Available workflows: ${context.availableWorkflows.slice(0, 5).join(', ')}${context.availableWorkflows.length > 5 ? '...' : ''}`,\n };\n }\n\n try {\n const timezone = context.timezone || 'UTC';\n const isRecurring = args.is_recurring === true || !!args.cron;\n\n // Build output context from AI-provided target info\n let outputContext: ScheduleOutputContext | undefined;\n if (args.target_type && args.target_id) {\n outputContext = {\n type: 'slack', // Currently only Slack supported\n target: args.target_id, // Channel ID (C... or D...)\n threadId: args.thread_ts, // Thread timestamp for replies\n metadata: {\n targetType: args.target_type,\n reminderText: args.reminder_text,\n },\n };\n }\n\n // Calculate next run time\n let nextRunAt: number | undefined;\n if (isRecurring && args.cron) {\n nextRunAt = getNextRunTime(args.cron, timezone).getTime();\n } else if (runAtTimestamp) {\n nextRunAt = runAtTimestamp;\n }\n\n // Create the schedule - AI has done all the parsing\n // workflow is only set when explicitly provided (e.g., from YAML cron jobs)\n // For simple reminders, workflow is undefined and scheduler posts text directly\n const schedule = await store.createAsync({\n creatorId: context.userId,\n creatorContext: context.contextType,\n creatorName: context.userName,\n timezone,\n schedule: args.cron || '',\n runAt: runAtTimestamp,\n isRecurring,\n originalExpression: args.original_expression || args.cron || args.run_at || '',\n workflow: args.workflow, // Only set if explicitly provided\n workflowInputs:\n args.workflow_inputs || (args.reminder_text ? { text: args.reminder_text } : undefined),\n outputContext,\n nextRunAt,\n });\n\n const displayText = args.reminder_text || args.workflow || 'scheduled task';\n logger.info(\n `[ScheduleTool] Created schedule ${schedule.id} for user ${context.userId}: \"${displayText}\"`\n );\n\n return {\n success: true,\n message: formatCreateConfirmation(schedule),\n schedule,\n };\n } catch (error) {\n const errorMsg = error instanceof Error ? error.message : 'Unknown error';\n logger.warn(`[ScheduleTool] Failed to create schedule: ${errorMsg}`);\n\n return {\n success: false,\n message: `Failed to create schedule: ${errorMsg}`,\n error: errorMsg,\n };\n }\n}\n\n/**\n * Handle list action\n * Users can only see their own schedules to protect privacy\n */\nasync function handleList(\n context: ScheduleToolContext,\n store: ScheduleStore\n): Promise<ScheduleToolResult> {\n // Only show schedules created by this user - protects privacy of personal schedules\n const allUserSchedules = await store.getByCreatorAsync(context.userId);\n const schedules = allUserSchedules.filter(s => s.status !== 'completed');\n\n // If in a specific context, optionally filter to show only relevant schedules\n let filteredSchedules = schedules;\n if (context.allowedScheduleType) {\n // Map schedule output context to schedule type for filtering\n filteredSchedules = schedules.filter(s => {\n const scheduleOutputType = s.outputContext?.type;\n if (!scheduleOutputType || scheduleOutputType === 'none') {\n return context.allowedScheduleType === 'personal';\n }\n if (scheduleOutputType === 'slack') {\n const target = s.outputContext?.target || '';\n if (target.startsWith('#') || target.match(/^C[A-Z0-9]+$/)) {\n return context.allowedScheduleType === 'channel';\n }\n if (target.startsWith('@') || target.match(/^U[A-Z0-9]+$/)) {\n return context.allowedScheduleType === 'dm';\n }\n }\n return context.allowedScheduleType === 'personal';\n });\n }\n\n return {\n success: true,\n message: formatScheduleList(filteredSchedules),\n schedules: filteredSchedules,\n };\n}\n\n/**\n * Handle cancel action\n * Only the creator can cancel their own schedules\n */\nasync function handleCancel(\n args: ScheduleToolArgs,\n context: ScheduleToolContext,\n store: ScheduleStore\n): Promise<ScheduleToolResult> {\n // Try to find by ID - only search in user's own schedules\n let schedule: Schedule | undefined;\n\n if (args.schedule_id) {\n // Only search in schedules created by this user\n const userSchedules = await store.getByCreatorAsync(context.userId);\n\n // Try exact match first\n schedule = userSchedules.find(s => s.id === args.schedule_id);\n\n // Try partial ID match (first 8 chars)\n if (!schedule) {\n schedule = userSchedules.find(s => s.id.startsWith(args.schedule_id!));\n }\n }\n\n if (!schedule) {\n return {\n success: false,\n message: 'Schedule not found',\n error: `Could not find schedule with ID \"${args.schedule_id}\" in your schedules. Use \"list my schedules\" to see your schedules.`,\n };\n }\n\n // Double-check ownership (defensive - already filtered above)\n if (schedule.creatorId !== context.userId) {\n logger.warn(\n `[ScheduleTool] Attempted cross-user schedule cancellation: ${context.userId} tried to cancel ${schedule.id} owned by ${schedule.creatorId}`\n );\n return {\n success: false,\n message: 'Not your schedule',\n error: 'You can only cancel your own schedules.',\n };\n }\n\n // Delete the schedule\n await store.deleteAsync(schedule.id);\n\n logger.info(`[ScheduleTool] Cancelled schedule ${schedule.id} for user ${context.userId}`);\n\n return {\n success: true,\n message: `**Schedule cancelled!**\n\nWas: \"${schedule.workflow}\" scheduled for ${schedule.originalExpression}`,\n };\n}\n\n/**\n * Handle pause/resume actions\n * Only the creator can pause/resume their own schedules\n */\nasync function handlePauseResume(\n args: ScheduleToolArgs,\n context: ScheduleToolContext,\n store: ScheduleStore,\n newStatus: 'active' | 'paused'\n): Promise<ScheduleToolResult> {\n if (!args.schedule_id) {\n return {\n success: false,\n message: 'Missing schedule ID',\n error: 'Please specify which schedule to pause/resume.',\n };\n }\n\n // Only search in schedules created by this user\n const userSchedules = await store.getByCreatorAsync(context.userId);\n\n // Try exact match first\n let schedule = userSchedules.find(s => s.id === args.schedule_id);\n\n // Try partial ID match (first 8 chars)\n if (!schedule) {\n schedule = userSchedules.find(s => s.id.startsWith(args.schedule_id!));\n }\n\n if (!schedule) {\n return {\n success: false,\n message: 'Schedule not found',\n error: `Could not find schedule with ID \"${args.schedule_id}\" in your schedules.`,\n };\n }\n\n // Double-check ownership (defensive - already filtered above)\n if (schedule.creatorId !== context.userId) {\n logger.warn(\n `[ScheduleTool] Attempted cross-user schedule modification: ${context.userId} tried to modify ${schedule.id} owned by ${schedule.creatorId}`\n );\n return {\n success: false,\n message: 'Not your schedule',\n error: 'You can only modify your own schedules.',\n };\n }\n\n // Update status\n const updated = await store.updateAsync(schedule.id, { status: newStatus });\n\n const action = newStatus === 'paused' ? 'paused' : 'resumed';\n logger.info(`[ScheduleTool] ${action} schedule ${schedule.id} for user ${context.userId}`);\n\n return {\n success: true,\n message: `**Schedule ${action}!**\n\n\"${schedule.workflow}\" - ${schedule.originalExpression}`,\n schedule: updated,\n };\n}\n\n/**\n * Get the schedule tool definition for registration with AI providers\n *\n * The AI is responsible for:\n * 1. Extracting the target (channel ID, user ID, thread_ts) from the conversation context\n * 2. Determining if the schedule is recurring or one-time\n * 3. Generating the cron expression OR ISO timestamp\n * 4. Extracting the reminder text or workflow name\n */\nexport function getScheduleToolDefinition(): CustomToolDefinition {\n return {\n name: 'schedule',\n description: `Schedule, list, and manage reminders or workflow executions.\n\nYOU (the AI) must extract and structure all scheduling parameters. Do NOT pass natural language time expressions - convert them to cron or ISO timestamps.\n\nCRITICAL WORKFLOW RULE:\n- To schedule a WORKFLOW, the user MUST use a '%' prefix (e.g., \"schedule %my-workflow daily\").\n- If the '%' prefix is present, extract the word following it as the 'workflow' parameter (without the '%').\n- If the '%' prefix is NOT present, the request is a simple text reminder. The ENTIRE user request (excluding the schedule expression) MUST be placed in the 'reminder_text' parameter.\n- DO NOT guess or infer a workflow name from a user's request without the '%' prefix.\n\nACTIONS:\n- create: Schedule a new reminder or workflow\n- list: Show user's active schedules\n- cancel: Remove a schedule by ID\n- pause/resume: Temporarily disable/enable a schedule\n\nFOR CREATE ACTION - Extract these from user's request:\n1. WHAT:\n - If user says \"schedule %some-workflow ...\", populate 'workflow' with \"some-workflow\".\n - Otherwise, populate 'reminder_text' with the user's full request text.\n2. WHERE: Use the CURRENT channel from context\n - target_id: The channel ID from context (C... for channels, D... for DMs)\n - target_type: \"channel\" for public/private channels, \"dm\" for direct messages\n - ONLY use target_type=\"thread\" with thread_ts if user is INSIDE a thread\n - When NOT in a thread, reminders post as NEW messages (not thread replies)\n3. WHEN: Either cron (for recurring) OR run_at (ISO 8601 for one-time)\n - Recurring: Generate cron expression (minute hour day-of-month month day-of-week)\n - One-time: Generate ISO 8601 timestamp\n\nCRON EXAMPLES:\n- \"every minute\" → cron: \"* * * * *\"\n- \"every hour\" → cron: \"0 * * * *\"\n- \"every day at 9am\" → cron: \"0 9 * * *\"\n- \"every Monday at 9am\" → cron: \"0 9 * * 1\"\n- \"weekdays at 8:30am\" → cron: \"30 8 * * 1-5\"\n- \"every 5 minutes\" → cron: \"*/5 * * * *\"\n\nONE-TIME EXAMPLES:\n- \"in 2 hours\" → run_at: \"<ISO timestamp 2 hours from now>\"\n- \"tomorrow at 3pm\" → run_at: \"2026-02-08T15:00:00Z\"\n\nUSAGE EXAMPLES:\n\nUser in DM: \"remind me to check builds every day at 9am\"\n→ {\n \"action\": \"create\",\n \"reminder_text\": \"check builds\",\n \"is_recurring\": true,\n \"cron\": \"0 9 * * *\",\n \"target_type\": \"dm\",\n \"target_id\": \"<DM channel ID from context, e.g., D09SZABNLG3>\",\n \"original_expression\": \"every day at 9am\"\n }\n\nUser in #security channel: \"schedule %security-scan every Monday at 10am\"\n→ {\n \"action\": \"create\",\n \"workflow\": \"security-scan\",\n \"is_recurring\": true,\n \"cron\": \"0 10 * * 1\",\n \"target_type\": \"channel\",\n \"target_id\": \"<channel ID from context, e.g., C05ABC123>\",\n \"original_expression\": \"every Monday at 10am\"\n }\n\nUser in #security channel: \"run security-scan every Monday at 10am\" (NO % prefix!)\n→ {\n \"action\": \"create\",\n \"reminder_text\": \"run security-scan every Monday at 10am\",\n \"is_recurring\": true,\n \"cron\": \"0 10 * * 1\",\n \"target_type\": \"channel\",\n \"target_id\": \"<channel ID from context, e.g., C05ABC123>\",\n \"original_expression\": \"every Monday at 10am\"\n }\n\nUser in DM: \"remind me in 2 hours to review the PR\"\n→ {\n \"action\": \"create\",\n \"reminder_text\": \"review the PR\",\n \"is_recurring\": false,\n \"run_at\": \"2026-02-07T18:00:00Z\",\n \"target_type\": \"dm\",\n \"target_id\": \"<DM channel ID from context>\",\n \"original_expression\": \"in 2 hours\"\n }\n\nUser inside a thread: \"remind me about this tomorrow\"\n→ {\n \"action\": \"create\",\n \"reminder_text\": \"Check this thread\",\n \"is_recurring\": false,\n \"run_at\": \"2026-02-08T09:00:00Z\",\n \"target_type\": \"thread\",\n \"target_id\": \"<channel ID>\",\n \"thread_ts\": \"<thread_ts from context>\",\n \"original_expression\": \"tomorrow\"\n }\n\nUser: \"list my schedules\"\n→ { \"action\": \"list\" }\n\nUser: \"cancel schedule abc123\"\n→ { \"action\": \"cancel\", \"schedule_id\": \"abc123\" }`,\n inputSchema: {\n type: 'object',\n properties: {\n action: {\n type: 'string',\n enum: ['create', 'list', 'cancel', 'pause', 'resume'],\n description: 'What to do: create new, list existing, cancel/pause/resume by ID',\n },\n // WHAT to do\n reminder_text: {\n type: 'string',\n description: 'For create: the message/reminder text to send when triggered',\n },\n workflow: {\n type: 'string',\n description:\n 'For create: workflow ID to run. ONLY populate this if the user used the % prefix (e.g., \"%my-workflow\"). Extract the name without the % symbol. If no % prefix, use reminder_text instead.',\n },\n workflow_inputs: {\n type: 'object',\n description: 'For create: optional inputs to pass to the workflow',\n },\n // WHERE to send\n target_type: {\n type: 'string',\n enum: ['channel', 'dm', 'thread', 'user'],\n description:\n 'For create: where to send output. channel=public/private channel, dm=DM to self (current DM channel), user=DM to specific user, thread=reply in current thread',\n },\n target_id: {\n type: 'string',\n description:\n 'For create: Slack channel ID. Channels start with C, DMs start with D. Always use the channel ID from the current context.',\n },\n thread_ts: {\n type: 'string',\n description:\n 'For create with target_type=thread: the thread timestamp to reply to. Get this from the current thread context.',\n },\n // WHEN to run\n is_recurring: {\n type: 'boolean',\n description:\n 'For create: true for recurring schedules (cron), false for one-time (run_at)',\n },\n cron: {\n type: 'string',\n description:\n 'For create recurring: cron expression (minute hour day-of-month month day-of-week). Examples: \"0 9 * * *\" (daily 9am), \"* * * * *\" (every minute), \"0 9 * * 1\" (Mondays 9am)',\n },\n run_at: {\n type: 'string',\n description:\n 'For create one-time: ISO 8601 timestamp when to run (e.g., \"2026-02-07T15:00:00Z\")',\n },\n original_expression: {\n type: 'string',\n description:\n 'For create: the original natural language expression from user (for display only)',\n },\n // For cancel/pause/resume\n schedule_id: {\n type: 'string',\n description:\n 'For cancel/pause/resume: the schedule ID to act on (first 8 chars is enough)',\n },\n },\n required: ['action'],\n },\n exec: '', // Not used - this tool has a custom handler\n };\n}\n\n/**\n * Check if this is the schedule tool and should be handled specially\n */\nexport function isScheduleTool(toolName: string): boolean {\n return toolName === 'schedule';\n}\n\n/**\n * Determine schedule type from context\n */\nfunction determineScheduleType(\n contextType: string,\n outputType?: 'slack' | 'github' | 'webhook' | 'none',\n outputTarget?: string\n): ScheduleType {\n // If output is to a Slack channel (starts with # or C), it's a channel schedule\n if (outputType === 'slack' && outputTarget) {\n if (outputTarget.startsWith('#') || outputTarget.match(/^C[A-Z0-9]+$/)) {\n return 'channel';\n }\n // If output is to a Slack user (starts with @ or U), it's a DM schedule\n if (outputTarget.startsWith('@') || outputTarget.match(/^U[A-Z0-9]+$/)) {\n return 'dm';\n }\n }\n\n // CLI and GitHub are personal schedules\n if (contextType === 'cli' || contextType.startsWith('github:')) {\n return 'personal';\n }\n\n // Default to personal for Slack DM context\n return 'personal';\n}\n\n/**\n * Map Slack channel type to allowed schedule type\n */\nfunction slackChannelTypeToScheduleType(channelType: 'channel' | 'dm' | 'group'): ScheduleType {\n switch (channelType) {\n case 'channel':\n return 'channel';\n case 'group':\n return 'dm'; // Group DMs map to 'dm' schedule type\n case 'dm':\n default:\n return 'personal';\n }\n}\n\n/**\n * Build schedule tool context from various sources\n */\nexport function buildScheduleToolContext(\n sources: {\n slackContext?: {\n userId: string;\n userName?: string;\n timezone?: string;\n channelType?: 'channel' | 'dm' | 'group';\n };\n cliContext?: { userId?: string };\n githubContext?: { login: string };\n },\n availableWorkflows?: string[],\n permissions?: SchedulePermissions,\n outputInfo?: { outputType?: 'slack' | 'github' | 'webhook' | 'none'; outputTarget?: string }\n): ScheduleToolContext {\n // Prefer Slack context, then GitHub, then CLI\n if (sources.slackContext) {\n const contextType = `slack:${sources.slackContext.userId}`;\n const scheduleType = determineScheduleType(\n contextType,\n outputInfo?.outputType,\n outputInfo?.outputTarget\n );\n\n // Determine allowed schedule type based on originating Slack context\n // This enforces that from a DM you can only create personal schedules, etc.\n let allowedScheduleType: ScheduleType | undefined;\n if (sources.slackContext.channelType) {\n allowedScheduleType = slackChannelTypeToScheduleType(sources.slackContext.channelType);\n }\n\n // Override schedule type based on Slack channel type if no explicit output\n let finalScheduleType = scheduleType;\n if (!outputInfo?.outputType && sources.slackContext.channelType) {\n finalScheduleType = slackChannelTypeToScheduleType(sources.slackContext.channelType);\n }\n\n return {\n userId: sources.slackContext.userId,\n userName: sources.slackContext.userName,\n contextType,\n timezone: sources.slackContext.timezone,\n availableWorkflows,\n scheduleType: finalScheduleType,\n permissions,\n allowedScheduleType,\n };\n }\n\n if (sources.githubContext) {\n return {\n userId: sources.githubContext.login,\n contextType: `github:${sources.githubContext.login}`,\n timezone: 'UTC', // GitHub doesn't provide timezone\n availableWorkflows,\n scheduleType: 'personal',\n permissions,\n allowedScheduleType: 'personal', // GitHub context only allows personal schedules\n };\n }\n\n // CLI/default context\n return {\n userId: sources.cliContext?.userId || process.env.USER || 'cli-user',\n contextType: 'cli',\n timezone: Intl.DateTimeFormat().resolvedOptions().timeZone || 'UTC',\n availableWorkflows,\n scheduleType: 'personal',\n permissions,\n allowedScheduleType: 'personal', // CLI context only allows personal schedules\n };\n}\n"],"mappings":"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","names":["fs","path","val"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/footer.ts","../../src/github-check-service.ts","../../src/github-comments.ts","../../src/frontends/github-frontend.ts"],"sourcesContent":["/**\n * Centralized footer generation for Visor comments and outputs\n */\n\nexport interface FooterOptions {\n /**\n * Include metadata like lastUpdated, triggeredBy, commitSha\n */\n includeMetadata?: {\n lastUpdated: string;\n triggeredBy: string;\n commitSha?: string;\n };\n /**\n * Include horizontal rule separator before footer\n */\n includeSeparator?: boolean;\n}\n\n/**\n * Generate a standard Visor footer with branding and optional tip\n */\nexport function generateFooter(options: FooterOptions = {}): string {\n const { includeMetadata, includeSeparator = true } = options;\n\n const parts: string[] = [];\n\n // Add separator\n if (includeSeparator) {\n parts.push('---');\n parts.push('');\n }\n\n // Add branding\n parts.push(\n '*Powered by [Visor](https://probelabs.com/visor) from [Probelabs](https://probelabs.com)*'\n );\n\n // Add metadata if provided\n if (includeMetadata) {\n const { lastUpdated, triggeredBy, commitSha } = includeMetadata;\n const commitInfo = commitSha ? ` | Commit: ${commitSha.substring(0, 7)}` : '';\n parts.push('');\n parts.push(`*Last updated: ${lastUpdated} | Triggered by: ${triggeredBy}${commitInfo}*`);\n }\n\n // Add tip\n parts.push('');\n parts.push('💡 **TIP:** You can chat with Visor using `/visor ask <your question>`');\n\n return parts.join('\\n');\n}\n\n/**\n * Check if a string contains a Visor footer\n */\nexport function hasVisorFooter(text: string): boolean {\n return (\n text.includes('*Powered by [Visor](https://probelabs.com/visor)') ||\n text.includes('*Powered by [Visor](https://github.com/probelabs/visor)')\n );\n}\n","/**\n * GitHub Check Service for creating and managing check runs based on failure conditions\n */\n\nimport { Octokit } from '@octokit/rest';\nimport { FailureConditionResult } from './types/config';\nimport { ReviewIssue } from './reviewer';\nimport { generateFooter } from './footer';\n\nexport interface CheckRunOptions {\n owner: string;\n repo: string;\n head_sha: string;\n name: string;\n details_url?: string;\n external_id?: string;\n engine_mode?: 'legacy' | 'state-machine'; // M4: Track which engine mode was used\n}\n\nexport interface CheckRunAnnotation {\n path: string;\n start_line: number;\n end_line: number;\n annotation_level: 'notice' | 'warning' | 'failure';\n message: string;\n title?: string;\n raw_details?: string;\n}\n\nexport interface CheckRunSummary {\n title: string;\n summary: string;\n text?: string;\n}\n\nexport type CheckRunStatus = 'queued' | 'in_progress' | 'completed';\nexport type CheckRunConclusion =\n | 'success'\n | 'failure'\n | 'neutral'\n | 'cancelled'\n | 'timed_out'\n | 'action_required';\n\n/**\n * Service for managing GitHub Check Runs based on Visor failure conditions\n */\nexport class GitHubCheckService {\n private octokit: Octokit;\n private maxAnnotations = 50; // GitHub API limit\n\n constructor(octokit: Octokit) {\n this.octokit = octokit;\n }\n\n /**\n * Create a new check run in queued status\n * M4: Includes engine_mode metadata in summary\n */\n async createCheckRun(\n options: CheckRunOptions,\n summary?: CheckRunSummary\n ): Promise<{ id: number; url: string }> {\n try {\n // M4: Add engine mode metadata to summary if provided\n const enhancedSummary =\n summary && options.engine_mode\n ? {\n ...summary,\n summary: `${summary.summary}\\n\\n_Engine: ${options.engine_mode}_`,\n }\n : summary;\n\n const response = await this.octokit.rest.checks.create({\n owner: options.owner,\n repo: options.repo,\n name: options.name,\n head_sha: options.head_sha,\n status: 'queued',\n details_url: options.details_url,\n external_id: options.external_id,\n output: enhancedSummary\n ? {\n title: enhancedSummary.title,\n summary: enhancedSummary.summary,\n text: enhancedSummary.text,\n }\n : undefined,\n });\n\n return {\n id: response.data.id,\n url: response.data.html_url || '',\n };\n } catch (error) {\n throw new Error(\n `Failed to create check run: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n }\n\n /**\n * Update check run to in_progress status\n */\n async updateCheckRunInProgress(\n owner: string,\n repo: string,\n check_run_id: number,\n summary?: CheckRunSummary\n ): Promise<void> {\n try {\n await this.octokit.rest.checks.update({\n owner,\n repo,\n check_run_id,\n status: 'in_progress',\n output: summary\n ? {\n title: summary.title,\n summary: summary.summary,\n text: summary.text,\n }\n : undefined,\n });\n } catch (error) {\n throw new Error(\n `Failed to update check run to in_progress: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n }\n\n /**\n * Complete a check run with results based on failure conditions\n */\n async completeCheckRun(\n owner: string,\n repo: string,\n check_run_id: number,\n checkName: string,\n failureResults: FailureConditionResult[],\n reviewIssues: ReviewIssue[] = [],\n executionError?: string,\n filesChangedInCommit?: string[],\n prNumber?: number,\n currentCommitSha?: string\n ): Promise<void> {\n try {\n // Clear old annotations from ALL previous check runs (including older runs on the same commit)\n // This prevents annotation accumulation when a check runs multiple times\n if (prNumber && currentCommitSha) {\n await this.clearOldAnnotations(\n owner,\n repo,\n prNumber,\n checkName,\n currentCommitSha,\n check_run_id\n );\n }\n\n const { conclusion, summary } = this.determineCheckRunConclusion(\n checkName,\n failureResults,\n reviewIssues,\n executionError\n );\n\n // Filter out system-level issues (fail_if conditions, internal errors)\n // These should not appear as annotations but affect the check conclusion\n let filteredIssues = reviewIssues.filter(\n issue => !(issue.file === 'system' && issue.line === 0)\n );\n\n // Filter annotations to only include files changed in this commit\n // This prevents old annotations from previous commits showing up in the Files tab\n if (filesChangedInCommit && filesChangedInCommit.length > 0) {\n filteredIssues = filteredIssues.filter(issue =>\n filesChangedInCommit.some(changedFile => issue.file === changedFile)\n );\n }\n\n const annotations = this.convertIssuesToAnnotations(filteredIssues);\n\n await this.octokit.rest.checks.update({\n owner,\n repo,\n check_run_id,\n status: 'completed',\n conclusion,\n completed_at: new Date().toISOString(),\n output: {\n title: summary.title,\n summary: summary.summary,\n text: summary.text,\n annotations: annotations.slice(0, this.maxAnnotations), // GitHub limit\n },\n });\n } catch (error) {\n throw new Error(\n `Failed to complete check run: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n }\n\n /**\n * Determine check run conclusion based on failure conditions and issues\n */\n private determineCheckRunConclusion(\n checkName: string,\n failureResults: FailureConditionResult[],\n reviewIssues: ReviewIssue[],\n executionError?: string\n ): { conclusion: CheckRunConclusion; summary: CheckRunSummary } {\n // Handle execution errors first\n if (executionError) {\n return {\n conclusion: 'failure',\n summary: {\n title: '❌ Check Execution Failed',\n summary: `The ${checkName} check failed to execute properly.`,\n text: `**Error:** ${executionError}\\n\\nPlease check your configuration and try again.`,\n },\n };\n }\n\n // Check if any fail_if conditions were met\n const failedConditions = failureResults.filter(result => result.failed);\n\n // Count issues by severity (for informational display only)\n const criticalIssues = reviewIssues.filter(issue => issue.severity === 'critical').length;\n const errorIssues = reviewIssues.filter(issue => issue.severity === 'error').length;\n const warningIssues = reviewIssues.filter(issue => issue.severity === 'warning').length;\n const totalIssues = reviewIssues.length;\n\n // Determine conclusion ONLY based on fail_if evaluation results\n // The presence of issues (critical, error, warning) does NOT affect the conclusion\n // Only the fail_if condition determines pass/fail status\n let conclusion: CheckRunConclusion;\n let title: string;\n let summaryText: string;\n let details: string;\n\n if (failedConditions.length > 0) {\n // Check fails if fail_if condition is met\n conclusion = 'failure';\n title = '🚨 Check Failed';\n summaryText = `${checkName} check failed because fail_if condition was met.`;\n\n details = this.formatCheckDetails(failureResults, reviewIssues, {\n failedConditions: failedConditions.length,\n warningConditions: 0,\n criticalIssues,\n errorIssues,\n warningIssues,\n totalIssues,\n });\n } else {\n // No fail_if conditions met - check passes regardless of issues found\n conclusion = 'success';\n\n // Adjust the title and summary based on issues found, but conclusion remains success\n if (criticalIssues > 0 || errorIssues > 0) {\n title = '✅ Check Passed (Issues Found)';\n summaryText = `${checkName} check passed. Found ${criticalIssues} critical and ${errorIssues} error issues, but fail_if condition was not met.`;\n } else if (warningIssues > 0) {\n title = '✅ Check Passed (Warnings Found)';\n summaryText = `${checkName} check passed. Found ${warningIssues} warning${warningIssues === 1 ? '' : 's'}, but fail_if condition was not met.`;\n } else {\n title = '✅ Check Passed';\n summaryText = `${checkName} check completed successfully with no issues found.`;\n }\n\n details = this.formatCheckDetails(failureResults, reviewIssues, {\n failedConditions: 0,\n warningConditions: 0,\n criticalIssues,\n errorIssues,\n warningIssues,\n totalIssues,\n });\n }\n\n return {\n conclusion,\n summary: {\n title,\n summary: summaryText,\n text: details,\n },\n };\n }\n\n /**\n * Format detailed check results for the check run summary\n */\n private formatCheckDetails(\n failureResults: FailureConditionResult[],\n reviewIssues: ReviewIssue[],\n counts: {\n failedConditions: number;\n warningConditions: number;\n criticalIssues: number;\n errorIssues: number;\n warningIssues: number;\n totalIssues: number;\n }\n ): string {\n const sections: string[] = [];\n\n // Summary section\n sections.push('## 📊 Summary');\n sections.push(`- **Total Issues:** ${counts.totalIssues}`);\n if (counts.criticalIssues > 0) {\n sections.push(`- **Critical Issues:** ${counts.criticalIssues}`);\n }\n if (counts.errorIssues > 0) {\n sections.push(`- **Error Issues:** ${counts.errorIssues}`);\n }\n if (counts.warningIssues > 0) {\n sections.push(`- **Warning Issues:** ${counts.warningIssues}`);\n }\n sections.push('');\n\n // Failure conditions section\n if (failureResults.length > 0) {\n sections.push('## 🔍 Failure Condition Results');\n\n const failedConditions = failureResults.filter(result => result.failed);\n const passedConditions = failureResults.filter(result => !result.failed);\n\n if (failedConditions.length > 0) {\n sections.push('### Failed Conditions');\n failedConditions.forEach(condition => {\n sections.push(\n `- **${condition.conditionName}**: ${condition.message || condition.expression}`\n );\n if (condition.severity) {\n const icon = this.getSeverityEmoji(condition.severity);\n sections.push(` - Severity: ${icon} ${condition.severity}`);\n }\n });\n sections.push('');\n }\n\n if (passedConditions.length > 0) {\n sections.push('### Passed Conditions');\n passedConditions.forEach(condition => {\n sections.push(\n `- **${condition.conditionName}**: ${condition.message || 'Condition passed'}`\n );\n });\n sections.push('');\n }\n }\n\n // Issues by category section\n if (reviewIssues.length > 0) {\n const issuesByCategory = this.groupIssuesByCategory(reviewIssues);\n sections.push('## Issues by Category');\n\n Object.entries(issuesByCategory).forEach(([category, issues]) => {\n if (issues.length > 0) {\n sections.push(\n `### ${category.charAt(0).toUpperCase() + category.slice(1)} (${issues.length})`\n );\n\n // Show only first 5 issues per category to keep the summary concise\n const displayIssues = issues.slice(0, 5);\n displayIssues.forEach(issue => {\n const severityIcon = this.getSeverityEmoji(issue.severity);\n sections.push(`- ${severityIcon} **${issue.file}:${issue.line}** - ${issue.message}`);\n });\n\n if (issues.length > 5) {\n sections.push(`- *...and ${issues.length - 5} more ${category} issues*`);\n }\n sections.push('');\n }\n });\n }\n\n // Footer\n sections.push('');\n sections.push(generateFooter());\n\n return sections.join('\\n');\n }\n\n /**\n * Convert review issues to GitHub check run annotations\n */\n private convertIssuesToAnnotations(reviewIssues: ReviewIssue[]): CheckRunAnnotation[] {\n return reviewIssues\n .slice(0, this.maxAnnotations) // Respect GitHub's annotation limit\n .map(issue => ({\n path: issue.file,\n start_line: issue.line,\n end_line: issue.endLine || issue.line,\n annotation_level: this.mapSeverityToAnnotationLevel(issue.severity),\n message: issue.message,\n title: `${issue.category} Issue`,\n raw_details: issue.suggestion || undefined,\n }));\n }\n\n /**\n * Map Visor issue severity to GitHub annotation level\n */\n private mapSeverityToAnnotationLevel(severity: string): 'notice' | 'warning' | 'failure' {\n switch (severity) {\n case 'critical':\n case 'error':\n return 'failure';\n case 'warning':\n return 'warning';\n case 'info':\n default:\n return 'notice';\n }\n }\n\n /**\n * Group issues by category\n */\n private groupIssuesByCategory(issues: ReviewIssue[]): Record<string, ReviewIssue[]> {\n const grouped: Record<string, ReviewIssue[]> = {};\n\n issues.forEach(issue => {\n const category = issue.category || 'general';\n if (!grouped[category]) {\n grouped[category] = [];\n }\n grouped[category].push(issue);\n });\n\n return grouped;\n }\n\n /**\n * Get emoji for issue severity (allowed; step/category emojis are removed)\n */\n private getSeverityEmoji(severity: string): string {\n const iconMap: Record<string, string> = {\n critical: '🚨',\n error: '❌',\n warning: '⚠️',\n info: 'ℹ️',\n };\n return iconMap[String(severity || '').toLowerCase()] || '';\n }\n\n /**\n * Create multiple check runs for different checks with failure condition support\n */\n async createMultipleCheckRuns(\n options: CheckRunOptions,\n checkResults: Array<{\n checkName: string;\n failureResults: FailureConditionResult[];\n reviewIssues: ReviewIssue[];\n executionError?: string;\n }>\n ): Promise<Array<{ checkName: string; id: number; url: string }>> {\n const results: Array<{ checkName: string; id: number; url: string }> = [];\n\n for (const checkResult of checkResults) {\n try {\n // Create check run\n const checkRun = await this.createCheckRun({\n ...options,\n name: `Visor: ${checkResult.checkName}`,\n external_id: `visor-${checkResult.checkName}-${options.head_sha.substring(0, 7)}`,\n });\n\n // Update to in progress\n await this.updateCheckRunInProgress(options.owner, options.repo, checkRun.id, {\n title: `Running ${checkResult.checkName} check...`,\n summary: `Analyzing code with ${checkResult.checkName} check using AI.`,\n });\n\n // Complete with results\n await this.completeCheckRun(\n options.owner,\n options.repo,\n checkRun.id,\n checkResult.checkName,\n checkResult.failureResults,\n checkResult.reviewIssues,\n checkResult.executionError\n );\n\n results.push({\n checkName: checkResult.checkName,\n id: checkRun.id,\n url: checkRun.url,\n });\n } catch (error) {\n console.error(`Failed to create check run for ${checkResult.checkName}:`, error);\n // Continue with other checks even if one fails\n }\n }\n\n return results;\n }\n\n /**\n * Get check runs for a specific commit\n */\n async getCheckRuns(\n owner: string,\n repo: string,\n ref: string\n ): Promise<Array<{ id: number; name: string; status: string; conclusion: string | null }>> {\n try {\n const response = await this.octokit.rest.checks.listForRef({\n owner,\n repo,\n ref,\n filter: 'all',\n });\n\n return response.data.check_runs\n .filter(check => check.name.startsWith('Visor:'))\n .map(check => ({\n id: check.id,\n name: check.name,\n status: check.status,\n conclusion: check.conclusion,\n }));\n } catch (error) {\n throw new Error(\n `Failed to get check runs: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n }\n\n /**\n * Get check runs for a specific commit SHA\n * Returns all check runs with the given name on this commit\n */\n async getCheckRunsForCommit(\n owner: string,\n repo: string,\n commitSha: string,\n checkName: string\n ): Promise<Array<{ id: number; head_sha: string }>> {\n try {\n const checksResponse = await this.octokit.rest.checks.listForRef({\n owner,\n repo,\n ref: commitSha,\n check_name: `Visor: ${checkName}`,\n });\n\n return checksResponse.data.check_runs.map(check => ({\n id: check.id,\n head_sha: commitSha,\n }));\n } catch (error) {\n throw new Error(\n `Failed to get check runs for commit ${commitSha}: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n }\n\n /**\n * Clear annotations from old check runs on the current commit\n * This prevents annotation accumulation when a check runs multiple times on the same commit\n * (e.g., force push, re-running checks)\n */\n async clearOldAnnotations(\n owner: string,\n repo: string,\n prNumber: number, // Not used, kept for backward compatibility\n checkName: string,\n currentCommitSha: string,\n currentCheckRunId: number\n ): Promise<void> {\n try {\n // Get all check runs for this check name on the current commit\n const allCheckRuns = await this.getCheckRunsForCommit(\n owner,\n repo,\n currentCommitSha,\n checkName\n );\n\n // Filter out the CURRENT check run (by ID)\n // This handles the case where Visor runs multiple times on the same commit\n const oldRuns = allCheckRuns.filter(run => run.id !== currentCheckRunId);\n\n if (oldRuns.length === 0) {\n console.debug(`No old check runs to clear for ${checkName} on commit ${currentCommitSha}`);\n return;\n }\n\n console.debug(\n `Clearing ${oldRuns.length} old check run(s) for ${checkName} on commit ${currentCommitSha.substring(0, 7)} (keeping current run ${currentCheckRunId})`\n );\n\n // Update each old check run to have empty annotations\n for (const run of oldRuns) {\n try {\n await this.octokit.rest.checks.update({\n owner,\n repo,\n check_run_id: run.id,\n output: {\n title: 'Outdated',\n summary: 'This check has been superseded by a newer run.',\n annotations: [], // Clear annotations\n },\n });\n console.debug(`✓ Cleared annotations from check run ${run.id}`);\n } catch (error) {\n console.debug(`Could not clear annotations for check run ${run.id}:`, error);\n }\n }\n } catch (error) {\n // Don't fail the whole check if we can't clear old annotations\n console.warn('Failed to clear old annotations:', error);\n }\n }\n}\n","import { Octokit } from '@octokit/rest';\nimport { generateShortHumanId } from './utils/human-id';\nimport { logger } from './logger';\nimport { generateFooter } from './footer';\n\nexport interface Comment {\n id: number;\n body: string;\n user: {\n login: string;\n };\n created_at: string;\n updated_at: string;\n}\n\nexport interface RetryConfig {\n maxRetries: number;\n baseDelay: number;\n maxDelay: number;\n backoffFactor: number;\n}\n\nexport interface CommentMetadata {\n commentId: string;\n lastUpdated: string;\n triggeredBy: string;\n commitSha?: string;\n}\n\ninterface GitHubApiError {\n status?: number;\n response?: {\n status?: number;\n data?: {\n message?: string;\n };\n };\n}\n\n/**\n * Manages GitHub PR comments with dynamic updating capabilities\n */\nexport class CommentManager {\n private octokit: Octokit;\n private retryConfig: RetryConfig;\n\n constructor(octokit: Octokit, retryConfig?: Partial<RetryConfig>) {\n this.octokit = octokit;\n this.retryConfig = {\n maxRetries: 3,\n baseDelay: 1000,\n maxDelay: 10000,\n backoffFactor: 2,\n ...retryConfig,\n };\n }\n\n /**\n * Find existing Visor comment by comment ID marker\n */\n public async findVisorComment(\n owner: string,\n repo: string,\n prNumber: number,\n commentId?: string\n ): Promise<Comment | null> {\n try {\n const comments = await this.octokit.rest.issues.listComments({\n owner,\n repo,\n issue_number: prNumber,\n per_page: 100, // GitHub default max\n });\n\n for (const comment of comments.data) {\n if (comment.body && this.isVisorComment(comment.body, commentId)) {\n return comment as Comment;\n }\n }\n\n return null;\n } catch (error) {\n if (\n this.isRateLimitError(\n error as { status?: number; response?: { data?: { message?: string } } }\n )\n ) {\n await this.handleRateLimit(error as { response?: { headers?: Record<string, string> } });\n return this.findVisorComment(owner, repo, prNumber, commentId);\n }\n throw error;\n }\n }\n\n /**\n * Update existing comment or create new one with collision detection\n */\n public async updateOrCreateComment(\n owner: string,\n repo: string,\n prNumber: number,\n content: string,\n options: {\n commentId?: string;\n triggeredBy?: string;\n allowConcurrentUpdates?: boolean;\n commitSha?: string;\n /** Cached GitHub comment ID to use for updates when listComments may not return it yet (eventual consistency) */\n cachedGithubCommentId?: number;\n } = {}\n ): Promise<Comment> {\n const {\n commentId = this.generateCommentId(),\n triggeredBy = 'unknown',\n allowConcurrentUpdates = false,\n commitSha,\n cachedGithubCommentId,\n } = options;\n\n return this.withRetry(async () => {\n // First try to find the comment via listComments API\n let existingComment = await this.findVisorComment(owner, repo, prNumber, commentId);\n\n // If not found but we have a cached GitHub ID, try to fetch it directly\n // This handles GitHub API eventual consistency where newly created comments\n // may not appear in listComments immediately\n if (!existingComment && cachedGithubCommentId) {\n try {\n const cachedComment = await this.octokit.rest.issues.getComment({\n owner,\n repo,\n comment_id: cachedGithubCommentId,\n });\n if (cachedComment.data && this.isVisorComment(cachedComment.data.body || '', commentId)) {\n existingComment = cachedComment.data as Comment;\n logger.debug(\n `[github-comments] Found comment via cached ID ${cachedGithubCommentId} (not visible in listComments yet)`\n );\n }\n } catch (_e) {\n // Comment may have been deleted, continue with create flow\n logger.debug(\n `[github-comments] Cached comment ${cachedGithubCommentId} not found, will create new`\n );\n }\n }\n\n const formattedContent = this.formatCommentWithMetadata(content, {\n commentId,\n lastUpdated: new Date().toISOString(),\n triggeredBy,\n commitSha,\n });\n\n if (existingComment) {\n // Check for collision if not allowing concurrent updates\n if (!allowConcurrentUpdates) {\n const currentComment = await this.octokit.rest.issues.getComment({\n owner,\n repo,\n comment_id: existingComment.id,\n });\n\n if (currentComment.data.updated_at !== existingComment.updated_at) {\n throw new Error(\n `Comment collision detected for comment ${commentId}. Another process may have updated it.`\n );\n }\n }\n\n const updatedComment = await this.octokit.rest.issues.updateComment({\n owner,\n repo,\n comment_id: existingComment.id,\n body: formattedContent,\n });\n\n logger.info(\n `✅ Successfully updated comment (ID: ${commentId}, GitHub ID: ${existingComment.id}) on PR #${prNumber} in ${owner}/${repo}`\n );\n\n return updatedComment.data as Comment;\n } else {\n const newComment = await this.octokit.rest.issues.createComment({\n owner,\n repo,\n issue_number: prNumber,\n body: formattedContent,\n });\n\n logger.info(\n `✅ Successfully created comment (ID: ${commentId}, GitHub ID: ${newComment.data.id}) on PR #${prNumber} in ${owner}/${repo}`\n );\n\n return newComment.data as Comment;\n }\n });\n }\n\n /**\n * Format comment content with metadata markers\n */\n public formatCommentWithMetadata(content: string, metadata: CommentMetadata): string {\n const { commentId, lastUpdated, triggeredBy, commitSha } = metadata;\n\n const footer = generateFooter({\n includeMetadata: {\n lastUpdated,\n triggeredBy,\n commitSha,\n },\n });\n\n return `<!-- visor-comment-id:${commentId} -->\n${content}\n\n${footer}\n<!-- /visor-comment-id:${commentId} -->`;\n }\n\n /**\n * Create collapsible sections for comment content\n */\n public createCollapsibleSection(\n title: string,\n content: string,\n isExpanded: boolean = false\n ): string {\n const openAttribute = isExpanded ? ' open' : '';\n return `<details${openAttribute}>\n<summary>${title}</summary>\n\n${content}\n\n</details>`;\n }\n\n /**\n * Group review results by check type with collapsible sections\n */\n public formatGroupedResults(\n results: Array<{ checkType: string; content: string; score?: number; issuesFound?: number }>,\n groupBy: 'check' | 'severity' = 'check'\n ): string {\n const grouped = this.groupResults(results, groupBy);\n const sections: string[] = [];\n\n for (const [groupKey, items] of Object.entries(grouped)) {\n const totalScore = items.reduce((sum, item) => sum + (item.score || 0), 0) / items.length;\n const totalIssues = items.reduce((sum, item) => sum + (item.issuesFound || 0), 0);\n\n const title = this.formatGroupTitle(groupKey, totalScore, totalIssues);\n\n const sectionContent = items.map(item => item.content).join('\\n\\n');\n sections.push(this.createCollapsibleSection(title, sectionContent, totalIssues > 0));\n }\n\n return sections.join('\\n\\n');\n }\n\n /**\n * Generate unique comment ID\n */\n private generateCommentId(): string {\n return generateShortHumanId();\n }\n\n /**\n * Check if comment is a Visor comment\n */\n private isVisorComment(body: string, commentId?: string): boolean {\n if (commentId) {\n // Check for the new format with exact matching - look for the exact ID followed by space or \" -->\"\n if (\n body.includes(`visor-comment-id:${commentId} `) ||\n body.includes(`visor-comment-id:${commentId} -->`)\n ) {\n return true;\n }\n // Check for legacy format (visor-review-* pattern) for backwards compatibility\n if (commentId.startsWith('pr-review-') && body.includes('visor-review-')) {\n return true;\n }\n // If we have a specific commentId but no exact match, return false\n return false;\n }\n // General Visor comment detection (only when no specific commentId provided)\n return (\n (body.includes('visor-comment-id:') && body.includes('<!-- /visor-comment-id:')) ||\n body.includes('visor-review-')\n );\n }\n\n /**\n * Extract comment ID from comment body\n */\n public extractCommentId(body: string): string | null {\n const match = body.match(/visor-comment-id:([a-f0-9-]+)/);\n return match ? match[1] : null;\n }\n\n /**\n * Handle rate limiting with exponential backoff\n */\n private async handleRateLimit(error: {\n response?: { headers?: Record<string, string> };\n }): Promise<void> {\n const resetTime = error.response?.headers?.['x-ratelimit-reset'];\n if (resetTime) {\n const resetDate = new Date(parseInt(resetTime) * 1000);\n const waitTime = Math.max(resetDate.getTime() - Date.now(), this.retryConfig.baseDelay);\n console.log(`Rate limit exceeded. Waiting ${Math.round(waitTime / 1000)}s until reset...`);\n await this.sleep(Math.min(waitTime, this.retryConfig.maxDelay));\n } else {\n await this.sleep(this.retryConfig.baseDelay);\n }\n }\n\n /**\n * Check if error is a rate limit error\n */\n private isRateLimitError(error: GitHubApiError): boolean {\n return error.status === 403 && (error.response?.data?.message?.includes('rate limit') ?? false);\n }\n\n /**\n * Check if error should not be retried (auth errors, not found, etc.)\n */\n private isNonRetryableError(error: GitHubApiError): boolean {\n // Don't retry auth errors, not found, etc., but allow rate limit errors to be handled separately\n const nonRetryableStatuses = [401, 404, 422]; // Unauthorized, Not Found, Unprocessable Entity\n const status = error.status || error.response?.status;\n\n // 403 is non-retryable unless it's a rate limit error\n if (status === 403) {\n return !this.isRateLimitError(error);\n }\n\n return status !== undefined && nonRetryableStatuses.includes(status);\n }\n\n /**\n * Retry wrapper with exponential backoff\n */\n private async withRetry<T>(operation: () => Promise<T>): Promise<T> {\n let lastError: Error = new Error('Unknown error');\n\n for (let attempt = 0; attempt <= this.retryConfig.maxRetries; attempt++) {\n try {\n return await operation();\n } catch (error) {\n lastError = error instanceof Error ? error : new Error(String(error));\n\n if (attempt === this.retryConfig.maxRetries) {\n break;\n }\n\n if (\n this.isRateLimitError(\n error as { status?: number; response?: { data?: { message?: string } } }\n )\n ) {\n await this.handleRateLimit(error as { response?: { headers?: Record<string, string> } });\n } else if (this.isNonRetryableError(error as GitHubApiError)) {\n // Don't retry auth errors, not found errors, etc.\n throw error;\n } else {\n const computed =\n this.retryConfig.baseDelay * Math.pow(this.retryConfig.backoffFactor, attempt);\n const delay =\n computed > this.retryConfig.maxDelay\n ? Math.max(0, this.retryConfig.maxDelay - 1)\n : computed;\n await this.sleep(delay);\n }\n }\n }\n\n throw lastError;\n }\n\n /**\n * Sleep utility\n */\n private sleep(ms: number): Promise<void> {\n return new Promise(resolve => {\n const t = setTimeout(resolve, ms);\n if (typeof (t as any).unref === 'function') {\n try {\n (t as any).unref();\n } catch {}\n }\n });\n }\n\n /**\n * Group results by specified criteria\n */\n private groupResults(\n results: Array<{ checkType: string; content: string; score?: number; issuesFound?: number }>,\n groupBy: 'check' | 'severity'\n ): Record<\n string,\n Array<{ checkType: string; content: string; score?: number; issuesFound?: number }>\n > {\n const grouped: Record<\n string,\n Array<{ checkType: string; content: string; score?: number; issuesFound?: number }>\n > = {};\n\n for (const result of results) {\n const key = groupBy === 'check' ? result.checkType : this.getSeverityGroup(result.score);\n if (!grouped[key]) {\n grouped[key] = [];\n }\n grouped[key].push(result);\n }\n\n return grouped;\n }\n\n /**\n * Get severity group based on score\n */\n private getSeverityGroup(score?: number): string {\n if (!score) return 'Unknown';\n if (score >= 90) return 'Excellent';\n if (score >= 75) return 'Good';\n if (score >= 50) return 'Needs Improvement';\n return 'Critical Issues';\n }\n\n // Emoji helper removed: plain titles are used in group headers\n\n /**\n * Format group title with score and issue count\n */\n private formatGroupTitle(groupKey: string, score: number, issuesFound: number): string {\n const formattedScore = Math.round(score);\n return `${groupKey} Review (Score: ${formattedScore}/100)${issuesFound > 0 ? ` - ${issuesFound} issues found` : ''}`;\n }\n}\n","import type { Frontend, FrontendContext } from './host';\nimport { logger } from '../logger';\nimport { extractTextFromJson } from '../utils/json-text-extractor';\n\n/**\n * Skeleton GitHub frontend.\n * - Subscribes to engine events via EventBus when present\n * - Maps key events to debug logs for now (no side effects)\n * - Real implementation will upsert checks and manage grouped PR comments\n */\ntype SectionState = {\n status: 'queued' | 'in_progress' | 'completed' | 'errored';\n conclusion?: 'success' | 'failure' | 'neutral' | 'skipped';\n issues?: number;\n lastUpdated: string;\n error?: string;\n content?: string;\n};\n\nexport class GitHubFrontend implements Frontend {\n public readonly name = 'github';\n private subs: Array<{ unsubscribe(): void }> = [];\n private checkRunIds: Map<string, number> = new Map();\n private revision = 0;\n private cachedCommentId?: string; // legacy single-thread id (kept for compatibility)\n // Group → (checkId → SectionState)\n private stepStatusByGroup: Map<string, Map<string, SectionState>> = new Map();\n\n // Debounce/coalescing state\n private debounceMs: number = 400;\n private maxWaitMs: number = 2000;\n private _timer: NodeJS.Timeout | null = null;\n private _lastFlush: number = 0;\n private _pendingIds: Set<string> = new Set<string>();\n\n // Mutex for serializing comment updates per group\n private updateLocks: Map<string, Promise<void>> = new Map();\n public minUpdateDelayMs: number = 1000; // Minimum delay between updates (public for testing)\n // Cache of created GitHub comment IDs per group to handle API eventual consistency\n private createdCommentGithubIds: Map<string, number> = new Map();\n\n start(ctx: FrontendContext): void {\n const log = ctx.logger;\n const bus = ctx.eventBus;\n const octokit = (ctx as any).octokit;\n const repo = ctx.run.repo;\n const pr = ctx.run.pr;\n const headSha = ctx.run.headSha;\n\n // Determine capabilities separately for comments vs check runs\n const canPostComments = !!(octokit && repo && pr);\n const canPostChecks = !!(octokit && repo && pr && headSha);\n\n // Create helpers if possible\n const svc = canPostChecks\n ? new (require('../github-check-service').GitHubCheckService)(octokit)\n : null;\n const CommentManager = require('../github-comments').CommentManager;\n const comments = canPostComments ? new CommentManager(octokit) : null;\n\n const threadKey =\n repo && pr && headSha\n ? `${repo.owner}/${repo.name}#${pr}@${(headSha || '').substring(0, 7)}`\n : ctx.run.runId;\n this.cachedCommentId = `visor-thread-${threadKey}`;\n\n // CheckScheduled → create queued check run\n this.subs.push(\n bus.on('CheckScheduled', async (env: any) => {\n const ev = (env && env.payload) || env;\n try {\n if (!canPostChecks || !svc) return;\n if (this.checkRunIds.has(ev.checkId)) return; // already created\n // Update local model only (no comment yet; wait for content)\n const group = this.getGroupForCheck(ctx, ev.checkId);\n this.upsertSectionState(group, ev.checkId, {\n status: 'queued',\n lastUpdated: new Date().toISOString(),\n });\n // Do not call updateGroupedComment here — avoid \"queued\" placeholder comments\n const res = await svc.createCheckRun(\n {\n owner: repo!.owner,\n repo: repo!.name,\n head_sha: headSha!,\n name: `Visor: ${ev.checkId}`,\n external_id: `visor:${ctx.run.runId}:${ev.checkId}`,\n engine_mode: 'state-machine',\n },\n { title: `${ev.checkId}`, summary: 'Queued' }\n );\n this.checkRunIds.set(ev.checkId, res.id);\n } catch (e) {\n log.warn(\n `[github-frontend] createCheckRun failed for ${ev.checkId}: ${e instanceof Error ? e.message : e}`\n );\n }\n })\n );\n\n // CheckCompleted → complete check run and update grouped comment\n this.subs.push(\n bus.on('CheckCompleted', async (env: any) => {\n const ev = (env && env.payload) || env;\n try {\n // Complete check run (only when we have headSha)\n if (canPostChecks && svc && this.checkRunIds.has(ev.checkId)) {\n const id = this.checkRunIds.get(ev.checkId)!;\n const issues = Array.isArray(ev.result?.issues) ? ev.result.issues : [];\n // Evaluate failure conditions so GitHub conclusion reflects actual pass/fail\n const failureResults = await this.evaluateFailureResults(ctx, ev.checkId, ev.result);\n await svc.completeCheckRun(\n repo!.owner,\n repo!.name,\n id,\n ev.checkId,\n failureResults,\n issues,\n undefined,\n undefined,\n pr!,\n headSha!\n );\n }\n\n // Update grouped summary comment\n if (canPostComments && comments) {\n const count = Array.isArray(ev.result?.issues) ? ev.result.issues.length : 0;\n const failureResults = await this.evaluateFailureResults(ctx, ev.checkId, ev.result);\n const failed = Array.isArray(failureResults)\n ? failureResults.some((r: any) => r && r.failed)\n : false;\n const group = this.getGroupForCheck(ctx, ev.checkId);\n // Extract text from JSON-like content if template didn't unwrap it properly\n const rawContent = (ev?.result as any)?.content;\n const extractedContent = extractTextFromJson(rawContent);\n this.upsertSectionState(group, ev.checkId, {\n status: 'completed',\n conclusion: failed ? 'failure' : 'success',\n issues: count,\n lastUpdated: new Date().toISOString(),\n content: extractedContent,\n });\n await this.updateGroupedComment(ctx, comments, group, ev.checkId);\n }\n } catch (e) {\n log.warn(\n `[github-frontend] handle CheckCompleted failed: ${e instanceof Error ? e.message : e}`\n );\n }\n })\n );\n\n // CheckErrored → mark failure and update comment\n this.subs.push(\n bus.on('CheckErrored', async (env: any) => {\n const ev = (env && env.payload) || env;\n try {\n if (canPostChecks && svc && this.checkRunIds.has(ev.checkId)) {\n const id = this.checkRunIds.get(ev.checkId)!;\n await svc.completeCheckRun(\n repo!.owner,\n repo!.name,\n id,\n ev.checkId,\n [],\n [],\n ev.error?.message || 'Execution error',\n undefined,\n pr!,\n headSha!\n );\n }\n if (canPostComments && comments) {\n const group = this.getGroupForCheck(ctx, ev.checkId);\n this.upsertSectionState(group, ev.checkId, {\n status: 'errored',\n conclusion: 'failure',\n issues: 0,\n lastUpdated: new Date().toISOString(),\n error: ev.error?.message || 'Execution error',\n });\n await this.updateGroupedComment(ctx, comments, group, ev.checkId);\n }\n } catch (e) {\n log.warn(\n `[github-frontend] handle CheckErrored failed: ${e instanceof Error ? e.message : e}`\n );\n }\n })\n );\n\n // StateTransition: update summary on terminal\n this.subs.push(\n bus.on('StateTransition', async (env: any) => {\n const ev = (env && env.payload) || env;\n try {\n if (ev.to === 'Completed' || ev.to === 'Error') {\n if (canPostComments && comments) {\n for (const group of this.stepStatusByGroup.keys()) {\n await this.updateGroupedComment(ctx, comments, group);\n }\n }\n }\n } catch (e) {\n log.warn(\n `[github-frontend] handle StateTransition failed: ${e instanceof Error ? e.message : e}`\n );\n }\n })\n );\n }\n\n stop(): void {\n for (const s of this.subs) s.unsubscribe();\n this.subs = [];\n }\n\n private async buildFullBody(ctx: FrontendContext, group: string): Promise<string> {\n const header = this.renderThreadHeader(ctx, group);\n const sections = this.renderSections(ctx, group);\n return `${header}\n\n${sections}\n\n<!-- visor:thread-end key=\"${this.threadKeyFor(ctx)}\" -->`;\n }\n\n private threadKeyFor(ctx: FrontendContext): string {\n const r = ctx.run;\n return r.repo && r.pr && r.headSha\n ? `${r.repo.owner}/${r.repo.name}#${r.pr}@${(r.headSha || '').substring(0, 7)}`\n : r.runId;\n }\n\n private renderThreadHeader(ctx: FrontendContext, group: string): string {\n const header = {\n key: this.threadKeyFor(ctx),\n runId: ctx.run.runId,\n workflowId: ctx.run.workflowId,\n revision: this.revision,\n group,\n generatedAt: new Date().toISOString(),\n } as any;\n return `<!-- visor:thread=${JSON.stringify(header)} -->`;\n }\n\n private renderSections(ctx: FrontendContext, group: string): string {\n const lines: string[] = [];\n const groupMap = this.stepStatusByGroup.get(group) || new Map<string, SectionState>();\n for (const [checkId, st] of groupMap.entries()) {\n const start = `<!-- visor:section=${JSON.stringify({ id: checkId, revision: this.revision })} -->`;\n const end = `<!-- visor:section-end id=\"${checkId}\" -->`;\n const body =\n st.content && st.content.toString().trim().length > 0 ? st.content.toString().trim() : '';\n lines.push(`${start}\n${body}\n${end}`);\n }\n return lines.join('\\\\n\\\\n');\n }\n\n /**\n * Acquires a mutex lock for the given group and executes the update.\n * This ensures only one comment update happens at a time per group,\n * preventing race conditions where updates overwrite each other.\n *\n * Uses a proper queue-based mutex: each new caller chains onto the previous\n * lock, ensuring strict serialization even when multiple callers wait\n * simultaneously.\n */\n private async updateGroupedComment(\n ctx: FrontendContext,\n comments: any,\n group: string,\n changedIds?: string | string[]\n ) {\n // Get the current lock (if any) - we'll wait for it before proceeding\n const existingLock = this.updateLocks.get(group);\n\n // Create our own lock promise that we'll resolve when done\n // This must be created BEFORE awaiting existingLock to ensure proper chaining\n let resolveLock: () => void;\n const ourLock = new Promise<void>(resolve => {\n resolveLock = resolve;\n });\n\n // Immediately set our lock so subsequent callers will wait for us\n this.updateLocks.set(group, ourLock);\n\n try {\n // Wait for the previous lock to complete (if any)\n if (existingLock) {\n try {\n await existingLock;\n } catch (error) {\n logger.warn(\n `[github-frontend] Previous update for group ${group} failed: ${error instanceof Error ? error.message : error}`\n );\n // Continue with current update despite previous failure\n }\n }\n\n // Now perform our update\n await this.performGroupedCommentUpdate(ctx, comments, group, changedIds);\n } finally {\n // Clean up the lock from the map if it's still ours (no one else is waiting)\n if (this.updateLocks.get(group) === ourLock) {\n this.updateLocks.delete(group);\n }\n // Always resolve our lock - this allows the next waiter (if any) to proceed.\n // Even if another caller replaced our lock in the map, they're waiting on\n // our lock promise, so we must resolve it.\n resolveLock!();\n }\n }\n\n /**\n * Performs the actual comment update with delay enforcement.\n */\n private async performGroupedCommentUpdate(\n ctx: FrontendContext,\n comments: any,\n group: string,\n changedIds?: string | string[]\n ) {\n try {\n if (!ctx.run.repo || !ctx.run.pr) return;\n\n // Check if PR comments are enabled (default to true if not specified)\n const config = ctx.config as any;\n const prCommentEnabled = config?.output?.pr_comment?.enabled !== false;\n if (!prCommentEnabled) {\n logger.debug(\n `[github-frontend] PR comments disabled in config, skipping comment for group: ${group}`\n );\n return;\n }\n\n // Enforce minimum delay between updates to prevent API rate limiting\n const timeSinceLastFlush = Date.now() - this._lastFlush;\n if (this._lastFlush > 0 && timeSinceLastFlush < this.minUpdateDelayMs) {\n const delay = this.minUpdateDelayMs - timeSinceLastFlush;\n logger.debug(\n `[github-frontend] Waiting ${delay}ms before next update to prevent rate limiting`\n );\n await this.sleep(delay);\n }\n\n this.revision++;\n const commentId = this.commentIdForGroup(ctx, group);\n const mergedBody = await this.mergeIntoExistingBody(ctx, comments, group, changedIds);\n\n // Check if we have a cached GitHub comment ID from a previous creation\n // This handles GitHub API eventual consistency where listComments may not\n // immediately return a newly created comment\n const cachedGithubId = this.createdCommentGithubIds.get(commentId);\n\n const result = await comments.updateOrCreateComment(\n ctx.run.repo.owner,\n ctx.run.repo.name,\n ctx.run.pr,\n mergedBody,\n {\n commentId,\n triggeredBy: this.deriveTriggeredBy(ctx),\n commitSha: ctx.run.headSha,\n // Pass the cached GitHub comment ID if available\n cachedGithubCommentId: cachedGithubId,\n }\n );\n\n // Cache the GitHub comment ID for future updates\n if (result && result.id) {\n this.createdCommentGithubIds.set(commentId, result.id);\n }\n\n this._lastFlush = Date.now();\n } catch (e) {\n logger.debug(\n `[github-frontend] updateGroupedComment failed: ${e instanceof Error ? e.message : e}`\n );\n }\n }\n\n private deriveTriggeredBy(ctx: FrontendContext): string {\n const ev = (ctx.run as any).event || '';\n const actor = (ctx.run as any).actor;\n const commentEvents = new Set([\n 'issue_comment',\n 'issue_comment_created',\n 'pr_comment',\n 'comment',\n 'pull_request_review_comment',\n ]);\n if (commentEvents.has(ev) && actor) return actor;\n if (ev) return ev;\n return actor || 'unknown';\n }\n\n private async mergeIntoExistingBody(\n ctx: FrontendContext,\n comments: any,\n group: string,\n changedIds?: string | string[]\n ): Promise<string> {\n const repo = ctx.run.repo!;\n const pr = ctx.run.pr!;\n const existing = await comments.findVisorComment(\n repo.owner,\n repo.name,\n pr,\n this.commentIdForGroup(ctx, group)\n );\n if (!existing || !existing.body) return this.buildFullBody(ctx, group);\n const body = String(existing.body);\n const doc = this.parseSections(body);\n doc.header = {\n ...(doc.header || {}),\n key: this.threadKeyFor(ctx),\n revision: this.revision,\n group,\n } as any;\n if (changedIds) {\n const ids = Array.isArray(changedIds) ? changedIds : [changedIds];\n const fresh = this.renderSections(ctx, group);\n for (const id of ids) {\n const block = this.extractSectionById(fresh, id);\n if (block) doc.sections.set(id, block);\n }\n } else {\n // Add any missing new sections; leave others untouched to preserve text\n const fresh = this.renderSections(ctx, group);\n const map = this.stepStatusByGroup.get(group) || new Map<string, SectionState>();\n for (const [checkId] of map.entries()) {\n if (!doc.sections.has(checkId)) {\n const block = this.extractSectionById(fresh, checkId);\n if (block) doc.sections.set(checkId, block);\n }\n }\n }\n return this.serializeSections(doc);\n }\n\n private parseSections(body: string): { header?: any; sections: Map<string, string> } {\n const sections = new Map<string, string>();\n const headerRe = /<!--\\s*visor:thread=(\\{[\\s\\S]*?\\})\\s*-->/m;\n const startRe = /<!--\\s*visor:section=(\\{[\\s\\S]*?\\})\\s*-->/g;\n const endRe = /<!--\\s*visor:section-end\\s+id=\\\"([^\\\"]+)\\\"\\s*-->/g;\n\n // Helper: safely parse JSON and pick only allowed keys\n const safePick = (obj: any, allowed: Record<string, 'string' | 'number'>) => {\n if (!obj || typeof obj !== 'object' || Array.isArray(obj)) return undefined;\n const out: Record<string, unknown> = Object.create(null);\n for (const [k, t] of Object.entries(allowed)) {\n if (Object.prototype.hasOwnProperty.call(obj, k)) {\n const v = (obj as any)[k];\n if (t === 'string' && typeof v === 'string') out[k] = v;\n else if (t === 'number' && typeof v === 'number' && Number.isFinite(v)) out[k] = v;\n }\n }\n return out;\n };\n\n const safeParse = (text: string) => {\n try {\n return JSON.parse(text);\n } catch {\n return undefined;\n }\n };\n\n let header: any;\n try {\n const h = headerRe.exec(body);\n if (h) {\n const parsed = safeParse(h[1]);\n const picked = safePick(parsed, {\n key: 'string',\n runId: 'string',\n workflowId: 'string',\n revision: 'number',\n group: 'string',\n generatedAt: 'string',\n });\n header = picked;\n }\n } catch {}\n\n let cursor = 0;\n while (true) {\n const s = startRe.exec(body);\n if (!s) break;\n const metaRaw = safeParse(s[1]);\n const meta = safePick(metaRaw, { id: 'string', revision: 'number' }) || { id: '' };\n const startIdx = startRe.lastIndex;\n endRe.lastIndex = startIdx;\n const e = endRe.exec(body);\n if (!e) break;\n const id =\n typeof (meta as any).id === 'string' && (meta as any).id\n ? String((meta as any).id)\n : String(e[1]);\n const content = body.substring(startIdx, e.index).trim();\n const block = `<!-- visor:section=${JSON.stringify(meta)} -->\\n${content}\\n<!-- visor:section-end id=\"${id}\" -->`;\n sections.set(id, block);\n cursor = endRe.lastIndex;\n startRe.lastIndex = cursor;\n }\n return { header, sections };\n }\n\n private serializeSections(doc: { header?: any; sections: Map<string, string> }): string {\n const header = `<!-- visor:thread=${JSON.stringify({ ...(doc.header || {}), generatedAt: new Date().toISOString() })} -->`;\n const blocks = Array.from(doc.sections.values()).join('\\n\\n');\n const key = (doc.header && (doc.header as any).key) || '';\n return `${header}\\n\\n${blocks}\\n\\n<!-- visor:thread-end key=\"${key}\" -->`;\n }\n\n private extractSectionById(rendered: string, id: string): string | undefined {\n const rx = new RegExp(\n `<!--\\\\s*visor:section=(\\\\{[\\\\s\\\\S]*?\\\\})\\\\s*-->[\\\\s\\\\S]*?<!--\\\\s*visor:section-end\\\\s+id=\\\\\"${this.escapeRegExp(id)}\\\\\"\\\\s*-->`,\n 'm'\n );\n const m = rx.exec(rendered);\n return m ? m[0] : undefined;\n }\n\n private escapeRegExp(s: string): string {\n return s.replace(/[.*+?^${}()|[\\\\]\\\\]/g, '\\\\$&');\n }\n\n private getGroupForCheck(ctx: FrontendContext, checkId: string): string {\n try {\n const cfg: any = ctx.config || {};\n const g = cfg?.checks?.[checkId]?.group || cfg?.steps?.[checkId]?.group;\n if (typeof g === 'string' && g.trim().length > 0) return g;\n } catch {}\n return 'review';\n }\n\n private upsertSectionState(group: string, checkId: string, patch: Partial<SectionState>): void {\n let groupMap = this.stepStatusByGroup.get(group);\n if (!groupMap) {\n groupMap = new Map<string, SectionState>();\n this.stepStatusByGroup.set(group, groupMap);\n }\n const prev =\n groupMap.get(checkId) ||\n ({ status: 'queued', lastUpdated: new Date().toISOString() } as SectionState);\n groupMap.set(checkId, { ...prev, ...patch });\n }\n\n private commentIdForGroup(ctx: FrontendContext, group: string): string {\n // For \"dynamic\" group, each run creates a new comment (not updated across runs)\n // This is used for assistants that respond to issue comments where each\n // response should be a separate comment rather than updating a previous one.\n // Within a single run, the comment ID stays stable so updates work correctly.\n if (group === 'dynamic') {\n return `visor-thread-dynamic-${ctx.run.runId}`;\n }\n // Stable per-PR per-group ID (does not include commit SHA)\n const r = ctx.run;\n const base = r.repo && r.pr ? `${r.repo.owner}/${r.repo.name}#${r.pr}` : r.runId;\n return `visor-thread-${group}-${base}`;\n }\n\n /**\n * Compute failure condition results for a completed check so Check Runs map to the\n * correct GitHub conclusion. This mirrors the engine's evaluation for fail_if.\n */\n private async evaluateFailureResults(\n ctx: FrontendContext,\n checkId: string,\n result: { issues?: any[]; output?: unknown }\n ): Promise<any[]> {\n try {\n const config: any = ctx.config || {};\n const checks = (config && config.checks) || {};\n const checkCfg = checks[checkId] || {};\n const checkSchema = typeof checkCfg.schema === 'string' ? checkCfg.schema : 'code-review';\n const checkGroup = checkCfg.group || 'default';\n\n const { FailureConditionEvaluator } = require('../failure-condition-evaluator');\n const evaluator = new FailureConditionEvaluator();\n const reviewSummary = { issues: Array.isArray(result?.issues) ? result.issues : [] };\n\n const failures: any[] = [];\n\n // Global fail_if\n if (config.fail_if) {\n const failed = await evaluator.evaluateSimpleCondition(\n checkId,\n checkSchema,\n checkGroup,\n reviewSummary,\n config.fail_if\n );\n failures.push({\n conditionName: 'global_fail_if',\n failed,\n expression: config.fail_if,\n severity: 'error',\n haltExecution: false,\n });\n }\n\n // Check-level fail_if\n if (checkCfg.fail_if) {\n const failed = await evaluator.evaluateSimpleCondition(\n checkId,\n checkSchema,\n checkGroup,\n reviewSummary,\n checkCfg.fail_if\n );\n failures.push({\n conditionName: `${checkId}_fail_if`,\n failed,\n expression: checkCfg.fail_if,\n severity: 'error',\n haltExecution: false,\n });\n }\n\n return failures;\n } catch {\n return [];\n }\n }\n\n // Debounce helpers\n private scheduleUpdate(ctx: FrontendContext, comments: any, group: string, id?: string) {\n if (id) this._pendingIds.add(id);\n const now = Date.now();\n const since = now - this._lastFlush;\n const remaining = this.maxWaitMs - since;\n if (this._timer) clearTimeout(this._timer);\n const wait = Math.max(0, Math.min(this.debounceMs, remaining));\n this._timer = setTimeout(async () => {\n const ids = Array.from(this._pendingIds);\n this._pendingIds.clear();\n this._timer = null;\n await this.updateGroupedComment(ctx, comments, group, ids.length > 0 ? ids : undefined);\n this._lastFlush = Date.now();\n }, wait);\n }\n\n private async flushNow(ctx: FrontendContext, comments: any, group: string) {\n if (this._timer) {\n clearTimeout(this._timer);\n this._timer = null;\n }\n const ids = Array.from(this._pendingIds);\n this._pendingIds.clear();\n await this.updateGroupedComment(ctx, comments, group, ids.length > 0 ? ids : undefined);\n this._lastFlush = Date.now();\n }\n\n /**\n * Sleep utility for enforcing delays\n */\n private sleep(ms: number): Promise<void> {\n return new Promise(resolve => setTimeout(resolve, ms));\n }\n}\n"],"mappings":"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","names":["CommentManager"]}
|