funolio-agent 0.17.8 → 1.0.3

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.
Files changed (223) hide show
  1. package/dist/approval.d.ts +7 -6
  2. package/dist/approval.d.ts.map +1 -1
  3. package/dist/approval.js +40 -10
  4. package/dist/approval.js.map +1 -1
  5. package/dist/auth/anthropic-subscription.d.ts +9 -29
  6. package/dist/auth/anthropic-subscription.d.ts.map +1 -1
  7. package/dist/auth/anthropic-subscription.js +12 -133
  8. package/dist/auth/anthropic-subscription.js.map +1 -1
  9. package/dist/auth/auto-detect.d.ts +28 -6
  10. package/dist/auth/auto-detect.d.ts.map +1 -1
  11. package/dist/auth/auto-detect.js +52 -205
  12. package/dist/auth/auto-detect.js.map +1 -1
  13. package/dist/auth/credential-reader.d.ts +24 -4
  14. package/dist/auth/credential-reader.d.ts.map +1 -1
  15. package/dist/auth/credential-reader.js +31 -256
  16. package/dist/auth/credential-reader.js.map +1 -1
  17. package/dist/auth/credential-status.d.ts +7 -27
  18. package/dist/auth/credential-status.d.ts.map +1 -1
  19. package/dist/auth/credential-status.js +7 -95
  20. package/dist/auth/credential-status.js.map +1 -1
  21. package/dist/auth/index.d.ts +9 -2
  22. package/dist/auth/index.d.ts.map +1 -1
  23. package/dist/auth/index.js +10 -10
  24. package/dist/auth/index.js.map +1 -1
  25. package/dist/auth/subscription-runtime.d.ts +19 -23
  26. package/dist/auth/subscription-runtime.d.ts.map +1 -1
  27. package/dist/auth/subscription-runtime.js +24 -292
  28. package/dist/auth/subscription-runtime.js.map +1 -1
  29. package/dist/auth/token-refresh.d.ts +19 -28
  30. package/dist/auth/token-refresh.d.ts.map +1 -1
  31. package/dist/auth/token-refresh.js +26 -464
  32. package/dist/auth/token-refresh.js.map +1 -1
  33. package/dist/backfill.js +2 -2
  34. package/dist/backfill.js.map +1 -1
  35. package/dist/bot-manager.d.ts +5 -6
  36. package/dist/bot-manager.d.ts.map +1 -1
  37. package/dist/bot-manager.js +20 -62
  38. package/dist/bot-manager.js.map +1 -1
  39. package/dist/clerk-model.d.ts +0 -1
  40. package/dist/clerk-model.d.ts.map +1 -1
  41. package/dist/clerk-model.js +24 -50
  42. package/dist/clerk-model.js.map +1 -1
  43. package/dist/commands/configure-provider.js +2 -2
  44. package/dist/commands/configure-provider.js.map +1 -1
  45. package/dist/commands/configure.d.ts.map +1 -1
  46. package/dist/commands/configure.js +10 -14
  47. package/dist/commands/configure.js.map +1 -1
  48. package/dist/commands/start.d.ts.map +1 -1
  49. package/dist/commands/start.js +51 -229
  50. package/dist/commands/start.js.map +1 -1
  51. package/dist/config.d.ts +1 -1
  52. package/dist/config.d.ts.map +1 -1
  53. package/dist/config.js +2 -2
  54. package/dist/config.js.map +1 -1
  55. package/dist/context-compressor.d.ts +6 -1
  56. package/dist/context-compressor.d.ts.map +1 -1
  57. package/dist/context-compressor.js +87 -18
  58. package/dist/context-compressor.js.map +1 -1
  59. package/dist/context-window.d.ts +2 -8
  60. package/dist/context-window.d.ts.map +1 -1
  61. package/dist/context-window.js +4 -4
  62. package/dist/context-window.js.map +1 -1
  63. package/dist/eval/orchestrator-front-door-replay.js +43 -2
  64. package/dist/eval/orchestrator-front-door-replay.js.map +1 -1
  65. package/dist/eval/orchestrator-todo-dispatch-replay.d.ts +2 -0
  66. package/dist/eval/orchestrator-todo-dispatch-replay.d.ts.map +1 -0
  67. package/dist/eval/orchestrator-todo-dispatch-replay.js +253 -0
  68. package/dist/eval/orchestrator-todo-dispatch-replay.js.map +1 -0
  69. package/dist/eval/orchestrator-todo-planning-replay.d.ts +2 -0
  70. package/dist/eval/orchestrator-todo-planning-replay.d.ts.map +1 -0
  71. package/dist/eval/orchestrator-todo-planning-replay.js +247 -0
  72. package/dist/eval/orchestrator-todo-planning-replay.js.map +1 -0
  73. package/dist/eval/policy-detection-replay.d.ts +2 -0
  74. package/dist/eval/policy-detection-replay.d.ts.map +1 -0
  75. package/dist/eval/policy-detection-replay.js +122 -0
  76. package/dist/eval/policy-detection-replay.js.map +1 -0
  77. package/dist/eval/todo-worker-runtime-replay.d.ts +2 -0
  78. package/dist/eval/todo-worker-runtime-replay.d.ts.map +1 -0
  79. package/dist/eval/todo-worker-runtime-replay.js +520 -0
  80. package/dist/eval/todo-worker-runtime-replay.js.map +1 -0
  81. package/dist/integration-tokens.d.ts +6 -0
  82. package/dist/integration-tokens.d.ts.map +1 -1
  83. package/dist/integration-tokens.js +43 -0
  84. package/dist/integration-tokens.js.map +1 -1
  85. package/dist/local-data.d.ts +134 -1
  86. package/dist/local-data.d.ts.map +1 -1
  87. package/dist/local-data.js +711 -18
  88. package/dist/local-data.js.map +1 -1
  89. package/dist/local-db.d.ts.map +1 -1
  90. package/dist/local-db.js +216 -12
  91. package/dist/local-db.js.map +1 -1
  92. package/dist/local-funnel.d.ts.map +1 -1
  93. package/dist/local-funnel.js +7 -0
  94. package/dist/local-funnel.js.map +1 -1
  95. package/dist/local-server.d.ts.map +1 -1
  96. package/dist/local-server.js +119 -96
  97. package/dist/local-server.js.map +1 -1
  98. package/dist/mcp/bridge-server.d.ts.map +1 -1
  99. package/dist/mcp/bridge-server.js +8 -2
  100. package/dist/mcp/bridge-server.js.map +1 -1
  101. package/dist/mcp/manager.d.ts +5 -0
  102. package/dist/mcp/manager.d.ts.map +1 -1
  103. package/dist/mcp/manager.js +131 -2
  104. package/dist/mcp/manager.js.map +1 -1
  105. package/dist/mcp/sync-cli-config.d.ts +5 -0
  106. package/dist/mcp/sync-cli-config.d.ts.map +1 -1
  107. package/dist/mcp/sync-cli-config.js +10 -2
  108. package/dist/mcp/sync-cli-config.js.map +1 -1
  109. package/dist/message-loop.d.ts +1 -10
  110. package/dist/message-loop.d.ts.map +1 -1
  111. package/dist/message-loop.js +179 -250
  112. package/dist/message-loop.js.map +1 -1
  113. package/dist/mqtt-client.d.ts +44 -0
  114. package/dist/mqtt-client.d.ts.map +1 -1
  115. package/dist/mqtt-client.js +2 -2
  116. package/dist/mqtt-client.js.map +1 -1
  117. package/dist/orchestration/front-door-policy.d.ts +26 -9
  118. package/dist/orchestration/front-door-policy.d.ts.map +1 -1
  119. package/dist/orchestration/front-door-policy.js +242 -69
  120. package/dist/orchestration/front-door-policy.js.map +1 -1
  121. package/dist/orchestration/orchestrator-blocked-prompt.d.ts +18 -0
  122. package/dist/orchestration/orchestrator-blocked-prompt.d.ts.map +1 -0
  123. package/dist/orchestration/orchestrator-blocked-prompt.js +46 -0
  124. package/dist/orchestration/orchestrator-blocked-prompt.js.map +1 -0
  125. package/dist/orchestration/orchestrator-final-response-prompt.d.ts +10 -0
  126. package/dist/orchestration/orchestrator-final-response-prompt.d.ts.map +1 -0
  127. package/dist/orchestration/orchestrator-final-response-prompt.js +39 -0
  128. package/dist/orchestration/orchestrator-final-response-prompt.js.map +1 -0
  129. package/dist/orchestration/orchestrator-operating-prompt.d.ts +11 -0
  130. package/dist/orchestration/orchestrator-operating-prompt.d.ts.map +1 -1
  131. package/dist/orchestration/orchestrator-operating-prompt.js +106 -36
  132. package/dist/orchestration/orchestrator-operating-prompt.js.map +1 -1
  133. package/dist/orchestration/policy-prompt.d.ts +6 -0
  134. package/dist/orchestration/policy-prompt.d.ts.map +1 -0
  135. package/dist/orchestration/policy-prompt.js +40 -0
  136. package/dist/orchestration/policy-prompt.js.map +1 -0
  137. package/dist/orchestration/worker-operating-prompt.d.ts +16 -0
  138. package/dist/orchestration/worker-operating-prompt.d.ts.map +1 -0
  139. package/dist/orchestration/worker-operating-prompt.js +75 -0
  140. package/dist/orchestration/worker-operating-prompt.js.map +1 -0
  141. package/dist/orchestrator.d.ts +19 -0
  142. package/dist/orchestrator.d.ts.map +1 -1
  143. package/dist/orchestrator.js +614 -54
  144. package/dist/orchestrator.js.map +1 -1
  145. package/dist/policy-detection.d.ts +40 -0
  146. package/dist/policy-detection.d.ts.map +1 -0
  147. package/dist/policy-detection.js +298 -0
  148. package/dist/policy-detection.js.map +1 -0
  149. package/dist/providers/anthropic.d.ts +0 -5
  150. package/dist/providers/anthropic.d.ts.map +1 -1
  151. package/dist/providers/anthropic.js +34 -51
  152. package/dist/providers/anthropic.js.map +1 -1
  153. package/dist/providers/claude-cli.d.ts.map +1 -1
  154. package/dist/providers/claude-cli.js +11 -2
  155. package/dist/providers/claude-cli.js.map +1 -1
  156. package/dist/providers/codex-cli.d.ts.map +1 -1
  157. package/dist/providers/codex-cli.js +121 -2
  158. package/dist/providers/codex-cli.js.map +1 -1
  159. package/dist/providers/index.d.ts +6 -2
  160. package/dist/providers/index.d.ts.map +1 -1
  161. package/dist/providers/index.js.map +1 -1
  162. package/dist/summarization-pipeline.d.ts +1 -4
  163. package/dist/summarization-pipeline.d.ts.map +1 -1
  164. package/dist/summarization-pipeline.js +43 -56
  165. package/dist/summarization-pipeline.js.map +1 -1
  166. package/dist/tools/analyze-image.js +2 -2
  167. package/dist/tools/analyze-image.js.map +1 -1
  168. package/dist/tools/edit-file.d.ts.map +1 -1
  169. package/dist/tools/edit-file.js +16 -1
  170. package/dist/tools/edit-file.js.map +1 -1
  171. package/dist/tools/git-tools.d.ts.map +1 -1
  172. package/dist/tools/git-tools.js +32 -1
  173. package/dist/tools/git-tools.js.map +1 -1
  174. package/dist/tools/index.d.ts.map +1 -1
  175. package/dist/tools/index.js +2 -0
  176. package/dist/tools/index.js.map +1 -1
  177. package/dist/tools/list-directory.d.ts.map +1 -1
  178. package/dist/tools/list-directory.js +23 -0
  179. package/dist/tools/list-directory.js.map +1 -1
  180. package/dist/tools/notify-user.d.ts.map +1 -1
  181. package/dist/tools/notify-user.js +15 -1
  182. package/dist/tools/notify-user.js.map +1 -1
  183. package/dist/tools/read-file.d.ts.map +1 -1
  184. package/dist/tools/read-file.js +3 -1
  185. package/dist/tools/read-file.js.map +1 -1
  186. package/dist/tools/request-mcp-install.js +5 -5
  187. package/dist/tools/request-mcp-install.js.map +1 -1
  188. package/dist/tools/run-command.d.ts.map +1 -1
  189. package/dist/tools/run-command.js +16 -4
  190. package/dist/tools/run-command.js.map +1 -1
  191. package/dist/tools/sandbox.d.ts.map +1 -1
  192. package/dist/tools/sandbox.js +6 -0
  193. package/dist/tools/sandbox.js.map +1 -1
  194. package/dist/tools/schedule-task.d.ts.map +1 -1
  195. package/dist/tools/schedule-task.js +37 -0
  196. package/dist/tools/schedule-task.js.map +1 -1
  197. package/dist/tools/search-codebase.d.ts.map +1 -1
  198. package/dist/tools/search-codebase.js +251 -32
  199. package/dist/tools/search-codebase.js.map +1 -1
  200. package/dist/tools/todo-tasks.d.ts +2 -0
  201. package/dist/tools/todo-tasks.d.ts.map +1 -1
  202. package/dist/tools/todo-tasks.js +203 -6
  203. package/dist/tools/todo-tasks.js.map +1 -1
  204. package/dist/tools/web-fetch.d.ts.map +1 -1
  205. package/dist/tools/web-fetch.js +21 -2
  206. package/dist/tools/web-fetch.js.map +1 -1
  207. package/dist/tools/web-search.d.ts.map +1 -1
  208. package/dist/tools/web-search.js +38 -34
  209. package/dist/tools/web-search.js.map +1 -1
  210. package/dist/types.d.ts +2 -0
  211. package/dist/types.d.ts.map +1 -1
  212. package/dist/wizard-state.d.ts.map +1 -1
  213. package/dist/wizard-state.js +30 -8
  214. package/dist/wizard-state.js.map +1 -1
  215. package/dist/wizard-support.d.ts +2 -2
  216. package/dist/wizard-support.d.ts.map +1 -1
  217. package/dist/wizard-support.js +80 -93
  218. package/dist/wizard-support.js.map +1 -1
  219. package/dist/workflow-engine.d.ts +3 -0
  220. package/dist/workflow-engine.d.ts.map +1 -1
  221. package/dist/workflow-engine.js +111 -82
  222. package/dist/workflow-engine.js.map +1 -1
  223. package/package.json +6 -1
