diff --git a/apps/server/agent/manager.ts b/apps/server/agent/manager.ts index 1b25798..1fb3655 100644 --- a/apps/server/agent/manager.ts +++ b/apps/server/agent/manager.ts @@ -328,7 +328,7 @@ export class MultiProviderAgentManager implements AgentManager { this.createLogChunkCallback(agentId, alias, 1), ); - await this.repository.update(agentId, { pid, outputFilePath }); + await this.repository.update(agentId, { pid, outputFilePath, prompt }); // Write spawn diagnostic file for post-execution verification const diagnostic = { @@ -1086,6 +1086,7 @@ export class MultiProviderAgentManager implements AgentManager { updatedAt: Date; userDismissedAt?: Date | null; exitCode?: number | null; + prompt?: string | null; }): AgentInfo { return { id: agent.id, @@ -1102,6 +1103,7 @@ export class MultiProviderAgentManager implements AgentManager { updatedAt: agent.updatedAt, userDismissedAt: agent.userDismissedAt, exitCode: agent.exitCode ?? null, + prompt: agent.prompt ?? null, }; } } diff --git a/apps/server/agent/mock-manager.ts b/apps/server/agent/mock-manager.ts index d8cb009..68d49be 100644 --- a/apps/server/agent/mock-manager.ts +++ b/apps/server/agent/mock-manager.ts @@ -143,6 +143,7 @@ export class MockAgentManager implements AgentManager { createdAt: now, updatedAt: now, exitCode: null, + prompt: null, }; const record: MockAgentRecord = { diff --git a/apps/server/agent/types.ts b/apps/server/agent/types.ts index 1f5e029..dcd44c8 100644 --- a/apps/server/agent/types.ts +++ b/apps/server/agent/types.ts @@ -95,6 +95,8 @@ export interface AgentInfo { userDismissedAt?: Date | null; /** Process exit code — null while running or if not yet exited */ exitCode: number | null; + /** Full assembled prompt passed to the agent process — null for agents spawned before DB persistence */ + prompt: string | null; } /** diff --git a/apps/server/db/repositories/agent-repository.ts b/apps/server/db/repositories/agent-repository.ts index c54ca40..f4f4994 100644 --- a/apps/server/db/repositories/agent-repository.ts +++ b/apps/server/db/repositories/agent-repository.ts @@ -45,6 +45,7 @@ export interface UpdateAgentData { accountId?: string | null; pid?: number | null; exitCode?: number | null; + prompt?: string | null; outputFilePath?: string | null; result?: string | null; pendingQuestions?: string | null; diff --git a/apps/server/db/schema.ts b/apps/server/db/schema.ts index 1e371db..3fdb362 100644 --- a/apps/server/db/schema.ts +++ b/apps/server/db/schema.ts @@ -265,6 +265,7 @@ export const agents = sqliteTable('agents', { .default('execute'), pid: integer('pid'), exitCode: integer('exit_code'), // Process exit code for debugging crashes + prompt: text('prompt'), // Full assembled prompt passed to the agent process (persisted for durability after log cleanup) outputFilePath: text('output_file_path'), result: text('result'), pendingQuestions: text('pending_questions'), diff --git a/apps/server/dispatch/manager.test.ts b/apps/server/dispatch/manager.test.ts index c6558ad..cb0f4e6 100644 --- a/apps/server/dispatch/manager.test.ts +++ b/apps/server/dispatch/manager.test.ts @@ -71,6 +71,7 @@ function createMockAgentManager( createdAt: new Date(), updatedAt: new Date(), exitCode: null, + prompt: null, }; mockAgents.push(newAgent); return newAgent; @@ -103,6 +104,7 @@ function createIdleAgent(id: string, name: string): AgentInfo { createdAt: new Date(), updatedAt: new Date(), exitCode: null, + prompt: null, }; } diff --git a/apps/server/drizzle/0031_icy_silvermane.sql b/apps/server/drizzle/0031_icy_silvermane.sql new file mode 100644 index 0000000..43dbeed --- /dev/null +++ b/apps/server/drizzle/0031_icy_silvermane.sql @@ -0,0 +1 @@ +ALTER TABLE `agents` ADD `prompt` text; diff --git a/apps/server/drizzle/meta/0031_snapshot.json b/apps/server/drizzle/meta/0031_snapshot.json new file mode 100644 index 0000000..f60484b --- /dev/null +++ b/apps/server/drizzle/meta/0031_snapshot.json @@ -0,0 +1,1159 @@ +{ + "id": "f85b9df3-dead-4c46-90ac-cf36bcaa6eb4", + "prevId": "c0b6d7d3-c9da-440a-9fb8-9dd88df5672a", + "version": "6", + "dialect": "sqlite", + "tables": { + "accounts": { + "name": "accounts", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'claude'" + }, + "config_dir": { + "name": "config_dir", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "is_exhausted": { + "name": "is_exhausted", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "exhausted_until": { + "name": "exhausted_until", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "agents": { + "name": "agents", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "task_id": { + "name": "task_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "initiative_id": { + "name": "initiative_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "worktree_id": { + "name": "worktree_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'claude'" + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'idle'" + }, + "mode": { + "name": "mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'execute'" + }, + "pid": { + "name": "pid", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "prompt": { + "name": "prompt", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "output_file_path": { + "name": "output_file_path", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "result": { + "name": "result", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "pending_questions": { + "name": "pending_questions", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "agents_name_unique": { + "name": "agents_name_unique", + "columns": [ + "name" + ], + "isUnique": true + } + }, + "foreignKeys": { + "agents_task_id_tasks_id_fk": { + "name": "agents_task_id_tasks_id_fk", + "tableFrom": "agents", + "columnsFrom": [ + "task_id" + ], + "tableTo": "tasks", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "set null" + }, + "agents_initiative_id_initiatives_id_fk": { + "name": "agents_initiative_id_initiatives_id_fk", + "tableFrom": "agents", + "columnsFrom": [ + "initiative_id" + ], + "tableTo": "initiatives", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "set null" + }, + "agents_account_id_accounts_id_fk": { + "name": "agents_account_id_accounts_id_fk", + "tableFrom": "agents", + "columnsFrom": [ + "account_id" + ], + "tableTo": "accounts", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "initiative_projects": { + "name": "initiative_projects", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "initiative_id": { + "name": "initiative_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "initiative_project_unique": { + "name": "initiative_project_unique", + "columns": [ + "initiative_id", + "project_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "initiative_projects_initiative_id_initiatives_id_fk": { + "name": "initiative_projects_initiative_id_initiatives_id_fk", + "tableFrom": "initiative_projects", + "columnsFrom": [ + "initiative_id" + ], + "tableTo": "initiatives", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "initiative_projects_project_id_projects_id_fk": { + "name": "initiative_projects_project_id_projects_id_fk", + "tableFrom": "initiative_projects", + "columnsFrom": [ + "project_id" + ], + "tableTo": "projects", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "initiatives": { + "name": "initiatives", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'active'" + }, + "merge_requires_approval": { + "name": "merge_requires_approval", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "merge_target": { + "name": "merge_target", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "messages": { + "name": "messages", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "sender_type": { + "name": "sender_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "sender_id": { + "name": "sender_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "recipient_type": { + "name": "recipient_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "recipient_id": { + "name": "recipient_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'info'" + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "requires_response": { + "name": "requires_response", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'pending'" + }, + "parent_message_id": { + "name": "parent_message_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "messages_sender_id_agents_id_fk": { + "name": "messages_sender_id_agents_id_fk", + "tableFrom": "messages", + "columnsFrom": [ + "sender_id" + ], + "tableTo": "agents", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "set null" + }, + "messages_recipient_id_agents_id_fk": { + "name": "messages_recipient_id_agents_id_fk", + "tableFrom": "messages", + "columnsFrom": [ + "recipient_id" + ], + "tableTo": "agents", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "set null" + }, + "messages_parent_message_id_messages_id_fk": { + "name": "messages_parent_message_id_messages_id_fk", + "tableFrom": "messages", + "columnsFrom": [ + "parent_message_id" + ], + "tableTo": "messages", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "pages": { + "name": "pages", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "initiative_id": { + "name": "initiative_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "parent_page_id": { + "name": "parent_page_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "pages_initiative_id_initiatives_id_fk": { + "name": "pages_initiative_id_initiatives_id_fk", + "tableFrom": "pages", + "columnsFrom": [ + "initiative_id" + ], + "tableTo": "initiatives", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "pages_parent_page_id_pages_id_fk": { + "name": "pages_parent_page_id_pages_id_fk", + "tableFrom": "pages", + "columnsFrom": [ + "parent_page_id" + ], + "tableTo": "pages", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "phase_dependencies": { + "name": "phase_dependencies", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "phase_id": { + "name": "phase_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "depends_on_phase_id": { + "name": "depends_on_phase_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "phase_dependencies_phase_id_phases_id_fk": { + "name": "phase_dependencies_phase_id_phases_id_fk", + "tableFrom": "phase_dependencies", + "columnsFrom": [ + "phase_id" + ], + "tableTo": "phases", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "phase_dependencies_depends_on_phase_id_phases_id_fk": { + "name": "phase_dependencies_depends_on_phase_id_phases_id_fk", + "tableFrom": "phase_dependencies", + "columnsFrom": [ + "depends_on_phase_id" + ], + "tableTo": "phases", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "phases": { + "name": "phases", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "initiative_id": { + "name": "initiative_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "number": { + "name": "number", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'pending'" + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "phases_initiative_id_initiatives_id_fk": { + "name": "phases_initiative_id_initiatives_id_fk", + "tableFrom": "phases", + "columnsFrom": [ + "initiative_id" + ], + "tableTo": "initiatives", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "plans": { + "name": "plans", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "phase_id": { + "name": "phase_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "number": { + "name": "number", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'pending'" + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "plans_phase_id_phases_id_fk": { + "name": "plans_phase_id_phases_id_fk", + "tableFrom": "plans", + "columnsFrom": [ + "phase_id" + ], + "tableTo": "phases", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "projects": { + "name": "projects", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "projects_name_unique": { + "name": "projects_name_unique", + "columns": [ + "name" + ], + "isUnique": true + }, + "projects_url_unique": { + "name": "projects_url_unique", + "columns": [ + "url" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "task_dependencies": { + "name": "task_dependencies", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "task_id": { + "name": "task_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "depends_on_task_id": { + "name": "depends_on_task_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "task_dependencies_task_id_tasks_id_fk": { + "name": "task_dependencies_task_id_tasks_id_fk", + "tableFrom": "task_dependencies", + "columnsFrom": [ + "task_id" + ], + "tableTo": "tasks", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "task_dependencies_depends_on_task_id_tasks_id_fk": { + "name": "task_dependencies_depends_on_task_id_tasks_id_fk", + "tableFrom": "task_dependencies", + "columnsFrom": [ + "depends_on_task_id" + ], + "tableTo": "tasks", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "tasks": { + "name": "tasks", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "plan_id": { + "name": "plan_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "phase_id": { + "name": "phase_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "initiative_id": { + "name": "initiative_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'auto'" + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'execute'" + }, + "priority": { + "name": "priority", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'medium'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'pending'" + }, + "requires_approval": { + "name": "requires_approval", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "order": { + "name": "order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "tasks_plan_id_plans_id_fk": { + "name": "tasks_plan_id_plans_id_fk", + "tableFrom": "tasks", + "columnsFrom": [ + "plan_id" + ], + "tableTo": "plans", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "tasks_phase_id_phases_id_fk": { + "name": "tasks_phase_id_phases_id_fk", + "tableFrom": "tasks", + "columnsFrom": [ + "phase_id" + ], + "tableTo": "phases", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "tasks_initiative_id_initiatives_id_fk": { + "name": "tasks_initiative_id_initiatives_id_fk", + "tableFrom": "tasks", + "columnsFrom": [ + "initiative_id" + ], + "tableTo": "initiatives", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + }, + "internal": { + "indexes": {} + } +} \ No newline at end of file diff --git a/apps/server/drizzle/meta/_journal.json b/apps/server/drizzle/meta/_journal.json index ac6687d..e4b74fa 100644 --- a/apps/server/drizzle/meta/_journal.json +++ b/apps/server/drizzle/meta/_journal.json @@ -218,6 +218,13 @@ "when": 1772150400000, "tag": "0030_remove_task_approval", "breakpoints": true + }, + { + "idx": 31, + "version": "6", + "when": 1772798869413, + "tag": "0031_icy_silvermane", + "breakpoints": true } ] } \ No newline at end of file diff --git a/apps/server/test/integration/crash-race-condition.test.ts b/apps/server/test/integration/crash-race-condition.test.ts index f7ce25f..4af02a1 100644 --- a/apps/server/test/integration/crash-race-condition.test.ts +++ b/apps/server/test/integration/crash-race-condition.test.ts @@ -32,6 +32,7 @@ interface TestAgent { initiativeId: string | null; userDismissedAt: Date | null; exitCode: number | null; + prompt: string | null; } describe('Crash marking race condition', () => { @@ -72,7 +73,8 @@ describe('Crash marking race condition', () => { pendingQuestions: null, initiativeId: 'init-1', userDismissedAt: null, - exitCode: null + exitCode: null, + prompt: null, }; // Mock repository that tracks all update calls diff --git a/apps/server/trpc/routers/agent.test.ts b/apps/server/trpc/routers/agent.test.ts index 49de47e..21bcc6d 100644 --- a/apps/server/trpc/routers/agent.test.ts +++ b/apps/server/trpc/routers/agent.test.ts @@ -50,6 +50,7 @@ function makeAgentInfo(overrides: Record = {}) { updatedAt: new Date('2026-01-01T00:00:00Z'), userDismissedAt: null, exitCode: null, + prompt: null, ...overrides, }; } @@ -273,7 +274,7 @@ describe('getAgentPrompt', () => { await fs.writeFile(path.join(promptDir, 'PROMPT.md'), promptContent); const mockManager = { - get: vi.fn().mockResolvedValue(makeAgentInfo({ name: agentName })), + get: vi.fn().mockResolvedValue(makeAgentInfo({ name: agentName, prompt: null })), }; const ctx = createTestContext({ @@ -285,4 +286,42 @@ describe('getAgentPrompt', () => { expect(result).toEqual({ content: promptContent }); }); + + it('returns prompt from DB when agent.prompt is set (no file needed)', async () => { + const dbPromptContent = '# DB Prompt\nThis is persisted in the database'; + const mockManager = { + get: vi.fn().mockResolvedValue(makeAgentInfo({ name: 'test-agent', prompt: dbPromptContent })), + }; + + // workspaceRoot has no PROMPT.md — but DB value takes precedence + const ctx = createTestContext({ + agentManager: mockManager as any, + workspaceRoot: tmpDir, + }); + const caller = createCaller(ctx); + const result = await caller.getAgentPrompt({ id: 'agent-1' }); + + expect(result).toEqual({ content: dbPromptContent }); + }); + + it('falls back to PROMPT.md when agent.prompt is null in DB', async () => { + const agentName = 'test-agent'; + const promptDir = path.join(tmpDir, '.cw', 'agent-logs', agentName); + await fs.mkdir(promptDir, { recursive: true }); + const fileContent = '# File Prompt\nThis is from the file (legacy)'; + await fs.writeFile(path.join(promptDir, 'PROMPT.md'), fileContent); + + const mockManager = { + get: vi.fn().mockResolvedValue(makeAgentInfo({ name: agentName, prompt: null })), + }; + + const ctx = createTestContext({ + agentManager: mockManager as any, + workspaceRoot: tmpDir, + }); + const caller = createCaller(ctx); + const result = await caller.getAgentPrompt({ id: 'agent-1' }); + + expect(result).toEqual({ content: fileContent }); + }); }); diff --git a/apps/server/trpc/routers/agent.ts b/apps/server/trpc/routers/agent.ts index 53b42fc..8c2f2fe 100644 --- a/apps/server/trpc/routers/agent.ts +++ b/apps/server/trpc/routers/agent.ts @@ -342,6 +342,22 @@ export function agentProcedures(publicProcedure: ProcedureBuilder) { .query(async ({ ctx, input }) => { const agent = await resolveAgent(ctx, { id: input.id }); + const MAX_BYTES = 1024 * 1024; // 1 MB + + function truncateIfNeeded(text: string): string { + if (Buffer.byteLength(text, 'utf-8') > MAX_BYTES) { + const buf = Buffer.from(text, 'utf-8'); + return buf.slice(0, MAX_BYTES).toString('utf-8') + '\n\n[truncated — prompt exceeds 1 MB]'; + } + return text; + } + + // Prefer DB-persisted prompt (durable even after log file cleanup) + if (agent.prompt !== null) { + return { content: truncateIfNeeded(agent.prompt) }; + } + + // Fall back to filesystem for agents spawned before DB persistence was added const promptPath = path.join(ctx.workspaceRoot!, '.cw', 'agent-logs', agent.name, 'PROMPT.md'); let raw: string; @@ -357,13 +373,7 @@ export function agentProcedures(publicProcedure: ProcedureBuilder) { }); } - const MAX_BYTES = 1024 * 1024; // 1 MB - if (Buffer.byteLength(raw, 'utf-8') > MAX_BYTES) { - const buf = Buffer.from(raw, 'utf-8'); - raw = buf.slice(0, MAX_BYTES).toString('utf-8') + '\n\n[truncated — prompt exceeds 1 MB]'; - } - - return { content: raw }; + return { content: truncateIfNeeded(raw) }; }), }; } diff --git a/docs/database.md b/docs/database.md index a32cac5..1c683b7 100644 --- a/docs/database.md +++ b/docs/database.md @@ -70,6 +70,7 @@ All adapters use nanoid() for IDs, auto-manage timestamps, and use Drizzle's `.r | mode | text enum | 'execute' \| 'discuss' \| 'plan' \| 'detail' \| 'refine' | | pid | integer nullable | OS process ID | | exitCode | integer nullable | | +| prompt | text nullable | Full assembled prompt passed to agent at spawn; persisted for durability after log cleanup | | outputFilePath | text nullable | | | result | text nullable | JSON | | pendingQuestions | text nullable | JSON | diff --git a/docs/server-api.md b/docs/server-api.md index dc22e20..929c748 100644 --- a/docs/server-api.md +++ b/docs/server-api.md @@ -64,7 +64,7 @@ Each procedure uses `require*Repository(ctx)` helpers that throw `TRPCError(INTE | getAgentQuestions | query | Pending questions | | getAgentOutput | query | Full output from DB log chunks | | getAgentInputFiles | query | Files written to agent's `.cw/input/` dir (text only, sorted, 500 KB cap) | -| getAgentPrompt | query | Content of `.cw/agent-logs//PROMPT.md` (1 MB cap) | +| getAgentPrompt | query | Assembled prompt — reads from DB (`agents.prompt`) first; falls back to `.cw/agent-logs//PROMPT.md` for pre-persistence agents (1 MB cap) | | getActiveRefineAgent | query | Active refine agent for initiative | | listWaitingAgents | query | Agents waiting for input | | onAgentOutput | subscription | Live raw JSONL output stream via EventBus |