@@ -49,6 +49,8 @@ exports.listProviderConnections = listProviderConnections;
49
49
  exports.getProviderConnection = getProviderConnection;
50
50
  exports.findProviderConnection = findProviderConnection;
51
51
  exports.updateProviderConnection = updateProviderConnection;
52
+ exports.normalizeCliProviderConnections = normalizeCliProviderConnections;
53
+ exports.pruneUnsupportedProviderConnections = pruneUnsupportedProviderConnections;
52
54
  exports.upsertOauthProviderConnection = upsertOauthProviderConnection;
53
55
  exports.deleteProviderConnection = deleteProviderConnection;
54
56
  exports.deleteAgentProfile = deleteAgentProfile;
@@ -82,6 +84,7 @@ exports.upsertMemoryFact = upsertMemoryFact;
82
84
  exports.searchMemoryFacts = searchMemoryFacts;
83
85
  exports.listMemoryFacts = listMemoryFacts;
84
86
  exports.deleteMemoryFact = deleteMemoryFact;
87
+ exports.getUserProfileFacts = getUserProfileFacts;
85
88
  exports.upsertEntity = upsertEntity;
86
89
  exports.getEntityGraph = getEntityGraph;
87
90
  exports.upsertEntityRelationship = upsertEntityRelationship;
@@ -111,6 +114,8 @@ exports.getEffectiveOrchestrationPolicy = getEffectiveOrchestrationPolicy;
111
114
  exports.getPendingOrchestrationPolicy = getPendingOrchestrationPolicy;
112
115
  exports.setPendingOrchestrationPolicy = setPendingOrchestrationPolicy;
113
116
  exports.clearPendingOrchestrationPolicy = clearPendingOrchestrationPolicy;
117
+ exports.getPolicyDetectionEventForMessage = getPolicyDetectionEventForMessage;
118
+ exports.recordPolicyDetectionEvent = recordPolicyDetectionEvent;
114
119
  exports.getOrchestrationCheckpoint = getOrchestrationCheckpoint;
115
120
  exports.setOrchestrationCheckpoint = setOrchestrationCheckpoint;
116
121
  exports.clearOrchestrationCheckpoint = clearOrchestrationCheckpoint;
@@ -124,12 +129,18 @@ exports.addTodoTask = addTodoTask;
124
129
  exports.editTodoTask = editTodoTask;
125
130
  exports.checkTodoTask = checkTodoTask;
126
131
  exports.completeTodoTaskByUser = completeTodoTaskByUser;
132
+ exports.listTodoArtifactsForTask = listTodoArtifactsForTask;
133
+ exports.completeTodoTaskByWorker = completeTodoTaskByWorker;
134
+ exports.blockTodoTaskByWorker = blockTodoTaskByWorker;
127
135
  exports.deleteTodoTask = deleteTodoTask;
128
136
  exports.reorderTodoTask = reorderTodoTask;
129
137
  exports.listTodoAudit = listTodoAudit;
130
138
  exports.moveNullTodoTasksToUnassigned = moveNullTodoTasksToUnassigned;
131
139
  exports.getLatestActiveTodoForConversation = getLatestActiveTodoForConversation;
140
+ exports.getNextActiveTodoForConversation = getNextActiveTodoForConversation;
141
+ exports.listCompletedTodoTasksForConversation = listCompletedTodoTasksForConversation;
132
142
  exports.createProject = createProject;
143
+ exports.upsertProject = upsertProject;
133
144
  exports.getProject = getProject;
134
145
  exports.getUnassignedProject = getUnassignedProject;
135
146
  exports.ensureUnassignedProject = ensureUnassignedProject;
@@ -197,6 +208,7 @@ exports.resetDerivedData = resetDerivedData;
197
208
  const local_db_1 = require("./local-db");
198
209
  const memory_text_1 = require("./memory-text");
199
210
  const memory_extraction_1 = require("./memory-extraction");
211
+ const orchestrator_blocked_prompt_1 = require("./orchestration/orchestrator-blocked-prompt");
200
212
  const crypto = __importStar(require("crypto"));
201
213
  const default_tool_profile_1 = require("./default-tool-profile");
202
214
  let _db = null;
@@ -418,6 +430,8 @@ function setOrchestratorBot(botId) {
418
430
  transaction();
419
431
  return db.prepare('SELECT * FROM agent_profile WHERE id = ?').get(botId);
420
432
  }
433
+ const CLI_PROVIDER_IDS = new Set(['claude-cli', 'codex-cli']);
434
+ const SUPPORTED_PROVIDER_ACCESS_MODES = new Set(['api', 'cli']);
421
435
  function createProviderConnection(p) {
422
436
  const db = getDb();
423
437
  const id = newId();
@@ -481,7 +495,93 @@ function updateProviderConnection(id, fields) {
481
495
  db.prepare(`UPDATE provider_connection SET ${sets.join(', ')} WHERE id = ?`).run(...vals);
482
496
  return db.prepare('SELECT * FROM provider_connection WHERE id = ?').get(id);
483
497
  }
498
+ function normalizeCliProviderConnections() {
499
+ const db = getDb();
500
+ const rows = db.prepare(`SELECT * FROM provider_connection WHERE provider_id IN ('claude-cli', 'codex-cli')`).all();
501
+ const updatedIds = [];
502
+ const tx = db.transaction(() => {
503
+ for (const row of rows) {
504
+ const needsNormalization = row.access_mode !== 'cli'
505
+ || row.auth_type !== 'cli'
506
+ || !!row.api_key_enc
507
+ || !!row.oauth_token
508
+ || !!row.oauth_refresh_token
509
+ || row.oauth_expires_at != null;
510
+ if (!needsNormalization)
511
+ continue;
512
+ db.prepare(`
513
+ UPDATE provider_connection
514
+ SET access_mode = 'cli',
515
+ auth_type = 'cli',
516
+ api_key_enc = NULL,
517
+ oauth_token = NULL,
518
+ oauth_refresh_token = NULL,
519
+ oauth_expires_at = NULL,
520
+ updated_at = datetime('now')
521
+ WHERE id = ?
522
+ `).run(row.id);
523
+ updatedIds.push(row.id);
524
+ }
525
+ });
526
+ tx();
527
+ return { updatedIds };
528
+ }
529
+ function pruneUnsupportedProviderConnections() {
530
+ const db = getDb();
531
+ const rows = listProviderConnections().filter((row) => !SUPPORTED_PROVIDER_ACCESS_MODES.has(row.access_mode));
532
+ if (rows.length === 0)
533
+ return { deletedIds: [], skippedIds: [] };
534
+ const referencedByBots = new Set(db.prepare(`SELECT provider_connection_id
535
+ FROM agent_profile
536
+ WHERE provider_connection_id IS NOT NULL`).all().map((row) => row.provider_connection_id));
537
+ const clerkConfig = getJsonSetting('clerk.config');
538
+ const protectedIds = new Set([
539
+ ...referencedByBots,
540
+ ...(clerkConfig?.providerConnectionId ? [clerkConfig.providerConnectionId] : []),
541
+ ]);
542
+ const deletedIds = [];
543
+ const skippedIds = [];
544
+ const tx = db.transaction(() => {
545
+ for (const row of rows) {
546
+ if (protectedIds.has(row.id)) {
547
+ skippedIds.push(row.id);
548
+ continue;
549
+ }
550
+ db.prepare('DELETE FROM provider_connection WHERE id = ?').run(row.id);
551
+ deletedIds.push(row.id);
552
+ }
553
+ });
554
+ tx();
555
+ return { deletedIds, skippedIds };
556
+ }
484
557
  function upsertOauthProviderConnection(input) {
558
+ if (CLI_PROVIDER_IDS.has(input.providerId)) {
559
+ const existing = findProviderConnection(input.providerId, 'cli')
560
+ || listProviderConnections().find((row) => row.provider_id === input.providerId);
561
+ if (existing) {
562
+ return updateProviderConnection(existing.id, {
563
+ accessMode: 'cli',
564
+ authType: 'cli',
565
+ apiKeyEnc: null,
566
+ oauthToken: null,
567
+ oauthRefreshToken: null,
568
+ oauthExpiresAt: null,
569
+ ...(input.defaultModel !== undefined ? { defaultModel: input.defaultModel } : {}),
570
+ ...(input.label !== undefined ? { label: input.label ?? undefined } : {}),
571
+ status: 'configured',
572
+ lastError: null,
573
+ });
574
+ }
575
+ return createProviderConnection({
576
+ providerId: input.providerId,
577
+ accessMode: 'cli',
578
+ authType: 'cli',
579
+ ...(input.label !== undefined ? { label: input.label ?? undefined } : {}),
580
+ ...(input.defaultModel !== undefined ? { defaultModel: input.defaultModel ?? undefined } : {}),
581
+ status: 'configured',
582
+ lastError: undefined,
583
+ });
584
+ }
485
585
  const existing = findProviderConnection(input.providerId, 'oauth')
486
586
  || listProviderConnections().find((row) => row.provider_id === input.providerId && row.auth_type === 'oauth');
487
587
  if (existing) {
@@ -1098,6 +1198,20 @@ function listMemoryFacts(opts) {
1098
1198
  function deleteMemoryFact(id) {
1099
1199
  return getDb().prepare('DELETE FROM memory_fact WHERE id = ?').run(id).changes > 0;
1100
1200
  }
1201
+ /**
1202
+ * Get user-profile facts (person entities) for injection into the system prompt.
1203
+ * Returns high-confidence facts about people (address, preferences, etc.)
1204
+ * limited to ~500 tokens worth of content.
1205
+ */
1206
+ function getUserProfileFacts(agentId, limit = 15) {
1207
+ const db = getDb();
1208
+ return db.prepare(`
1209
+ SELECT * FROM memory_fact
1210
+ WHERE agent_id = ? AND entity_type = 'person'
1211
+ ORDER BY confidence DESC, updated_at DESC
1212
+ LIMIT ?
1213
+ `).all(agentId, limit);
1214
+ }
1101
1215
  function upsertEntity(agentId, name, type) {
1102
1216
  const db = getDb();
1103
1217
  const existing = db.prepare('SELECT * FROM entity WHERE agent_id = ? AND name = ?').get(agentId, name);
@@ -1521,6 +1635,29 @@ function setPendingOrchestrationPolicy(entry) {
1521
1635
  function clearPendingOrchestrationPolicy(conversationId) {
1522
1636
  return getDb().prepare('DELETE FROM pending_orchestration_policy WHERE conversation_id = ?').run(conversationId).changes > 0;
1523
1637
  }
1638
+ function getPolicyDetectionEventForMessage(messageId) {
1639
+ return getDb().prepare('SELECT * FROM policy_detection_event WHERE message_id = ?').get(messageId);
1640
+ }
1641
+ function recordPolicyDetectionEvent(entry) {
1642
+ const db = getDb();
1643
+ const existing = getPolicyDetectionEventForMessage(entry.messageId);
1644
+ const id = existing?.id || newId();
1645
+ db.prepare(`
1646
+ INSERT INTO policy_detection_event (
1647
+ id, conversation_id, message_id, detection_source, detected_by_code,
1648
+ signal_json, status, scope, summary_text, policy_json, created_at
1649
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, datetime('now'))
1650
+ ON CONFLICT(message_id) DO UPDATE SET
1651
+ detection_source = excluded.detection_source,
1652
+ detected_by_code = excluded.detected_by_code,
1653
+ signal_json = excluded.signal_json,
1654
+ status = excluded.status,
1655
+ scope = excluded.scope,
1656
+ summary_text = excluded.summary_text,
1657
+ policy_json = excluded.policy_json
1658
+ `).run(id, entry.conversationId, entry.messageId, entry.source, entry.detectedByCode ? 1 : 0, entry.matchedSignals?.length ? JSON.stringify(entry.matchedSignals) : null, entry.status, entry.scope ?? null, entry.summaryText ?? null, entry.policy ? JSON.stringify(entry.policy) : null);
1659
+ return getPolicyDetectionEventForMessage(entry.messageId);
1660
+ }
1524
1661
  function getOrchestrationCheckpoint(conversationId) {
1525
1662
  const row = getDb().prepare('SELECT * FROM orchestration_checkpoint WHERE conversation_id = ?').get(conversationId);
1526
1663
  if (!row)
@@ -1620,6 +1757,19 @@ function parseJsonStatus(raw) {
1620
1757
  return {};
1621
1758
  }
1622
1759
  }
1760
+ function parseJsonNumberArray(raw) {
1761
+ try {
1762
+ const parsed = JSON.parse(raw);
1763
+ if (!Array.isArray(parsed))
1764
+ return [];
1765
+ return parsed
1766
+ .map((value) => Number(value))
1767
+ .filter((value) => Number.isFinite(value) && value > 0);
1768
+ }
1769
+ catch {
1770
+ return [];
1771
+ }
1772
+ }
1623
1773
  function normalizeParticipants(input) {
1624
1774
  const seen = new Set();
1625
1775
  const out = [];
@@ -1642,11 +1792,27 @@ function normalizeTodoRow(row, state) {
1642
1792
  conversation_id: row.conversation_id ? String(row.conversation_id) : null,
1643
1793
  origin: row.origin ? String(row.origin) : null,
1644
1794
  created_by_bot_id: row.created_by_bot_id ? String(row.created_by_bot_id) : null,
1795
+ owner_bot_id: row.owner_bot_id ? String(row.owner_bot_id) : null,
1796
+ owner_name: row.owner_name ? String(row.owner_name) : null,
1797
+ task_type: row.task_type ? String(row.task_type) : null,
1798
+ profile_name: row.profile_name ? String(row.profile_name) : null,
1799
+ next_worker_bot_id: row.next_worker_bot_id ? String(row.next_worker_bot_id) : null,
1800
+ next_worker_name: row.next_worker_name ? String(row.next_worker_name) : null,
1801
+ next_worker_role: row.next_worker_role ? String(row.next_worker_role) : null,
1645
1802
  title: String(row.title || ''),
1646
1803
  details: String(row.details || ''),
1804
+ task_prompt: row.task_prompt ? String(row.task_prompt) : null,
1805
+ handoff_prompt: row.handoff_prompt ? String(row.handoff_prompt) : null,
1806
+ output_summary: row.output_summary ? String(row.output_summary) : null,
1807
+ blocker_summary: row.blocker_summary ? String(row.blocker_summary) : null,
1808
+ blocker_checked: row.blocker_checked ? String(row.blocker_checked) : null,
1809
+ blocker_question: row.blocker_question ? String(row.blocker_question) : null,
1810
+ artifact_ids: parseJsonNumberArray(String(row.artifact_ids_json || '[]')),
1811
+ internal_only: Number(row.internal_only || 0),
1647
1812
  participants: parseJsonArray(String(row.participants || '[]')),
1648
1813
  status: parseJsonStatus(String(row.status || '{}')),
1649
1814
  created_order: Number(row.created_order || 0),
1815
+ position: Number(row.created_order || 0),
1650
1816
  state,
1651
1817
  created_at: String(row.created_at || ''),
1652
1818
  completed_at: row.completed_at ? String(row.completed_at) : null,
@@ -1658,6 +1824,47 @@ function normalizeTodoRow(row, state) {
1658
1824
  function isUserName(name) {
1659
1825
  return name.trim().toLowerCase() === 'user';
1660
1826
  }
1827
+ function roleClassToTaskType(roleClass) {
1828
+ const normalized = String(roleClass || '').trim().toLowerCase();
1829
+ if (!normalized)
1830
+ return null;
1831
+ if (normalized === 'code' || normalized === 'coding' || normalized === 'developer')
1832
+ return 'coding';
1833
+ if (normalized === 'qa' || normalized === 'review')
1834
+ return 'qa';
1835
+ if (normalized === 'research' || normalized === 'brainstorm' || normalized === 'planning' || normalized === 'plan')
1836
+ return 'research';
1837
+ if (normalized === 'verify' || normalized === 'verification')
1838
+ return 'verify';
1839
+ if (normalized === 'deploy' || normalized === 'deployment')
1840
+ return 'deploy';
1841
+ return normalized;
1842
+ }
1843
+ function resolveAgentByName(name) {
1844
+ const normalized = String(name || '').trim().toLowerCase();
1845
+ if (!normalized)
1846
+ return undefined;
1847
+ return listAgentProfiles().find((agent) => agent.name.trim().toLowerCase() === normalized);
1848
+ }
1849
+ function inferTaskTypeForAgent(agent, requested) {
1850
+ const explicit = String(requested || '').trim();
1851
+ if (explicit)
1852
+ return explicit;
1853
+ return roleClassToTaskType(agent?.role_class) || roleClassToTaskType(agent?.role_label) || null;
1854
+ }
1855
+ function normalizeArtifactRow(row) {
1856
+ return {
1857
+ id: Number(row.id),
1858
+ project_id: row.project_id ? String(row.project_id) : null,
1859
+ conversation_id: row.conversation_id ? String(row.conversation_id) : null,
1860
+ artifact_type: String(row.artifact_type || 'reference'),
1861
+ label: row.label ? String(row.label) : null,
1862
+ path_or_ref: String(row.path_or_ref || ''),
1863
+ metadata_json: row.metadata_json ? String(row.metadata_json) : null,
1864
+ created_by_bot_id: row.created_by_bot_id ? String(row.created_by_bot_id) : null,
1865
+ created_at: String(row.created_at || ''),
1866
+ };
1867
+ }
1661
1868
  function resolveAssignedParticipant(participants, requested) {
1662
1869
  const want = requested.trim().toLowerCase();
1663
1870
  const found = participants.find((p) => p.trim().toLowerCase() === want);
@@ -1691,16 +1898,142 @@ function moveActiveTaskToCompletedTx(db, taskId, actor, reason) {
1691
1898
  const completedAt = now();
1692
1899
  db.prepare(`
1693
1900
  INSERT INTO todo_completed (
1694
- id, project_id, conversation_id, title, details, participants, status, created_order,
1695
- success_criteria, origin, created_by_bot_id, created_at, completed_at, last_updated, version
1696
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
1697
- `).run(row.id, row.project_id ?? null, row.conversation_id ?? null, row.title, row.details, row.participants, row.status, row.created_order, row.success_criteria ?? null, row.origin ?? 'user', row.created_by_bot_id ?? null, row.created_at, completedAt, completedAt, row.version);
1901
+ id, project_id, conversation_id, origin, created_by_bot_id, owner_bot_id, owner_name,
1902
+ task_type, profile_name, next_worker_bot_id, next_worker_name, next_worker_role,
1903
+ title, details, task_prompt, handoff_prompt, output_summary, blocker_summary, blocker_checked, blocker_question, artifact_ids_json,
1904
+ success_criteria, internal_only, participants, status, created_order,
1905
+ created_at, completed_at, last_updated, version
1906
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
1907
+ `).run(row.id, row.project_id ?? null, row.conversation_id ?? null, row.origin ?? 'user', row.created_by_bot_id ?? null, row.owner_bot_id ?? null, row.owner_name ?? null, row.task_type ?? null, row.profile_name ?? 'Programming', row.next_worker_bot_id ?? null, row.next_worker_name ?? null, row.next_worker_role ?? null, row.title, row.details, row.task_prompt ?? null, row.handoff_prompt ?? null, row.output_summary ?? null, row.blocker_summary ?? null, row.blocker_checked ?? null, row.blocker_question ?? null, row.artifact_ids_json ?? '[]', row.success_criteria ?? null, Number(row.internal_only || 0), row.participants, row.status, row.created_order, row.created_at, completedAt, completedAt, row.version);
1698
1908
  db.prepare('DELETE FROM todo_active WHERE id = ?').run(taskId);
1699
- normalizeTodoOrderTx(db, row.project_id ? String(row.project_id) : null);
1700
1909
  logTodoAuditTx(db, taskId, 'completed', 'complete', actor, { reason });
1701
1910
  const completed = db.prepare('SELECT * FROM todo_completed WHERE id = ?').get(taskId);
1702
1911
  return normalizeTodoRow(completed, 'completed');
1703
1912
  }
1913
+ function determineInsertPositionTx(db, projectId, insertAfterTaskId) {
1914
+ if (insertAfterTaskId) {
1915
+ const row = db.prepare(`
1916
+ SELECT created_order, project_id
1917
+ FROM todo_active
1918
+ WHERE id = ?
1919
+ UNION ALL
1920
+ SELECT created_order, project_id
1921
+ FROM todo_completed
1922
+ WHERE id = ?
1923
+ LIMIT 1
1924
+ `).get(insertAfterTaskId, insertAfterTaskId);
1925
+ if (!row)
1926
+ throw new Error(`Task ${insertAfterTaskId} not found`);
1927
+ const normalizedProjectId = projectId ?? null;
1928
+ if ((row.project_id ?? null) !== normalizedProjectId) {
1929
+ throw new Error('Inserted task must stay within the same project scope');
1930
+ }
1931
+ const targetPosition = Number(row.created_order || 0) + 1;
1932
+ if (normalizedProjectId) {
1933
+ db.prepare('UPDATE todo_active SET created_order = created_order + 1, last_updated = datetime(\'now\'), version = version + 1 WHERE project_id = ? AND created_order >= ?')
1934
+ .run(normalizedProjectId, targetPosition);
1935
+ }
1936
+ else {
1937
+ db.prepare('UPDATE todo_active SET created_order = created_order + 1, last_updated = datetime(\'now\'), version = version + 1 WHERE project_id IS NULL AND created_order >= ?')
1938
+ .run(targetPosition);
1939
+ }
1940
+ return targetPosition;
1941
+ }
1942
+ const maxOrder = projectId
1943
+ ? db.prepare('SELECT COALESCE(MAX(created_order), 0) as n FROM todo_active WHERE project_id = ?').get(projectId)
1944
+ : db.prepare('SELECT COALESCE(MAX(created_order), 0) as n FROM todo_active WHERE project_id IS NULL').get();
1945
+ return Number(maxOrder.n) + 1;
1946
+ }
1947
+ function resolveOrchestratorName() {
1948
+ return getOrchestratorBot()?.name || null;
1949
+ }
1950
+ function resolveAllowedWorkerNamesForInsertion(currentTask) {
1951
+ const allowed = new Set();
1952
+ if (currentTask.next_worker_name)
1953
+ allowed.add(currentTask.next_worker_name.trim().toLowerCase());
1954
+ const orchestratorName = resolveOrchestratorName();
1955
+ if (orchestratorName)
1956
+ allowed.add(orchestratorName.trim().toLowerCase());
1957
+ const db = getDb();
1958
+ const previous = currentTask.project_id
1959
+ ? db.prepare(`
1960
+ SELECT owner_name, created_order, internal_only
1961
+ FROM (
1962
+ SELECT owner_name, created_order, internal_only FROM todo_active WHERE project_id = ?
1963
+ UNION ALL
1964
+ SELECT owner_name, created_order, internal_only FROM todo_completed WHERE project_id = ?
1965
+ )
1966
+ WHERE created_order < ? AND COALESCE(internal_only, 0) = 0 AND owner_name IS NOT NULL
1967
+ ORDER BY created_order DESC
1968
+ LIMIT 1
1969
+ `).get(currentTask.project_id, currentTask.project_id, currentTask.position)
1970
+ : db.prepare(`
1971
+ SELECT owner_name, created_order, internal_only
1972
+ FROM (
1973
+ SELECT owner_name, created_order, internal_only FROM todo_active WHERE project_id IS NULL
1974
+ UNION ALL
1975
+ SELECT owner_name, created_order, internal_only FROM todo_completed WHERE project_id IS NULL
1976
+ )
1977
+ WHERE created_order < ? AND COALESCE(internal_only, 0) = 0 AND owner_name IS NOT NULL
1978
+ ORDER BY created_order DESC
1979
+ LIMIT 1
1980
+ `).get(currentTask.position);
1981
+ if (previous?.owner_name)
1982
+ allowed.add(previous.owner_name.trim().toLowerCase());
1983
+ return Array.from(allowed);
1984
+ }
1985
+ function normalizeArtifactInput(ref) {
1986
+ if (typeof ref === 'string') {
1987
+ const trimmed = ref.trim();
1988
+ return {
1989
+ pathOrRef: trimmed,
1990
+ artifactType: /\.[a-z0-9]+$/i.test(trimmed) || /[\\/]/.test(trimmed) ? 'file' : 'reference',
1991
+ label: trimmed.split(/[\\/]/).filter(Boolean).pop() || trimmed,
1992
+ };
1993
+ }
1994
+ return {
1995
+ artifactType: String(ref.artifactType || 'reference'),
1996
+ label: ref.label ? String(ref.label) : undefined,
1997
+ pathOrRef: String(ref.pathOrRef || '').trim(),
1998
+ metadata: ref.metadata ?? null,
1999
+ };
2000
+ }
2001
+ function resolveArtifactRowsTx(db, refs, context) {
2002
+ const rows = [];
2003
+ for (const raw of refs) {
2004
+ const normalized = normalizeArtifactInput(raw);
2005
+ if (!normalized.pathOrRef)
2006
+ continue;
2007
+ const existing = db.prepare(`
2008
+ SELECT * FROM todo_artifact
2009
+ WHERE project_id IS ? AND conversation_id IS ? AND artifact_type = ? AND path_or_ref = ?
2010
+ ORDER BY id DESC
2011
+ LIMIT 1
2012
+ `).get(context.projectId ?? null, context.conversationId ?? null, normalized.artifactType || 'reference', normalized.pathOrRef);
2013
+ if (existing) {
2014
+ rows.push(normalizeArtifactRow(existing));
2015
+ continue;
2016
+ }
2017
+ const result = db.prepare(`
2018
+ INSERT INTO todo_artifact (
2019
+ project_id, conversation_id, artifact_type, label, path_or_ref, metadata_json, created_by_bot_id, created_at
2020
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
2021
+ `).run(context.projectId ?? null, context.conversationId ?? null, normalized.artifactType || 'reference', normalized.label ?? null, normalized.pathOrRef, normalized.metadata ? JSON.stringify(normalized.metadata) : null, context.createdByBotId ?? null, now());
2022
+ const created = db.prepare('SELECT * FROM todo_artifact WHERE id = ?').get(Number(result.lastInsertRowid));
2023
+ rows.push(normalizeArtifactRow(created));
2024
+ }
2025
+ return rows;
2026
+ }
2027
+ function loadArtifactRowsByIdsTx(db, artifactIds) {
2028
+ const normalized = artifactIds
2029
+ .map((value) => Number(value))
2030
+ .filter((value) => Number.isFinite(value) && value > 0);
2031
+ if (normalized.length === 0)
2032
+ return [];
2033
+ const placeholders = normalized.map(() => '?').join(', ');
2034
+ const rows = db.prepare(`SELECT * FROM todo_artifact WHERE id IN (${placeholders}) ORDER BY id ASC`).all(...normalized);
2035
+ return rows.map((row) => normalizeArtifactRow(row));
2036
+ }
1704
2037
  function getTodoStatusMarker(projectId) {
1705
2038
  const row = projectId
1706
2039
  ? getDb().prepare('SELECT COUNT(*) as n FROM todo_active WHERE project_id = ?').get(projectId)
@@ -1735,13 +2068,24 @@ function listAllTodoTasks(opts) {
1735
2068
  };
1736
2069
  }
1737
2070
  function addTodoTask(params) {
1738
- const db = getDb();
1739
2071
  const title = String(params.title || '').trim();
1740
2072
  const details = String(params.details || '').trim();
1741
2073
  const participants = normalizeParticipants(params.participants || []);
1742
2074
  const successCriteria = params.successCriteria?.trim() || null;
1743
2075
  const origin = params.actor.actorType;
1744
2076
  const createdByBotId = params.actor.actorId?.trim() || null;
2077
+ const ownerBotId = params.ownerBotId?.trim() || null;
2078
+ const ownerName = params.ownerName?.trim() || null;
2079
+ const taskType = params.taskType?.trim() || null;
2080
+ const profileName = params.profileName?.trim() || 'Programming';
2081
+ const nextWorkerBotId = params.nextWorkerBotId?.trim() || null;
2082
+ const nextWorkerName = params.nextWorkerName?.trim() || null;
2083
+ const nextWorkerRole = params.nextWorkerRole?.trim() || null;
2084
+ const taskPrompt = params.taskPrompt?.trim() || null;
2085
+ const handoffPrompt = params.handoffPrompt?.trim() || null;
2086
+ const outputSummary = params.outputSummary?.trim() || null;
2087
+ const artifactIdsJson = JSON.stringify((params.artifactIds || []).filter((id) => Number.isFinite(id) && id > 0));
2088
+ const internalOnly = params.internalOnly ? 1 : 0;
1745
2089
  if (!title)
1746
2090
  throw new Error('Title is required');
1747
2091
  if (countWords(title) > TODO_TITLE_WORD_LIMIT)
@@ -1756,21 +2100,61 @@ function addTodoTask(params) {
1756
2100
  if (!successCriteria)
1757
2101
  throw new Error('Orchestrator TODOs must include success criteria');
1758
2102
  }
1759
- const tx = db.transaction(() => {
1760
- const maxOrder = params.projectId
1761
- ? db.prepare('SELECT COALESCE(MAX(created_order), 0) as n FROM todo_active WHERE project_id = ?').get(params.projectId)
1762
- : db.prepare('SELECT COALESCE(MAX(created_order), 0) as n FROM todo_active WHERE project_id IS NULL').get();
1763
- const createdOrder = Number(maxOrder.n) + 1;
2103
+ const db = getDb();
2104
+ const tx = db.transaction(() => insertTodoTaskTx(db, {
2105
+ ...params,
2106
+ title,
2107
+ details,
2108
+ participants,
2109
+ successCriteria: successCriteria || undefined,
2110
+ ownerBotId,
2111
+ ownerName,
2112
+ taskType,
2113
+ profileName,
2114
+ nextWorkerBotId,
2115
+ nextWorkerName,
2116
+ nextWorkerRole,
2117
+ taskPrompt,
2118
+ handoffPrompt,
2119
+ outputSummary,
2120
+ artifactIds: JSON.parse(artifactIdsJson),
2121
+ internalOnly: Boolean(internalOnly),
2122
+ }));
2123
+ return tx();
2124
+ }
2125
+ function insertTodoTaskTx(db, params) {
2126
+ const title = String(params.title || '').trim();
2127
+ const details = String(params.details || '').trim();
2128
+ const participants = normalizeParticipants(params.participants || []);
2129
+ const successCriteria = params.successCriteria?.trim() || null;
2130
+ const origin = params.actor.actorType;
2131
+ const createdByBotId = params.actor.actorId?.trim() || null;
2132
+ const ownerBotId = params.ownerBotId?.trim() || null;
2133
+ const ownerName = params.ownerName?.trim() || null;
2134
+ const taskType = params.taskType?.trim() || null;
2135
+ const profileName = params.profileName?.trim() || 'Programming';
2136
+ const nextWorkerBotId = params.nextWorkerBotId?.trim() || null;
2137
+ const nextWorkerName = params.nextWorkerName?.trim() || null;
2138
+ const nextWorkerRole = params.nextWorkerRole?.trim() || null;
2139
+ const taskPrompt = params.taskPrompt?.trim() || null;
2140
+ const handoffPrompt = params.handoffPrompt?.trim() || null;
2141
+ const outputSummary = params.outputSummary?.trim() || null;
2142
+ const artifactIdsJson = JSON.stringify((params.artifactIds || []).filter((id) => Number.isFinite(id) && id > 0));
2143
+ const internalOnly = params.internalOnly ? 1 : 0;
2144
+ {
2145
+ const createdOrder = determineInsertPositionTx(db, params.projectId, params.insertAfterTaskId);
1764
2146
  const status = {};
1765
2147
  for (const p of participants)
1766
2148
  status[p] = false;
1767
2149
  const result = db.prepare(`
1768
2150
  INSERT INTO todo_active (
1769
- project_id, conversation_id, origin, created_by_bot_id, title, details,
1770
- participants, status, created_order, success_criteria, created_at, last_updated, version
2151
+ project_id, conversation_id, origin, created_by_bot_id, owner_bot_id, owner_name,
2152
+ task_type, profile_name, next_worker_bot_id, next_worker_name, next_worker_role,
2153
+ title, details, task_prompt, handoff_prompt, output_summary, artifact_ids_json,
2154
+ success_criteria, internal_only, participants, status, created_order, created_at, last_updated, version
1771
2155
  )
1772
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1)
1773
- `).run(params.projectId ?? null, params.conversationId ?? null, origin, createdByBotId, title, details, JSON.stringify(participants), JSON.stringify(status), createdOrder, successCriteria, now(), now());
2156
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1)
2157
+ `).run(params.projectId ?? null, params.conversationId ?? null, origin, createdByBotId, ownerBotId, ownerName, taskType, profileName, nextWorkerBotId, nextWorkerName, nextWorkerRole, title, details, taskPrompt, handoffPrompt, outputSummary, artifactIdsJson, successCriteria, internalOnly, JSON.stringify(participants), JSON.stringify(status), createdOrder, now(), now());
1774
2158
  const id = Number(result.lastInsertRowid);
1775
2159
  logTodoAuditTx(db, id, 'active', 'add', params.actor, {
1776
2160
  title,
@@ -1779,11 +2163,19 @@ function addTodoTask(params) {
1779
2163
  conversationId: params.conversationId ?? null,
1780
2164
  origin,
1781
2165
  createdByBotId,
2166
+ ownerBotId,
2167
+ ownerName,
2168
+ taskType,
2169
+ profileName,
2170
+ nextWorkerBotId,
2171
+ nextWorkerName,
2172
+ nextWorkerRole,
2173
+ insertAfterTaskId: params.insertAfterTaskId ?? null,
2174
+ internalOnly,
1782
2175
  });
1783
2176
  const row = db.prepare('SELECT * FROM todo_active WHERE id = ?').get(id);
1784
2177
  return normalizeTodoRow(row, 'active');
1785
- });
1786
- return tx();
2178
+ }
1787
2179
  }
1788
2180
  function editTodoTask(taskId, params) {
1789
2181
  const db = getDb();
@@ -1919,6 +2311,289 @@ function completeTodoTaskByUser(taskId, params) {
1919
2311
  });
1920
2312
  return tx();
1921
2313
  }
2314
+ function listTodoArtifactsForTask(taskId, state = 'active') {
2315
+ const task = getTodoTask(taskId, state);
2316
+ if (!task || task.artifact_ids.length === 0)
2317
+ return [];
2318
+ const placeholders = task.artifact_ids.map(() => '?').join(', ');
2319
+ const rows = getDb().prepare(`SELECT * FROM todo_artifact WHERE id IN (${placeholders}) ORDER BY id ASC`).all(...task.artifact_ids);
2320
+ return rows.map((row) => normalizeArtifactRow(row));
2321
+ }
2322
+ function completeTodoTaskByWorker(taskId, params) {
2323
+ if (params.actor.actorType !== 'llm' && params.actor.actorType !== 'orchestrator') {
2324
+ throw new Error('Only worker LLMs or orchestrator can complete workflow TODOs');
2325
+ }
2326
+ const db = getDb();
2327
+ const tx = db.transaction(() => {
2328
+ const row = db.prepare('SELECT * FROM todo_active WHERE id = ?').get(taskId);
2329
+ if (!row)
2330
+ throw new Error(`Task ${taskId} not found`);
2331
+ if (params.expectedVersion !== undefined && Number(row.version) !== Number(params.expectedVersion)) {
2332
+ throw new Error('Conflict: task version changed');
2333
+ }
2334
+ if (params.expectedLastUpdated !== undefined && String(row.last_updated) !== String(params.expectedLastUpdated)) {
2335
+ throw new Error('Conflict: task was updated by another actor');
2336
+ }
2337
+ const currentTask = normalizeTodoRow(row, 'active');
2338
+ const actorName = String(params.actor.actorId || '').trim();
2339
+ const actorBot = resolveAgentByName(actorName);
2340
+ const isAssignedWorker = Boolean(actorName &&
2341
+ currentTask.owner_name &&
2342
+ actorName.toLowerCase() === currentTask.owner_name.toLowerCase());
2343
+ const isOrchestratorActor = params.actor.actorType === 'orchestrator';
2344
+ if (!isAssignedWorker && !isOrchestratorActor) {
2345
+ throw new Error(`Actor "${actorName || 'unknown'}" is not the owner of task ${taskId}`);
2346
+ }
2347
+ const existingArtifactRows = loadArtifactRowsByIdsTx(db, currentTask.artifact_ids);
2348
+ const newArtifactRows = resolveArtifactRowsTx(db, params.artifactRefs || [], {
2349
+ projectId: currentTask.project_id,
2350
+ conversationId: currentTask.conversation_id,
2351
+ createdByBotId: actorBot?.id || currentTask.owner_bot_id || currentTask.created_by_bot_id,
2352
+ });
2353
+ const artifactRowsById = new Map();
2354
+ for (const item of [...existingArtifactRows, ...newArtifactRows])
2355
+ artifactRowsById.set(item.id, item);
2356
+ const artifactRows = Array.from(artifactRowsById.values()).sort((a, b) => a.id - b.id);
2357
+ const artifactIds = artifactRows.map((item) => item.id);
2358
+ const outputSummary = params.outputSummary?.trim() || currentTask.output_summary || null;
2359
+ const handoffPrompt = params.handoffPrompt?.trim() || currentTask.handoff_prompt || null;
2360
+ db.prepare(`
2361
+ UPDATE todo_active
2362
+ SET output_summary = ?,
2363
+ handoff_prompt = ?,
2364
+ artifact_ids_json = ?,
2365
+ last_updated = ?,
2366
+ version = version + 1
2367
+ WHERE id = ?
2368
+ `).run(outputSummary, handoffPrompt, JSON.stringify(artifactIds), now(), taskId);
2369
+ logTodoAuditTx(db, taskId, 'active', 'worker_complete', params.actor, {
2370
+ outputSummary,
2371
+ artifactIds,
2372
+ inserted: Boolean(params.insertTask),
2373
+ });
2374
+ const completedTask = moveActiveTaskToCompletedTx(db, taskId, params.actor, 'all_llms_complete');
2375
+ let insertedTask;
2376
+ let handedOffTask;
2377
+ let returnedToOrchestrator = false;
2378
+ if (params.insertTask) {
2379
+ const allowedTargets = resolveAllowedWorkerNamesForInsertion(completedTask);
2380
+ const requestedWorkerName = String(params.insertTask.nextWorker || '').trim();
2381
+ if (!requestedWorkerName) {
2382
+ throw new Error('Inserted worker task requires a next worker');
2383
+ }
2384
+ if (!allowedTargets.includes(requestedWorkerName.toLowerCase())) {
2385
+ throw new Error(`Next worker "${requestedWorkerName}" is not allowed from this task`);
2386
+ }
2387
+ const targetWorker = resolveAgentByName(requestedWorkerName);
2388
+ const orchestratorName = resolveOrchestratorName();
2389
+ if (!targetWorker && (!orchestratorName || requestedWorkerName.toLowerCase() !== orchestratorName.toLowerCase())) {
2390
+ throw new Error(`Unknown next worker "${requestedWorkerName}"`);
2391
+ }
2392
+ if (orchestratorName && requestedWorkerName.toLowerCase() === orchestratorName.toLowerCase()) {
2393
+ returnedToOrchestrator = true;
2394
+ logTodoAuditTx(db, taskId, 'completed', 'return_to_orchestrator', params.actor, {
2395
+ handoffPrompt: params.insertTask.prompt,
2396
+ title: params.insertTask.title,
2397
+ });
2398
+ }
2399
+ else {
2400
+ const participants = targetWorker?.name ? [targetWorker.name] : [requestedWorkerName];
2401
+ insertedTask = insertTodoTaskTx(db, {
2402
+ projectId: completedTask.project_id,
2403
+ conversationId: completedTask.conversation_id,
2404
+ title: params.insertTask.title,
2405
+ details: params.insertTask.details || params.insertTask.prompt,
2406
+ participants,
2407
+ successCriteria: params.insertTask.successCriteria || undefined,
2408
+ ownerBotId: targetWorker?.id || null,
2409
+ ownerName: targetWorker?.name || requestedWorkerName,
2410
+ taskType: inferTaskTypeForAgent(targetWorker, params.insertTask.taskType),
2411
+ profileName: completedTask.profile_name || 'Programming',
2412
+ nextWorkerBotId: currentTask.owner_bot_id,
2413
+ nextWorkerName: currentTask.owner_name,
2414
+ nextWorkerRole: actorBot?.role_label || actorBot?.role_class || null,
2415
+ taskPrompt: params.insertTask.prompt,
2416
+ artifactIds,
2417
+ insertAfterTaskId: completedTask.id,
2418
+ actor: params.actor,
2419
+ });
2420
+ logTodoAuditTx(db, insertedTask.id, 'active', 'worker_insert', params.actor, {
2421
+ insertedAfterTaskId: completedTask.id,
2422
+ requestedWorkerName,
2423
+ });
2424
+ }
2425
+ }
2426
+ else {
2427
+ const nextWorkerName = completedTask.next_worker_name?.trim();
2428
+ if (nextWorkerName) {
2429
+ const nextRow = completedTask.next_worker_bot_id
2430
+ ? db.prepare(`
2431
+ SELECT * FROM todo_active
2432
+ WHERE conversation_id = ?
2433
+ AND id <> ?
2434
+ AND owner_bot_id = ?
2435
+ AND created_order > ?
2436
+ AND COALESCE(internal_only, 0) = 0
2437
+ ORDER BY created_order ASC, id ASC
2438
+ LIMIT 1
2439
+ `).get(completedTask.conversation_id, completedTask.id, completedTask.next_worker_bot_id, completedTask.created_order)
2440
+ : db.prepare(`
2441
+ SELECT * FROM todo_active
2442
+ WHERE conversation_id = ?
2443
+ AND id <> ?
2444
+ AND LOWER(COALESCE(owner_name, '')) = LOWER(?)
2445
+ AND created_order > ?
2446
+ AND COALESCE(internal_only, 0) = 0
2447
+ ORDER BY created_order ASC, id ASC
2448
+ LIMIT 1
2449
+ `).get(completedTask.conversation_id, completedTask.id, nextWorkerName, completedTask.created_order);
2450
+ if (nextRow) {
2451
+ const nextTask = normalizeTodoRow(nextRow, 'active');
2452
+ const handoffText = handoffPrompt?.trim()
2453
+ || outputSummary?.trim()
2454
+ || `Continue from the completed work summary: ${completedTask.title}`;
2455
+ const existingPrompt = nextTask.task_prompt?.trim() || nextTask.details || nextTask.title;
2456
+ const mergedArtifactIds = Array.from(new Set([...(nextTask.artifact_ids || []), ...artifactIds]));
2457
+ const nextPrompt = [
2458
+ 'PREVIOUS WORKER HANDOFF',
2459
+ handoffText,
2460
+ '',
2461
+ 'ORIGINAL ASSIGNED TODO',
2462
+ existingPrompt,
2463
+ ].join('\n');
2464
+ db.prepare(`
2465
+ UPDATE todo_active
2466
+ SET task_prompt = ?,
2467
+ artifact_ids_json = ?,
2468
+ last_updated = ?,
2469
+ version = version + 1
2470
+ WHERE id = ?
2471
+ `).run(nextPrompt, JSON.stringify(mergedArtifactIds), now(), nextTask.id);
2472
+ handedOffTask = normalizeTodoRow(db.prepare('SELECT * FROM todo_active WHERE id = ?').get(nextTask.id), 'active');
2473
+ logTodoAuditTx(db, nextTask.id, 'active', 'edit', params.actor, {
2474
+ previousTaskId: completedTask.id,
2475
+ receivedHandoff: true,
2476
+ });
2477
+ }
2478
+ else {
2479
+ returnedToOrchestrator = true;
2480
+ logTodoAuditTx(db, taskId, 'completed', 'return_to_orchestrator', params.actor, {
2481
+ handoffPrompt,
2482
+ missingQueuedNextWorker: nextWorkerName,
2483
+ });
2484
+ }
2485
+ }
2486
+ else {
2487
+ returnedToOrchestrator = true;
2488
+ logTodoAuditTx(db, taskId, 'completed', 'return_to_orchestrator', params.actor, {
2489
+ handoffPrompt,
2490
+ });
2491
+ }
2492
+ }
2493
+ return {
2494
+ completedTask,
2495
+ insertedTask,
2496
+ handedOffTask,
2497
+ artifacts: artifactRows,
2498
+ returnedToOrchestrator,
2499
+ };
2500
+ });
2501
+ return tx();
2502
+ }
2503
+ function blockTodoTaskByWorker(taskId, params) {
2504
+ if (params.actor.actorType !== 'llm' && params.actor.actorType !== 'orchestrator') {
2505
+ throw new Error('Only worker LLMs or orchestrator can block workflow TODOs');
2506
+ }
2507
+ const blockerSummary = String(params.blockerSummary || '').trim();
2508
+ const checkedContext = String(params.checkedContext || '').trim();
2509
+ const userQuestion = String(params.userQuestion || '').trim();
2510
+ if (!blockerSummary)
2511
+ throw new Error('Blocked worker task requires blockerSummary');
2512
+ if (!checkedContext)
2513
+ throw new Error('Blocked worker task requires checkedContext');
2514
+ if (!userQuestion)
2515
+ throw new Error('Blocked worker task requires userQuestion');
2516
+ const db = getDb();
2517
+ const tx = db.transaction(() => {
2518
+ const row = db.prepare('SELECT * FROM todo_active WHERE id = ?').get(taskId);
2519
+ if (!row)
2520
+ throw new Error(`Task ${taskId} not found`);
2521
+ if (params.expectedVersion !== undefined && Number(row.version) !== Number(params.expectedVersion)) {
2522
+ throw new Error('Conflict: task version changed');
2523
+ }
2524
+ if (params.expectedLastUpdated !== undefined && String(row.last_updated) !== String(params.expectedLastUpdated)) {
2525
+ throw new Error('Conflict: task was updated by another actor');
2526
+ }
2527
+ const currentTask = normalizeTodoRow(row, 'active');
2528
+ const actorName = String(params.actor.actorId || '').trim();
2529
+ const actorBot = resolveAgentByName(actorName);
2530
+ const isAssignedWorker = Boolean(actorName &&
2531
+ currentTask.owner_name &&
2532
+ actorName.toLowerCase() === currentTask.owner_name.toLowerCase());
2533
+ const isOrchestratorActor = params.actor.actorType === 'orchestrator';
2534
+ if (!isAssignedWorker && !isOrchestratorActor) {
2535
+ throw new Error(`Actor "${actorName || 'unknown'}" is not the owner of task ${taskId}`);
2536
+ }
2537
+ const existingArtifactRows = loadArtifactRowsByIdsTx(db, currentTask.artifact_ids);
2538
+ const newArtifactRows = resolveArtifactRowsTx(db, params.artifactRefs || [], {
2539
+ projectId: currentTask.project_id,
2540
+ conversationId: currentTask.conversation_id,
2541
+ createdByBotId: actorBot?.id || currentTask.owner_bot_id || currentTask.created_by_bot_id,
2542
+ });
2543
+ const artifactRowsById = new Map();
2544
+ for (const item of [...existingArtifactRows, ...newArtifactRows])
2545
+ artifactRowsById.set(item.id, item);
2546
+ const artifactRows = Array.from(artifactRowsById.values()).sort((a, b) => a.id - b.id);
2547
+ const artifactIds = artifactRows.map((item) => item.id);
2548
+ db.prepare(`
2549
+ UPDATE todo_active
2550
+ SET blocker_summary = ?,
2551
+ blocker_checked = ?,
2552
+ blocker_question = ?,
2553
+ artifact_ids_json = ?,
2554
+ last_updated = ?,
2555
+ version = version + 1
2556
+ WHERE id = ?
2557
+ `).run(blockerSummary, checkedContext, userQuestion, JSON.stringify(artifactIds), now(), taskId);
2558
+ logTodoAuditTx(db, taskId, 'active', 'worker_blocked', params.actor, {
2559
+ blockerSummary,
2560
+ checkedContext,
2561
+ userQuestion,
2562
+ artifactIds,
2563
+ });
2564
+ logTodoAuditTx(db, taskId, 'active', 'return_to_orchestrator', params.actor, {
2565
+ blocked: true,
2566
+ blockerSummary,
2567
+ userQuestion,
2568
+ });
2569
+ const blockedTask = normalizeTodoRow(db.prepare('SELECT * FROM todo_active WHERE id = ?').get(taskId), 'active');
2570
+ const orchestratorBot = getOrchestratorBot();
2571
+ const ownerRole = String(actorBot?.role_label || actorBot?.role_class || currentTask.task_type || 'general').trim() || 'general';
2572
+ const effectivePolicy = getEffectiveOrchestrationPolicy(currentTask.project_id || undefined);
2573
+ const orchestratorPrompt = (0, orchestrator_blocked_prompt_1.buildBlockedWorkerOrchestratorPrompt)({
2574
+ orchestratorName: orchestratorBot?.name || 'Orchestrator',
2575
+ workerName: blockedTask.owner_name || actorName || 'Worker',
2576
+ workerRole: ownerRole,
2577
+ taskTitle: blockedTask.title,
2578
+ taskPrompt: blockedTask.task_prompt || blockedTask.details || '(not set)',
2579
+ projectId: blockedTask.project_id,
2580
+ projectName: blockedTask.project_id ? getProject(blockedTask.project_id)?.name || null : null,
2581
+ workspacePath: blockedTask.project_id ? getProject(blockedTask.project_id)?.folder || null : null,
2582
+ effectivePolicy,
2583
+ artifactRefs: artifactRows.map((item) => item.path_or_ref),
2584
+ blockerSummary,
2585
+ checkedContext,
2586
+ userQuestion,
2587
+ });
2588
+ return {
2589
+ blockedTask,
2590
+ artifacts: artifactRows,
2591
+ returnedToOrchestrator: true,
2592
+ orchestratorPrompt,
2593
+ };
2594
+ });
2595
+ return tx();
2596
+ }
1922
2597
  function deleteTodoTask(taskId, params) {
1923
2598
  if (params.actor.actorType !== 'user' && params.actor.actorType !== 'orchestrator')
1924
2599
  throw new Error('Only user or orchestrator can delete tasks');
@@ -1999,6 +2674,16 @@ function getLatestActiveTodoForConversation(conversationId) {
1999
2674
  return undefined;
2000
2675
  return normalizeTodoRow(row, 'active');
2001
2676
  }
2677
+ function getNextActiveTodoForConversation(conversationId) {
2678
+ const row = getDb().prepare('SELECT * FROM todo_active WHERE conversation_id = ? AND COALESCE(internal_only, 0) = 0 ORDER BY created_order ASC, id ASC LIMIT 1').get(conversationId);
2679
+ if (!row)
2680
+ return undefined;
2681
+ return normalizeTodoRow(row, 'active');
2682
+ }
2683
+ function listCompletedTodoTasksForConversation(conversationId) {
2684
+ const rows = getDb().prepare('SELECT * FROM todo_completed WHERE conversation_id = ? AND COALESCE(internal_only, 0) = 0 ORDER BY created_order ASC, id ASC').all(conversationId);
2685
+ return rows.map((row) => normalizeTodoRow(row, 'completed'));
2686
+ }
2002
2687
  exports.UNASSIGNED_PROJECT_NAME = 'unassigned';
2003
2688
  function createProject(p) {
2004
2689
  const db = getDb();
@@ -2008,6 +2693,14 @@ function createProject(p) {
2008
2693
  .run(id, p.name, p.description ?? null, p.folder ?? null, p.operatingPrompt ?? null, ts, ts);
2009
2694
  return db.prepare('SELECT * FROM project WHERE id = ?').get(id);
2010
2695
  }
2696
+ function upsertProject(project) {
2697
+ const db = getDb();
2698
+ db.prepare(`
2699
+ INSERT INTO project (id, name, folder, description, created_at, updated_at, archived, sort_order)
2700
+ VALUES (?, ?, ?, ?, datetime('now'), datetime('now'), 0, 0)
2701
+ ON CONFLICT(id) DO UPDATE SET name=excluded.name, folder=excluded.folder, description=excluded.description, updated_at=datetime('now')
2702
+ `).run(project.id, project.name, project.folder || null, project.description || null);
2703
+ }
2011
2704
  function getProject(id) {
2012
2705
  const db = getDb();
2013
2706
  const row = db.prepare('SELECT * FROM project WHERE id = ?').get(id);
@@ -2088,7 +2781,7 @@ function getProjectOverview(projectId) {
2088
2781
  label: connection.label || null,
2089
2782
  status: connection.status,
2090
2783
  defaultModel: connection.default_model || null,
2091
- hasSecret: Boolean(connection.api_key_enc || connection.oauth_token),
2784
+ hasSecret: connection.access_mode === 'cli' || Boolean(connection.api_key_enc),
2092
2785
  }));
2093
2786
  const userPolicy = getUserOrchestrationPolicy() || null;
2094
2787
  const projectPolicyOverride = getProjectOrchestrationPolicyOverride(projectId) || null;