funolio-agent 1.0.7 → 1.0.47
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/approval.d.ts +1 -0
- package/dist/approval.d.ts.map +1 -1
- package/dist/approval.js +12 -0
- package/dist/approval.js.map +1 -1
- package/dist/auth/auto-detect.d.ts +11 -3
- package/dist/auth/auto-detect.d.ts.map +1 -1
- package/dist/auth/auto-detect.js +136 -168
- package/dist/auth/auto-detect.js.map +1 -1
- package/dist/auth/subscription-runtime.js +1 -1
- package/dist/auth/subscription-runtime.js.map +1 -1
- package/dist/backfill.d.ts.map +1 -1
- package/dist/backfill.js +31 -28
- package/dist/backfill.js.map +1 -1
- package/dist/bot-manager.d.ts +16 -4
- package/dist/bot-manager.d.ts.map +1 -1
- package/dist/bot-manager.js +280 -73
- package/dist/bot-manager.js.map +1 -1
- package/dist/clerk-model.d.ts +10 -6
- package/dist/clerk-model.d.ts.map +1 -1
- package/dist/clerk-model.js +39 -16
- package/dist/clerk-model.js.map +1 -1
- package/dist/cli.js +7 -2
- package/dist/cli.js.map +1 -1
- package/dist/commands/import-history.js +5 -1
- package/dist/commands/import-history.js.map +1 -1
- package/dist/commands/start.d.ts.map +1 -1
- package/dist/commands/start.js +57 -225
- package/dist/commands/start.js.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +13 -2
- package/dist/config.js.map +1 -1
- package/dist/context-window.js +1 -1
- package/dist/context-window.js.map +1 -1
- package/dist/import-parser-core.d.ts.map +1 -1
- package/dist/import-parser-core.js +74 -8
- package/dist/import-parser-core.js.map +1 -1
- package/dist/local-data.d.ts +59 -6
- package/dist/local-data.d.ts.map +1 -1
- package/dist/local-data.js +544 -62
- package/dist/local-data.js.map +1 -1
- package/dist/local-db.d.ts.map +1 -1
- package/dist/local-db.js +21 -1
- package/dist/local-db.js.map +1 -1
- package/dist/local-funnel.d.ts +0 -7
- package/dist/local-funnel.d.ts.map +1 -1
- package/dist/local-funnel.js +19 -28
- package/dist/local-funnel.js.map +1 -1
- package/dist/local-import-worker.d.ts.map +1 -1
- package/dist/local-import-worker.js +49 -4
- package/dist/local-import-worker.js.map +1 -1
- package/dist/local-memory-search.d.ts.map +1 -1
- package/dist/local-memory-search.js +6 -3
- package/dist/local-memory-search.js.map +1 -1
- package/dist/local-server.d.ts +37 -0
- package/dist/local-server.d.ts.map +1 -1
- package/dist/local-server.js +731 -227
- package/dist/local-server.js.map +1 -1
- package/dist/mcp/local-memory-server.d.ts +1 -1
- package/dist/mcp/local-memory-server.d.ts.map +1 -1
- package/dist/mcp/local-memory-server.js +23 -11
- package/dist/mcp/local-memory-server.js.map +1 -1
- package/dist/mcp/manager.d.ts.map +1 -1
- package/dist/mcp/manager.js +86 -18
- package/dist/mcp/manager.js.map +1 -1
- package/dist/message-loop.d.ts +5 -10
- package/dist/message-loop.d.ts.map +1 -1
- package/dist/message-loop.js +393 -289
- package/dist/message-loop.js.map +1 -1
- package/dist/mqtt-client.d.ts +4 -1
- package/dist/mqtt-client.d.ts.map +1 -1
- package/dist/mqtt-client.js.map +1 -1
- package/dist/orchestration/orchestrator-blocked-prompt.d.ts +2 -1
- package/dist/orchestration/orchestrator-blocked-prompt.d.ts.map +1 -1
- package/dist/orchestration/orchestrator-blocked-prompt.js +11 -0
- package/dist/orchestration/orchestrator-blocked-prompt.js.map +1 -1
- package/dist/orchestration/orchestrator-final-response-prompt.d.ts +4 -1
- package/dist/orchestration/orchestrator-final-response-prompt.d.ts.map +1 -1
- package/dist/orchestration/orchestrator-final-response-prompt.js +8 -6
- package/dist/orchestration/orchestrator-final-response-prompt.js.map +1 -1
- package/dist/orchestration/worker-operating-prompt.d.ts +2 -0
- package/dist/orchestration/worker-operating-prompt.d.ts.map +1 -1
- package/dist/orchestration/worker-operating-prompt.js +39 -0
- package/dist/orchestration/worker-operating-prompt.js.map +1 -1
- package/dist/orchestrator.d.ts +13 -0
- package/dist/orchestrator.d.ts.map +1 -1
- package/dist/orchestrator.js +235 -133
- package/dist/orchestrator.js.map +1 -1
- package/dist/providers/anthropic.d.ts +0 -5
- package/dist/providers/anthropic.d.ts.map +1 -1
- package/dist/providers/anthropic.js +29 -75
- package/dist/providers/anthropic.js.map +1 -1
- package/dist/providers/claude-cli.d.ts.map +1 -1
- package/dist/providers/claude-cli.js +16 -140
- package/dist/providers/claude-cli.js.map +1 -1
- package/dist/providers/codex-cli.d.ts.map +1 -1
- package/dist/providers/codex-cli.js +78 -59
- package/dist/providers/codex-cli.js.map +1 -1
- package/dist/providers/google.d.ts.map +1 -1
- package/dist/providers/google.js +4 -2
- package/dist/providers/google.js.map +1 -1
- package/dist/providers/index.d.ts +2 -0
- package/dist/providers/index.d.ts.map +1 -1
- package/dist/providers/index.js.map +1 -1
- package/dist/providers/openai.d.ts.map +1 -1
- package/dist/providers/openai.js +27 -2
- package/dist/providers/openai.js.map +1 -1
- package/dist/storage-mode.d.ts +5 -0
- package/dist/storage-mode.d.ts.map +1 -0
- package/dist/storage-mode.js +21 -0
- package/dist/storage-mode.js.map +1 -0
- package/dist/summarization-pipeline.d.ts +9 -0
- package/dist/summarization-pipeline.d.ts.map +1 -1
- package/dist/summarization-pipeline.js +61 -17
- package/dist/summarization-pipeline.js.map +1 -1
- package/dist/tools/analyze-image.js +2 -2
- package/dist/tools/analyze-image.js.map +1 -1
- package/dist/tools/edit-file.js +3 -3
- package/dist/tools/edit-file.js.map +1 -1
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +36 -0
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/list-directory.js +7 -4
- package/dist/tools/list-directory.js.map +1 -1
- package/dist/tools/read-file.js +3 -3
- package/dist/tools/read-file.js.map +1 -1
- package/dist/tools/run-command.js +3 -3
- package/dist/tools/run-command.js.map +1 -1
- package/dist/tools/sandbox.d.ts +10 -5
- package/dist/tools/sandbox.d.ts.map +1 -1
- package/dist/tools/sandbox.js +41 -13
- package/dist/tools/sandbox.js.map +1 -1
- package/dist/tools/search-codebase.js +2 -2
- package/dist/tools/search-codebase.js.map +1 -1
- package/dist/tools/search-local-memory.d.ts.map +1 -1
- package/dist/tools/search-local-memory.js +19 -8
- package/dist/tools/search-local-memory.js.map +1 -1
- package/dist/tools/spawn-subagent.js.map +1 -1
- package/dist/tools/write-file.js +3 -3
- package/dist/tools/write-file.js.map +1 -1
- package/dist/types.d.ts +2 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/verification/index.js +2 -2
- package/dist/verification/index.js.map +1 -1
- package/dist/wizard-state.d.ts.map +1 -1
- package/dist/wizard-state.js +16 -2
- package/dist/wizard-state.js.map +1 -1
- package/dist/wizard-support.d.ts +2 -2
- package/dist/wizard-support.d.ts.map +1 -1
- package/dist/wizard-support.js +80 -93
- package/dist/wizard-support.js.map +1 -1
- package/dist/workflow-engine.d.ts +3 -1
- package/dist/workflow-engine.d.ts.map +1 -1
- package/dist/workflow-engine.js +131 -12
- package/dist/workflow-engine.js.map +1 -1
- package/package.json +1 -1
package/dist/local-data.js
CHANGED
|
@@ -58,9 +58,20 @@ exports.createConversation = createConversation;
|
|
|
58
58
|
exports.getConversation = getConversation;
|
|
59
59
|
exports.listConversations = listConversations;
|
|
60
60
|
exports.countConversations = countConversations;
|
|
61
|
+
exports.countMessagesForBot = countMessagesForBot;
|
|
62
|
+
exports.listBotConversationActivity = listBotConversationActivity;
|
|
61
63
|
exports.updateConversation = updateConversation;
|
|
64
|
+
exports.touchConversationActivity = touchConversationActivity;
|
|
62
65
|
exports.deleteConversation = deleteConversation;
|
|
63
66
|
exports.addMessage = addMessage;
|
|
67
|
+
exports.updateMessage = updateMessage;
|
|
68
|
+
exports.createChatJob = createChatJob;
|
|
69
|
+
exports.getChatJob = getChatJob;
|
|
70
|
+
exports.getLatestConversationChatJob = getLatestConversationChatJob;
|
|
71
|
+
exports.listQueuedChatJobs = listQueuedChatJobs;
|
|
72
|
+
exports.countRunningChatJobs = countRunningChatJobs;
|
|
73
|
+
exports.updateChatJob = updateChatJob;
|
|
74
|
+
exports.markRunningChatJobsInterrupted = markRunningChatJobsInterrupted;
|
|
64
75
|
exports.createMessageActivity = createMessageActivity;
|
|
65
76
|
exports.attachMessageActivitiesToMessage = attachMessageActivitiesToMessage;
|
|
66
77
|
exports.listMessageActivities = listMessageActivities;
|
|
@@ -104,6 +115,7 @@ exports.setSetting = setSetting;
|
|
|
104
115
|
exports.setJsonSetting = setJsonSetting;
|
|
105
116
|
exports.getAllSettings = getAllSettings;
|
|
106
117
|
exports.deleteSetting = deleteSetting;
|
|
118
|
+
exports.purgeLegacyExtractionDataOnce = purgeLegacyExtractionDataOnce;
|
|
107
119
|
exports.defaultOrchestrationPolicy = defaultOrchestrationPolicy;
|
|
108
120
|
exports.resolveCurrentUserIdentity = resolveCurrentUserIdentity;
|
|
109
121
|
exports.getUserOrchestrationPolicy = getUserOrchestrationPolicy;
|
|
@@ -211,6 +223,7 @@ const memory_extraction_1 = require("./memory-extraction");
|
|
|
211
223
|
const orchestrator_blocked_prompt_1 = require("./orchestration/orchestrator-blocked-prompt");
|
|
212
224
|
const crypto = __importStar(require("crypto"));
|
|
213
225
|
const default_tool_profile_1 = require("./default-tool-profile");
|
|
226
|
+
const approval_1 = require("./approval");
|
|
214
227
|
let _db = null;
|
|
215
228
|
/** Get or open the shared database connection */
|
|
216
229
|
function getDb() {
|
|
@@ -249,7 +262,7 @@ function createAgentProfile(p) {
|
|
|
249
262
|
if (p.isOrchestrator) {
|
|
250
263
|
db.prepare("UPDATE agent_profile SET is_orchestrator = 0, updated_at = datetime('now') WHERE is_orchestrator = 1").run();
|
|
251
264
|
}
|
|
252
|
-
insert.run(id, p.provider, p.model, p.name, p.soulMd ?? null, p.memoryMd ?? null, p.toolsMd ?? null, p.skillsMd ?? null, p.apiKeyEnc ?? null, p.permissionMode ?? default_tool_profile_1.DEFAULT_PERMISSION_MODE, p.isDefault ? 1 : 0, p.roleLabel ?? null, p.roleClass ?? null, p.isActive !== false ? 1 : 0, p.priority ?? 100, p.providerConnectionId ?? null, p.color ?? null, p.purposeMd ?? null, p.identitySummary ?? null, p.finalPrompt ?? null, p.enabledBuiltinToolsJson ?? null, p.enabledMcpToolsJson ?? null, p.isOrchestrator ? 1 : 0, ts, ts);
|
|
265
|
+
insert.run(id, p.provider, p.model, p.name, p.soulMd ?? null, p.memoryMd ?? null, p.toolsMd ?? null, p.skillsMd ?? null, p.apiKeyEnc ?? null, (0, approval_1.normalizePermissionMode)(p.permissionMode ?? default_tool_profile_1.DEFAULT_PERMISSION_MODE), p.isDefault ? 1 : 0, p.roleLabel ?? null, p.roleClass ?? null, p.isActive !== false ? 1 : 0, p.priority ?? 100, p.providerConnectionId ?? null, p.color ?? null, p.purposeMd ?? null, p.identitySummary ?? null, p.finalPrompt ?? null, p.enabledBuiltinToolsJson ?? null, p.enabledMcpToolsJson ?? null, p.isOrchestrator ? 1 : 0, ts, ts);
|
|
253
266
|
});
|
|
254
267
|
transaction();
|
|
255
268
|
return db.prepare('SELECT * FROM agent_profile WHERE id = ?').get(id);
|
|
@@ -286,7 +299,8 @@ function migrateAgentProfilesToDefaultToolProfile() {
|
|
|
286
299
|
catch {
|
|
287
300
|
tools = [];
|
|
288
301
|
}
|
|
289
|
-
const
|
|
302
|
+
const normalizedPermission = (0, approval_1.normalizePermissionMode)(row.permission_mode);
|
|
303
|
+
const needsPermission = !row.permission_mode || row.permission_mode !== normalizedPermission;
|
|
290
304
|
const needsTools = tools.length === 0;
|
|
291
305
|
if (!needsPermission && !needsTools)
|
|
292
306
|
continue;
|
|
@@ -294,7 +308,7 @@ function migrateAgentProfilesToDefaultToolProfile() {
|
|
|
294
308
|
const vals = [now()];
|
|
295
309
|
if (needsPermission) {
|
|
296
310
|
sets.push('permission_mode = ?');
|
|
297
|
-
vals.push(
|
|
311
|
+
vals.push(normalizedPermission);
|
|
298
312
|
}
|
|
299
313
|
if (needsTools) {
|
|
300
314
|
sets.push('enabled_builtin_tools_json = ?');
|
|
@@ -347,7 +361,7 @@ function updateAgentProfile(id, fields) {
|
|
|
347
361
|
}
|
|
348
362
|
if (fields.permissionMode !== undefined) {
|
|
349
363
|
sets.push('permission_mode = ?');
|
|
350
|
-
vals.push(fields.permissionMode);
|
|
364
|
+
vals.push((0, approval_1.normalizePermissionMode)(fields.permissionMode));
|
|
351
365
|
}
|
|
352
366
|
if (fields.isDefault !== undefined) {
|
|
353
367
|
sets.push('is_default = ?');
|
|
@@ -660,37 +674,206 @@ function createConversation(agentId, title, source, opts) {
|
|
|
660
674
|
`).run(id, agentId, title ?? null, source ?? 'local', projectId, projectName, ts, ts);
|
|
661
675
|
return db.prepare('SELECT * FROM conversation WHERE id = ?').get(id);
|
|
662
676
|
}
|
|
677
|
+
function latestConversationSummarySql(conversationAlias) {
|
|
678
|
+
return `(
|
|
679
|
+
SELECT cs.summary_text
|
|
680
|
+
FROM conversation_summary cs
|
|
681
|
+
WHERE cs.conversation_id = ${conversationAlias}.id
|
|
682
|
+
ORDER BY cs.turn_range_end DESC, cs.created_at DESC
|
|
683
|
+
LIMIT 1
|
|
684
|
+
)`;
|
|
685
|
+
}
|
|
686
|
+
function primaryConversationTopicTitleSql(conversationAlias) {
|
|
687
|
+
return `(
|
|
688
|
+
SELECT t.title
|
|
689
|
+
FROM topic_segment ts
|
|
690
|
+
INNER JOIN topic t ON t.id = ts.topic_id
|
|
691
|
+
WHERE ts.conversation_id = ${conversationAlias}.id
|
|
692
|
+
ORDER BY ts.created_at ASC
|
|
693
|
+
LIMIT 1
|
|
694
|
+
)`;
|
|
695
|
+
}
|
|
696
|
+
function firstConversationUserMessageSql(conversationAlias, maxLength = 120) {
|
|
697
|
+
return `(
|
|
698
|
+
SELECT substr(trim(m.content), 1, ${maxLength})
|
|
699
|
+
FROM message m
|
|
700
|
+
WHERE m.conversation_id = ${conversationAlias}.id
|
|
701
|
+
AND m.role = 'user'
|
|
702
|
+
AND trim(coalesce(m.content, '')) <> ''
|
|
703
|
+
AND trim(coalesce(m.content, '')) NOT LIKE '[System Instructions]%'
|
|
704
|
+
AND trim(coalesce(m.content, '')) NOT LIKE '# AGENTS.md%'
|
|
705
|
+
AND trim(coalesce(m.content, '')) NOT LIKE '<INSTRUCTIONS%'
|
|
706
|
+
AND trim(coalesce(m.content, '')) NOT LIKE '<environment_context%'
|
|
707
|
+
AND trim(coalesce(m.content, '')) NOT LIKE 'CURRENT TODO ID:%'
|
|
708
|
+
ORDER BY m.seq ASC
|
|
709
|
+
LIMIT 1
|
|
710
|
+
)`;
|
|
711
|
+
}
|
|
712
|
+
function firstConversationAssistantMessageSql(conversationAlias, maxLength = 120) {
|
|
713
|
+
return `(
|
|
714
|
+
SELECT substr(trim(m.content), 1, ${maxLength})
|
|
715
|
+
FROM message m
|
|
716
|
+
WHERE m.conversation_id = ${conversationAlias}.id
|
|
717
|
+
AND m.role = 'assistant'
|
|
718
|
+
AND trim(coalesce(m.content, '')) <> ''
|
|
719
|
+
ORDER BY m.seq ASC
|
|
720
|
+
LIMIT 1
|
|
721
|
+
)`;
|
|
722
|
+
}
|
|
723
|
+
function conversationSelectSql(conversationAlias) {
|
|
724
|
+
const latestSummary = latestConversationSummarySql(conversationAlias);
|
|
725
|
+
const topicTitle = primaryConversationTopicTitleSql(conversationAlias);
|
|
726
|
+
const firstUserMessage = firstConversationUserMessageSql(conversationAlias);
|
|
727
|
+
const firstAssistantMessage = firstConversationAssistantMessageSql(conversationAlias);
|
|
728
|
+
return `
|
|
729
|
+
SELECT
|
|
730
|
+
${conversationAlias}.*,
|
|
731
|
+
COALESCE(
|
|
732
|
+
CASE
|
|
733
|
+
WHEN ${conversationAlias}.title IS NOT NULL
|
|
734
|
+
AND trim(${conversationAlias}.title) <> ''
|
|
735
|
+
AND trim(${conversationAlias}.title) <> 'New Conversation'
|
|
736
|
+
AND trim(${conversationAlias}.title) NOT LIKE '[System Instructions]%'
|
|
737
|
+
AND trim(${conversationAlias}.title) NOT LIKE '# AGENTS.md%'
|
|
738
|
+
AND trim(${conversationAlias}.title) NOT LIKE '<INSTRUCTIONS%'
|
|
739
|
+
AND trim(${conversationAlias}.title) NOT LIKE '<environment_context%'
|
|
740
|
+
AND trim(${conversationAlias}.title) NOT LIKE 'CURRENT TODO ID:%'
|
|
741
|
+
THEN ${conversationAlias}.title
|
|
742
|
+
ELSE NULL
|
|
743
|
+
END,
|
|
744
|
+
${topicTitle},
|
|
745
|
+
${firstUserMessage},
|
|
746
|
+
${firstAssistantMessage},
|
|
747
|
+
${conversationAlias}.title
|
|
748
|
+
) AS resolved_title,
|
|
749
|
+
COALESCE(${latestSummary}, ${conversationAlias}.summary) AS resolved_summary,
|
|
750
|
+
CASE
|
|
751
|
+
WHEN ${conversationAlias}.source = 'import'
|
|
752
|
+
AND ${conversationAlias}.conversation_started_at IS NOT NULL
|
|
753
|
+
THEN ${conversationAlias}.conversation_started_at
|
|
754
|
+
ELSE ${conversationAlias}.created_at
|
|
755
|
+
END AS effective_created_at,
|
|
756
|
+
CASE
|
|
757
|
+
WHEN ${conversationAlias}.source = 'import'
|
|
758
|
+
AND ${conversationAlias}.conversation_ended_at IS NOT NULL
|
|
759
|
+
THEN ${conversationAlias}.conversation_ended_at
|
|
760
|
+
ELSE ${conversationAlias}.updated_at
|
|
761
|
+
END AS effective_updated_at
|
|
762
|
+
FROM conversation ${conversationAlias}
|
|
763
|
+
`;
|
|
764
|
+
}
|
|
765
|
+
function hydrateConversationRow(row) {
|
|
766
|
+
if (!row)
|
|
767
|
+
return undefined;
|
|
768
|
+
return {
|
|
769
|
+
...row,
|
|
770
|
+
title: row.resolved_title ?? row.title,
|
|
771
|
+
summary: row.resolved_summary ?? row.summary,
|
|
772
|
+
created_at: row.effective_created_at ?? row.created_at,
|
|
773
|
+
updated_at: row.effective_updated_at ?? row.updated_at,
|
|
774
|
+
};
|
|
775
|
+
}
|
|
663
776
|
function getConversation(id) {
|
|
664
|
-
return getDb().prepare('
|
|
777
|
+
return hydrateConversationRow(getDb().prepare(`${conversationSelectSql('c')} WHERE c.id = ?`).get(id));
|
|
665
778
|
}
|
|
666
779
|
function listConversations(opts) {
|
|
667
780
|
const db = getDb();
|
|
668
781
|
const limit = opts?.limit ?? 50;
|
|
669
782
|
const offset = opts?.offset ?? 0;
|
|
670
|
-
let sql = '
|
|
783
|
+
let sql = conversationSelectSql('c');
|
|
671
784
|
const params = [];
|
|
672
785
|
const wheres = [];
|
|
673
786
|
if (opts?.agentId) {
|
|
674
|
-
wheres.push(
|
|
675
|
-
|
|
787
|
+
wheres.push(`(
|
|
788
|
+
c.agent_id = ?
|
|
789
|
+
OR EXISTS (
|
|
790
|
+
SELECT 1
|
|
791
|
+
FROM message m
|
|
792
|
+
WHERE m.conversation_id = c.id
|
|
793
|
+
AND m.bot_id = ?
|
|
794
|
+
)
|
|
795
|
+
)`);
|
|
796
|
+
params.push(opts.agentId, opts.agentId);
|
|
676
797
|
}
|
|
677
798
|
if (opts?.search) {
|
|
678
|
-
wheres.push(
|
|
679
|
-
|
|
799
|
+
wheres.push(`(
|
|
800
|
+
coalesce(c.title, '') LIKE ?
|
|
801
|
+
OR coalesce(c.summary, '') LIKE ?
|
|
802
|
+
OR coalesce(${latestConversationSummarySql('c')}, '') LIKE ?
|
|
803
|
+
OR coalesce(${primaryConversationTopicTitleSql('c')}, '') LIKE ?
|
|
804
|
+
OR coalesce(c.project_name, '') LIKE ?
|
|
805
|
+
OR coalesce(c.description, '') LIKE ?
|
|
806
|
+
OR coalesce(c.tags, '') LIKE ?
|
|
807
|
+
OR coalesce(c.topics_discussed, '') LIKE ?
|
|
808
|
+
OR coalesce(c.tech_stack, '') LIKE ?
|
|
809
|
+
OR coalesce(c.category, '') LIKE ?
|
|
810
|
+
)`);
|
|
811
|
+
const needle = `%${opts.search}%`;
|
|
812
|
+
params.push(needle, needle, needle, needle, needle, needle, needle, needle, needle, needle);
|
|
680
813
|
}
|
|
681
814
|
if (wheres.length)
|
|
682
815
|
sql += ' WHERE ' + wheres.join(' AND ');
|
|
683
|
-
sql += ' ORDER BY updated_at DESC LIMIT ? OFFSET ?';
|
|
816
|
+
sql += ' ORDER BY c.updated_at DESC LIMIT ? OFFSET ?';
|
|
684
817
|
params.push(limit, offset);
|
|
685
|
-
return db.prepare(sql).all(...params);
|
|
818
|
+
return db.prepare(sql).all(...params).map((row) => hydrateConversationRow(row));
|
|
686
819
|
}
|
|
687
820
|
function countConversations(agentId) {
|
|
688
821
|
const db = getDb();
|
|
689
822
|
if (agentId) {
|
|
690
|
-
return db.prepare(
|
|
823
|
+
return db.prepare(`
|
|
824
|
+
SELECT count(DISTINCT c.id) as cnt
|
|
825
|
+
FROM conversation c
|
|
826
|
+
WHERE c.agent_id = ?
|
|
827
|
+
OR EXISTS (
|
|
828
|
+
SELECT 1
|
|
829
|
+
FROM message m
|
|
830
|
+
WHERE m.conversation_id = c.id
|
|
831
|
+
AND m.bot_id = ?
|
|
832
|
+
)
|
|
833
|
+
`).get(agentId, agentId).cnt;
|
|
691
834
|
}
|
|
692
835
|
return db.prepare('SELECT count(*) as cnt FROM conversation').get().cnt;
|
|
693
836
|
}
|
|
837
|
+
function countMessagesForBot(botId) {
|
|
838
|
+
return getDb().prepare(`
|
|
839
|
+
SELECT count(*) as cnt
|
|
840
|
+
FROM message
|
|
841
|
+
WHERE bot_id = ?
|
|
842
|
+
`).get(botId).cnt;
|
|
843
|
+
}
|
|
844
|
+
function listBotConversationActivity(botId, limit = 10) {
|
|
845
|
+
const db = getDb();
|
|
846
|
+
return db.prepare(`
|
|
847
|
+
SELECT
|
|
848
|
+
conv.*,
|
|
849
|
+
COALESCE((
|
|
850
|
+
SELECT count(*)
|
|
851
|
+
FROM message bm
|
|
852
|
+
WHERE bm.conversation_id = conv.id
|
|
853
|
+
AND bm.bot_id = ?
|
|
854
|
+
), 0) AS bot_message_count,
|
|
855
|
+
(
|
|
856
|
+
SELECT max(bm.created_at)
|
|
857
|
+
FROM message bm
|
|
858
|
+
WHERE bm.conversation_id = conv.id
|
|
859
|
+
AND bm.bot_id = ?
|
|
860
|
+
) AS bot_last_message_at
|
|
861
|
+
FROM (
|
|
862
|
+
${conversationSelectSql('c')}
|
|
863
|
+
WHERE (
|
|
864
|
+
c.agent_id = ?
|
|
865
|
+
OR EXISTS (
|
|
866
|
+
SELECT 1
|
|
867
|
+
FROM message m
|
|
868
|
+
WHERE m.conversation_id = c.id
|
|
869
|
+
AND m.bot_id = ?
|
|
870
|
+
)
|
|
871
|
+
)
|
|
872
|
+
) conv
|
|
873
|
+
ORDER BY COALESCE(bot_last_message_at, conv.updated_at, conv.created_at) DESC
|
|
874
|
+
LIMIT ?
|
|
875
|
+
`).all(botId, botId, botId, botId, limit).map((row) => hydrateConversationRow(row));
|
|
876
|
+
}
|
|
694
877
|
function updateConversation(id, fields) {
|
|
695
878
|
const db = getDb();
|
|
696
879
|
const sets = [];
|
|
@@ -731,6 +914,8 @@ function updateConversation(id, fields) {
|
|
|
731
914
|
processingError: ['processing_error', fields.processingError],
|
|
732
915
|
botId: ['bot_id', fields.botId],
|
|
733
916
|
forkedFromId: ['forked_from_id', fields.forkedFromId],
|
|
917
|
+
createdAt: ['created_at', fields.createdAt],
|
|
918
|
+
updatedAt: ['updated_at', fields.updatedAt],
|
|
734
919
|
};
|
|
735
920
|
for (const [key, [col, val]] of Object.entries(map)) {
|
|
736
921
|
if (fields[key] !== undefined) {
|
|
@@ -748,17 +933,30 @@ function updateConversation(id, fields) {
|
|
|
748
933
|
}
|
|
749
934
|
if (sets.length === 0)
|
|
750
935
|
return;
|
|
751
|
-
|
|
936
|
+
if (fields.updatedAt === undefined) {
|
|
937
|
+
sets.push("updated_at = datetime('now')");
|
|
938
|
+
}
|
|
752
939
|
vals.push(id);
|
|
753
940
|
db.prepare(`UPDATE conversation SET ${sets.join(', ')} WHERE id = ?`).run(...vals);
|
|
754
941
|
}
|
|
942
|
+
function touchConversationActivity(id, ts) {
|
|
943
|
+
const stamp = ts?.trim() || now();
|
|
944
|
+
getDb().prepare(`
|
|
945
|
+
UPDATE conversation
|
|
946
|
+
SET updated_at = CASE
|
|
947
|
+
WHEN datetime(updated_at) > datetime(?) THEN updated_at
|
|
948
|
+
ELSE ?
|
|
949
|
+
END
|
|
950
|
+
WHERE id = ?
|
|
951
|
+
`).run(stamp, stamp, id);
|
|
952
|
+
}
|
|
755
953
|
function deleteConversation(id) {
|
|
756
954
|
return getDb().prepare('DELETE FROM conversation WHERE id = ?').run(id).changes > 0;
|
|
757
955
|
}
|
|
758
|
-
function addMessage(conversationId, role, content, model, toolCallsJson, botId, agentName) {
|
|
956
|
+
function addMessage(conversationId, role, content, model, toolCallsJson, botId, agentName, createdAt) {
|
|
759
957
|
const db = getDb();
|
|
760
958
|
const id = newId();
|
|
761
|
-
const ts = now();
|
|
959
|
+
const ts = createdAt?.trim() || now();
|
|
762
960
|
// Get next sequence number
|
|
763
961
|
const maxSeq = db.prepare('SELECT COALESCE(MAX(seq), 0) as m FROM message WHERE conversation_id = ?').get(conversationId);
|
|
764
962
|
const seq = maxSeq.m + 1;
|
|
@@ -767,9 +965,158 @@ function addMessage(conversationId, role, content, model, toolCallsJson, botId,
|
|
|
767
965
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
768
966
|
`).run(id, conversationId, seq, role, content, model ?? null, toolCallsJson ?? null, botId ?? null, agentName ?? null, ts);
|
|
769
967
|
// Update conversation message_count and updated_at
|
|
770
|
-
db.prepare(
|
|
968
|
+
db.prepare(`
|
|
969
|
+
UPDATE conversation
|
|
970
|
+
SET message_count = message_count + 1,
|
|
971
|
+
updated_at = CASE
|
|
972
|
+
WHEN datetime(updated_at) > datetime(?) THEN updated_at
|
|
973
|
+
ELSE ?
|
|
974
|
+
END
|
|
975
|
+
WHERE id = ?
|
|
976
|
+
`).run(ts, ts, conversationId);
|
|
771
977
|
return db.prepare('SELECT * FROM message WHERE id = ?').get(id);
|
|
772
978
|
}
|
|
979
|
+
function updateMessage(id, fields) {
|
|
980
|
+
const db = getDb();
|
|
981
|
+
const row = db.prepare('SELECT * FROM message WHERE id = ?').get(id);
|
|
982
|
+
if (!row)
|
|
983
|
+
return undefined;
|
|
984
|
+
const sets = [];
|
|
985
|
+
const vals = [];
|
|
986
|
+
if (fields.content !== undefined) {
|
|
987
|
+
sets.push('content = ?');
|
|
988
|
+
vals.push(fields.content);
|
|
989
|
+
}
|
|
990
|
+
if (fields.model !== undefined) {
|
|
991
|
+
sets.push('model = ?');
|
|
992
|
+
vals.push(fields.model);
|
|
993
|
+
}
|
|
994
|
+
if (fields.toolCallsJson !== undefined) {
|
|
995
|
+
sets.push('tool_calls_json = ?');
|
|
996
|
+
vals.push(fields.toolCallsJson);
|
|
997
|
+
}
|
|
998
|
+
if (fields.botId !== undefined) {
|
|
999
|
+
sets.push('bot_id = ?');
|
|
1000
|
+
vals.push(fields.botId);
|
|
1001
|
+
}
|
|
1002
|
+
if (fields.agentName !== undefined) {
|
|
1003
|
+
sets.push('agent_name = ?');
|
|
1004
|
+
vals.push(fields.agentName);
|
|
1005
|
+
}
|
|
1006
|
+
if (fields.resultArtifact !== undefined) {
|
|
1007
|
+
sets.push('result_artifact = ?');
|
|
1008
|
+
vals.push(fields.resultArtifact);
|
|
1009
|
+
}
|
|
1010
|
+
if (fields.resultSummary !== undefined) {
|
|
1011
|
+
sets.push('result_summary = ?');
|
|
1012
|
+
vals.push(fields.resultSummary);
|
|
1013
|
+
}
|
|
1014
|
+
if (fields.resultStatus !== undefined) {
|
|
1015
|
+
sets.push('result_status = ?');
|
|
1016
|
+
vals.push(fields.resultStatus);
|
|
1017
|
+
}
|
|
1018
|
+
if (sets.length === 0)
|
|
1019
|
+
return row;
|
|
1020
|
+
vals.push(id);
|
|
1021
|
+
db.prepare(`UPDATE message SET ${sets.join(', ')} WHERE id = ?`).run(...vals);
|
|
1022
|
+
return db.prepare('SELECT * FROM message WHERE id = ?').get(id);
|
|
1023
|
+
}
|
|
1024
|
+
function createChatJob(input) {
|
|
1025
|
+
const db = getDb();
|
|
1026
|
+
const id = newId();
|
|
1027
|
+
const ts = now();
|
|
1028
|
+
db.prepare(`
|
|
1029
|
+
INSERT INTO chat_job (
|
|
1030
|
+
id, conversation_id, user_message_id, assistant_message_id, bot_id,
|
|
1031
|
+
status, request_json, created_at, updated_at
|
|
1032
|
+
)
|
|
1033
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1034
|
+
`).run(id, input.conversationId, input.userMessageId, input.assistantMessageId, input.botId, input.status ?? 'queued', input.requestJson ?? null, ts, ts);
|
|
1035
|
+
return db.prepare('SELECT * FROM chat_job WHERE id = ?').get(id);
|
|
1036
|
+
}
|
|
1037
|
+
function getChatJob(id) {
|
|
1038
|
+
return getDb().prepare('SELECT * FROM chat_job WHERE id = ?').get(id);
|
|
1039
|
+
}
|
|
1040
|
+
function getLatestConversationChatJob(conversationId) {
|
|
1041
|
+
return getDb().prepare(`
|
|
1042
|
+
SELECT *
|
|
1043
|
+
FROM chat_job
|
|
1044
|
+
WHERE conversation_id = ?
|
|
1045
|
+
ORDER BY datetime(created_at) DESC, rowid DESC
|
|
1046
|
+
LIMIT 1
|
|
1047
|
+
`).get(conversationId);
|
|
1048
|
+
}
|
|
1049
|
+
function listQueuedChatJobs(limit = 50) {
|
|
1050
|
+
return getDb().prepare(`
|
|
1051
|
+
SELECT *
|
|
1052
|
+
FROM chat_job
|
|
1053
|
+
WHERE status = 'queued'
|
|
1054
|
+
ORDER BY datetime(created_at) ASC, rowid ASC
|
|
1055
|
+
LIMIT ?
|
|
1056
|
+
`).all(limit);
|
|
1057
|
+
}
|
|
1058
|
+
function countRunningChatJobs() {
|
|
1059
|
+
return getDb().prepare(`
|
|
1060
|
+
SELECT count(*) AS cnt
|
|
1061
|
+
FROM chat_job
|
|
1062
|
+
WHERE status = 'running'
|
|
1063
|
+
`).get().cnt;
|
|
1064
|
+
}
|
|
1065
|
+
function updateChatJob(id, fields) {
|
|
1066
|
+
const db = getDb();
|
|
1067
|
+
const row = db.prepare('SELECT * FROM chat_job WHERE id = ?').get(id);
|
|
1068
|
+
if (!row)
|
|
1069
|
+
return undefined;
|
|
1070
|
+
const sets = [];
|
|
1071
|
+
const vals = [];
|
|
1072
|
+
if (fields.status !== undefined) {
|
|
1073
|
+
sets.push('status = ?');
|
|
1074
|
+
vals.push(fields.status);
|
|
1075
|
+
}
|
|
1076
|
+
if (fields.error !== undefined) {
|
|
1077
|
+
sets.push('error = ?');
|
|
1078
|
+
vals.push(fields.error);
|
|
1079
|
+
}
|
|
1080
|
+
if (fields.requestJson !== undefined) {
|
|
1081
|
+
sets.push('request_json = ?');
|
|
1082
|
+
vals.push(fields.requestJson);
|
|
1083
|
+
}
|
|
1084
|
+
if (fields.startedAt !== undefined) {
|
|
1085
|
+
sets.push('started_at = ?');
|
|
1086
|
+
vals.push(fields.startedAt);
|
|
1087
|
+
}
|
|
1088
|
+
if (fields.completedAt !== undefined) {
|
|
1089
|
+
sets.push('completed_at = ?');
|
|
1090
|
+
vals.push(fields.completedAt);
|
|
1091
|
+
}
|
|
1092
|
+
if (fields.cancelledAt !== undefined) {
|
|
1093
|
+
sets.push('cancelled_at = ?');
|
|
1094
|
+
vals.push(fields.cancelledAt);
|
|
1095
|
+
}
|
|
1096
|
+
if (sets.length === 0)
|
|
1097
|
+
return row;
|
|
1098
|
+
sets.push('updated_at = ?');
|
|
1099
|
+
vals.push(now());
|
|
1100
|
+
vals.push(id);
|
|
1101
|
+
db.prepare(`UPDATE chat_job SET ${sets.join(', ')} WHERE id = ?`).run(...vals);
|
|
1102
|
+
return db.prepare('SELECT * FROM chat_job WHERE id = ?').get(id);
|
|
1103
|
+
}
|
|
1104
|
+
function markRunningChatJobsInterrupted(reason = 'Interrupted when app closed') {
|
|
1105
|
+
const db = getDb();
|
|
1106
|
+
const ts = now();
|
|
1107
|
+
const result = db.prepare(`
|
|
1108
|
+
UPDATE chat_job
|
|
1109
|
+
SET status = 'failed',
|
|
1110
|
+
error = CASE
|
|
1111
|
+
WHEN error IS NULL OR trim(error) = '' THEN ?
|
|
1112
|
+
ELSE error
|
|
1113
|
+
END,
|
|
1114
|
+
completed_at = COALESCE(completed_at, ?),
|
|
1115
|
+
updated_at = ?
|
|
1116
|
+
WHERE status = 'running'
|
|
1117
|
+
`).run(reason, ts, ts);
|
|
1118
|
+
return result.changes;
|
|
1119
|
+
}
|
|
773
1120
|
function createMessageActivity(activity) {
|
|
774
1121
|
const db = getDb();
|
|
775
1122
|
const id = newId();
|
|
@@ -1198,11 +1545,6 @@ function listMemoryFacts(opts) {
|
|
|
1198
1545
|
function deleteMemoryFact(id) {
|
|
1199
1546
|
return getDb().prepare('DELETE FROM memory_fact WHERE id = ?').run(id).changes > 0;
|
|
1200
1547
|
}
|
|
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
1548
|
function getUserProfileFacts(agentId, limit = 15) {
|
|
1207
1549
|
const db = getDb();
|
|
1208
1550
|
return db.prepare(`
|
|
@@ -1398,6 +1740,19 @@ function getAllSettings() {
|
|
|
1398
1740
|
function deleteSetting(key) {
|
|
1399
1741
|
return getDb().prepare('DELETE FROM settings WHERE key = ?').run(key).changes > 0;
|
|
1400
1742
|
}
|
|
1743
|
+
function purgeLegacyExtractionDataOnce() {
|
|
1744
|
+
if (getSetting('legacy_extraction_cleanup_v1') === 'done')
|
|
1745
|
+
return false;
|
|
1746
|
+
const db = getDb();
|
|
1747
|
+
const tx = db.transaction(() => {
|
|
1748
|
+
for (const table of ['entity_relationship', 'entity', 'memory_fact', 'decision', 'action_item']) {
|
|
1749
|
+
db.prepare(`DELETE FROM ${table}`).run();
|
|
1750
|
+
}
|
|
1751
|
+
setSetting('legacy_extraction_cleanup_v1', 'done');
|
|
1752
|
+
});
|
|
1753
|
+
tx();
|
|
1754
|
+
return true;
|
|
1755
|
+
}
|
|
1401
1756
|
const AUTH_SESSION_KEY = 'auth.session';
|
|
1402
1757
|
function defaultOrchestrationPolicy() {
|
|
1403
1758
|
return {
|
|
@@ -1852,6 +2207,36 @@ function inferTaskTypeForAgent(agent, requested) {
|
|
|
1852
2207
|
return explicit;
|
|
1853
2208
|
return roleClassToTaskType(agent?.role_class) || roleClassToTaskType(agent?.role_label) || null;
|
|
1854
2209
|
}
|
|
2210
|
+
function buildQueuedTaskContext(task) {
|
|
2211
|
+
const lines = [
|
|
2212
|
+
'NEXT TASK CONTEXT',
|
|
2213
|
+
`Title: ${task.title}`,
|
|
2214
|
+
task.task_type ? `Task type: ${task.task_type}` : null,
|
|
2215
|
+
task.details?.trim() ? `Details: ${task.details.trim()}` : null,
|
|
2216
|
+
task.success_criteria?.trim() ? `Success criteria: ${task.success_criteria.trim()}` : null,
|
|
2217
|
+
].filter(Boolean);
|
|
2218
|
+
return lines.join('\n');
|
|
2219
|
+
}
|
|
2220
|
+
function injectWorkerHandoffIntoPrompt(existingPrompt, handoffText, nextTask) {
|
|
2221
|
+
const taskReplacement = [
|
|
2222
|
+
'Task:',
|
|
2223
|
+
'PREVIOUS WORKER HANDOFF',
|
|
2224
|
+
handoffText,
|
|
2225
|
+
'',
|
|
2226
|
+
buildQueuedTaskContext(nextTask),
|
|
2227
|
+
'',
|
|
2228
|
+
'RUNTIME CONTEXT',
|
|
2229
|
+
].join('\n');
|
|
2230
|
+
if (existingPrompt.includes('\nRUNTIME CONTEXT\n')) {
|
|
2231
|
+
return existingPrompt.replace(/Task:\s*[\s\S]*?\nRUNTIME CONTEXT\n/, `${taskReplacement}\n`);
|
|
2232
|
+
}
|
|
2233
|
+
return [
|
|
2234
|
+
'PREVIOUS WORKER HANDOFF',
|
|
2235
|
+
handoffText,
|
|
2236
|
+
'',
|
|
2237
|
+
buildQueuedTaskContext(nextTask),
|
|
2238
|
+
].join('\n');
|
|
2239
|
+
}
|
|
1855
2240
|
function normalizeArtifactRow(row) {
|
|
1856
2241
|
return {
|
|
1857
2242
|
id: Number(row.id),
|
|
@@ -1982,6 +2367,44 @@ function resolveAllowedWorkerNamesForInsertion(currentTask) {
|
|
|
1982
2367
|
allowed.add(previous.owner_name.trim().toLowerCase());
|
|
1983
2368
|
return Array.from(allowed);
|
|
1984
2369
|
}
|
|
2370
|
+
function resolvePreviousWorkerName(currentTask) {
|
|
2371
|
+
const db = getDb();
|
|
2372
|
+
const previous = currentTask.project_id
|
|
2373
|
+
? db.prepare(`
|
|
2374
|
+
SELECT owner_name
|
|
2375
|
+
FROM (
|
|
2376
|
+
SELECT owner_name, created_order, internal_only FROM todo_active WHERE project_id = ?
|
|
2377
|
+
UNION ALL
|
|
2378
|
+
SELECT owner_name, created_order, internal_only FROM todo_completed WHERE project_id = ?
|
|
2379
|
+
)
|
|
2380
|
+
WHERE created_order < ? AND COALESCE(internal_only, 0) = 0 AND owner_name IS NOT NULL
|
|
2381
|
+
ORDER BY created_order DESC
|
|
2382
|
+
LIMIT 1
|
|
2383
|
+
`).get(currentTask.project_id, currentTask.project_id, currentTask.position)
|
|
2384
|
+
: db.prepare(`
|
|
2385
|
+
SELECT owner_name
|
|
2386
|
+
FROM (
|
|
2387
|
+
SELECT owner_name, created_order, internal_only FROM todo_active WHERE project_id IS NULL
|
|
2388
|
+
UNION ALL
|
|
2389
|
+
SELECT owner_name, created_order, internal_only FROM todo_completed WHERE project_id IS NULL
|
|
2390
|
+
)
|
|
2391
|
+
WHERE created_order < ? AND COALESCE(internal_only, 0) = 0 AND owner_name IS NOT NULL
|
|
2392
|
+
ORDER BY created_order DESC
|
|
2393
|
+
LIMIT 1
|
|
2394
|
+
`).get(currentTask.position);
|
|
2395
|
+
return previous?.owner_name?.trim() || null;
|
|
2396
|
+
}
|
|
2397
|
+
function shouldAutoCreateQaFixTask(task, outputSummary, handoffPrompt) {
|
|
2398
|
+
if (String(task.task_type || '').trim().toLowerCase() !== 'qa')
|
|
2399
|
+
return false;
|
|
2400
|
+
const combined = `${outputSummary || ''}\n${handoffPrompt || ''}`.trim().toLowerCase();
|
|
2401
|
+
if (!combined)
|
|
2402
|
+
return false;
|
|
2403
|
+
if (/(^|\b)(qa passed|all checks passed|no issues found|looks good to ship)(\b|$)/i.test(combined)) {
|
|
2404
|
+
return false;
|
|
2405
|
+
}
|
|
2406
|
+
return /\b(fix|defect|issue|problem|error|not ready|fails?|failed|remaining problem|remaining issue|validator|re-qa)\b/i.test(combined);
|
|
2407
|
+
}
|
|
1985
2408
|
function normalizeArtifactInput(ref) {
|
|
1986
2409
|
if (typeof ref === 'string') {
|
|
1987
2410
|
const trimmed = ref.trim();
|
|
@@ -2424,10 +2847,49 @@ function completeTodoTaskByWorker(taskId, params) {
|
|
|
2424
2847
|
}
|
|
2425
2848
|
}
|
|
2426
2849
|
else {
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
const
|
|
2430
|
-
|
|
2850
|
+
if (shouldAutoCreateQaFixTask(completedTask, outputSummary, handoffPrompt)) {
|
|
2851
|
+
const previousWorkerName = resolvePreviousWorkerName(completedTask);
|
|
2852
|
+
const previousWorker = resolveAgentByName(previousWorkerName);
|
|
2853
|
+
if (previousWorkerName && previousWorker) {
|
|
2854
|
+
const participants = [previousWorker.name];
|
|
2855
|
+
insertedTask = insertTodoTaskTx(db, {
|
|
2856
|
+
projectId: completedTask.project_id,
|
|
2857
|
+
conversationId: completedTask.conversation_id,
|
|
2858
|
+
title: 'Fix QA findings',
|
|
2859
|
+
details: handoffPrompt || outputSummary || 'Address the QA findings and update the implementation.',
|
|
2860
|
+
participants,
|
|
2861
|
+
successCriteria: 'Address the QA findings and return the updated work for re-QA.',
|
|
2862
|
+
ownerBotId: previousWorker.id,
|
|
2863
|
+
ownerName: previousWorker.name,
|
|
2864
|
+
taskType: inferTaskTypeForAgent(previousWorker, null),
|
|
2865
|
+
profileName: completedTask.profile_name || 'Programming',
|
|
2866
|
+
nextWorkerBotId: currentTask.owner_bot_id,
|
|
2867
|
+
nextWorkerName: currentTask.owner_name,
|
|
2868
|
+
nextWorkerRole: actorBot?.role_label || actorBot?.role_class || null,
|
|
2869
|
+
taskPrompt: handoffPrompt || outputSummary || 'Address the QA findings and keep the approved parts unchanged.',
|
|
2870
|
+
artifactIds,
|
|
2871
|
+
insertAfterTaskId: completedTask.id,
|
|
2872
|
+
actor: params.actor,
|
|
2873
|
+
});
|
|
2874
|
+
logTodoAuditTx(db, insertedTask.id, 'active', 'worker_insert', params.actor, {
|
|
2875
|
+
insertedAfterTaskId: completedTask.id,
|
|
2876
|
+
requestedWorkerName: previousWorker.name,
|
|
2877
|
+
autoCreatedFromQaFailure: true,
|
|
2878
|
+
});
|
|
2879
|
+
}
|
|
2880
|
+
else {
|
|
2881
|
+
returnedToOrchestrator = true;
|
|
2882
|
+
logTodoAuditTx(db, taskId, 'completed', 'return_to_orchestrator', params.actor, {
|
|
2883
|
+
handoffPrompt,
|
|
2884
|
+
missingPreviousWorkerForQaFix: true,
|
|
2885
|
+
});
|
|
2886
|
+
}
|
|
2887
|
+
}
|
|
2888
|
+
else {
|
|
2889
|
+
const nextWorkerName = completedTask.next_worker_name?.trim();
|
|
2890
|
+
if (nextWorkerName) {
|
|
2891
|
+
const nextRow = completedTask.next_worker_bot_id
|
|
2892
|
+
? db.prepare(`
|
|
2431
2893
|
SELECT * FROM todo_active
|
|
2432
2894
|
WHERE conversation_id = ?
|
|
2433
2895
|
AND id <> ?
|
|
@@ -2437,7 +2899,7 @@ function completeTodoTaskByWorker(taskId, params) {
|
|
|
2437
2899
|
ORDER BY created_order ASC, id ASC
|
|
2438
2900
|
LIMIT 1
|
|
2439
2901
|
`).get(completedTask.conversation_id, completedTask.id, completedTask.next_worker_bot_id, completedTask.created_order)
|
|
2440
|
-
|
|
2902
|
+
: db.prepare(`
|
|
2441
2903
|
SELECT * FROM todo_active
|
|
2442
2904
|
WHERE conversation_id = ?
|
|
2443
2905
|
AND id <> ?
|
|
@@ -2447,21 +2909,15 @@ function completeTodoTaskByWorker(taskId, params) {
|
|
|
2447
2909
|
ORDER BY created_order ASC, id ASC
|
|
2448
2910
|
LIMIT 1
|
|
2449
2911
|
`).get(completedTask.conversation_id, completedTask.id, nextWorkerName, completedTask.created_order);
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
handoffText,
|
|
2460
|
-
'',
|
|
2461
|
-
'ORIGINAL ASSIGNED TODO',
|
|
2462
|
-
existingPrompt,
|
|
2463
|
-
].join('\n');
|
|
2464
|
-
db.prepare(`
|
|
2912
|
+
if (nextRow) {
|
|
2913
|
+
const nextTask = normalizeTodoRow(nextRow, 'active');
|
|
2914
|
+
const handoffText = handoffPrompt?.trim()
|
|
2915
|
+
|| outputSummary?.trim()
|
|
2916
|
+
|| `Continue from the completed work summary: ${completedTask.title}`;
|
|
2917
|
+
const existingPrompt = nextTask.task_prompt?.trim() || nextTask.details || nextTask.title;
|
|
2918
|
+
const mergedArtifactIds = Array.from(new Set([...(nextTask.artifact_ids || []), ...artifactIds]));
|
|
2919
|
+
const nextPrompt = injectWorkerHandoffIntoPrompt(existingPrompt, handoffText, nextTask);
|
|
2920
|
+
db.prepare(`
|
|
2465
2921
|
UPDATE todo_active
|
|
2466
2922
|
SET task_prompt = ?,
|
|
2467
2923
|
artifact_ids_json = ?,
|
|
@@ -2469,26 +2925,27 @@ function completeTodoTaskByWorker(taskId, params) {
|
|
|
2469
2925
|
version = version + 1
|
|
2470
2926
|
WHERE id = ?
|
|
2471
2927
|
`).run(nextPrompt, JSON.stringify(mergedArtifactIds), now(), nextTask.id);
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2928
|
+
handedOffTask = normalizeTodoRow(db.prepare('SELECT * FROM todo_active WHERE id = ?').get(nextTask.id), 'active');
|
|
2929
|
+
logTodoAuditTx(db, nextTask.id, 'active', 'edit', params.actor, {
|
|
2930
|
+
previousTaskId: completedTask.id,
|
|
2931
|
+
receivedHandoff: true,
|
|
2932
|
+
});
|
|
2933
|
+
}
|
|
2934
|
+
else {
|
|
2935
|
+
returnedToOrchestrator = true;
|
|
2936
|
+
logTodoAuditTx(db, taskId, 'completed', 'return_to_orchestrator', params.actor, {
|
|
2937
|
+
handoffPrompt,
|
|
2938
|
+
missingQueuedNextWorker: nextWorkerName,
|
|
2939
|
+
});
|
|
2940
|
+
}
|
|
2477
2941
|
}
|
|
2478
2942
|
else {
|
|
2479
2943
|
returnedToOrchestrator = true;
|
|
2480
2944
|
logTodoAuditTx(db, taskId, 'completed', 'return_to_orchestrator', params.actor, {
|
|
2481
2945
|
handoffPrompt,
|
|
2482
|
-
missingQueuedNextWorker: nextWorkerName,
|
|
2483
2946
|
});
|
|
2484
2947
|
}
|
|
2485
2948
|
}
|
|
2486
|
-
else {
|
|
2487
|
-
returnedToOrchestrator = true;
|
|
2488
|
-
logTodoAuditTx(db, taskId, 'completed', 'return_to_orchestrator', params.actor, {
|
|
2489
|
-
handoffPrompt,
|
|
2490
|
-
});
|
|
2491
|
-
}
|
|
2492
2949
|
}
|
|
2493
2950
|
return {
|
|
2494
2951
|
completedTask,
|
|
@@ -3040,13 +3497,20 @@ function listTopicConversations(topicId, opts) {
|
|
|
3040
3497
|
const db = getDb();
|
|
3041
3498
|
const limit = opts?.limit ?? 50;
|
|
3042
3499
|
const offset = opts?.offset ?? 0;
|
|
3043
|
-
|
|
3044
|
-
SELECT DISTINCT
|
|
3045
|
-
|
|
3500
|
+
const rows = db.prepare(`
|
|
3501
|
+
SELECT DISTINCT conv.*
|
|
3502
|
+
FROM (
|
|
3503
|
+
${conversationSelectSql('c')}
|
|
3504
|
+
) conv
|
|
3505
|
+
INNER JOIN topic_segment ts ON ts.conversation_id = conv.id
|
|
3046
3506
|
WHERE ts.topic_id = ?
|
|
3047
|
-
ORDER BY
|
|
3507
|
+
ORDER BY datetime(COALESCE(conv.effective_updated_at, conv.updated_at)) DESC,
|
|
3508
|
+
datetime(COALESCE(conv.effective_created_at, conv.created_at)) DESC
|
|
3048
3509
|
LIMIT ? OFFSET ?
|
|
3049
3510
|
`).all(topicId, limit, offset);
|
|
3511
|
+
return rows
|
|
3512
|
+
.map((row) => hydrateConversationRow(row))
|
|
3513
|
+
.filter((row) => !!row);
|
|
3050
3514
|
}
|
|
3051
3515
|
function getPrimaryTopicIdForConversation(conversationId) {
|
|
3052
3516
|
const db = getDb();
|
|
@@ -3090,16 +3554,29 @@ function listTopicConversationsWithPreviews(topicId, opts) {
|
|
|
3090
3554
|
const limit = opts?.limit ?? 50;
|
|
3091
3555
|
const offset = opts?.offset ?? 0;
|
|
3092
3556
|
// Primary: find conversations via topic_segment links
|
|
3093
|
-
|
|
3094
|
-
SELECT DISTINCT
|
|
3095
|
-
|
|
3557
|
+
const rawConvs = db.prepare(`
|
|
3558
|
+
SELECT DISTINCT conv.*
|
|
3559
|
+
FROM (
|
|
3560
|
+
${conversationSelectSql('c')}
|
|
3561
|
+
) conv
|
|
3562
|
+
INNER JOIN topic_segment ts ON ts.conversation_id = conv.id
|
|
3096
3563
|
WHERE ts.topic_id = ?
|
|
3097
|
-
ORDER BY
|
|
3564
|
+
ORDER BY datetime(COALESCE(conv.effective_updated_at, conv.updated_at)) DESC,
|
|
3565
|
+
datetime(COALESCE(conv.effective_created_at, conv.created_at)) DESC
|
|
3098
3566
|
LIMIT ? OFFSET ?
|
|
3099
3567
|
`).all(topicId, limit, offset);
|
|
3568
|
+
const convs = rawConvs
|
|
3569
|
+
.map((row) => hydrateConversationRow(row))
|
|
3570
|
+
.filter((row) => !!row);
|
|
3100
3571
|
// No fallback — topics with no segments have no conversations yet.
|
|
3101
3572
|
// Backfill/import must create topic_segment rows explicitly.
|
|
3102
3573
|
return convs.map(conv => {
|
|
3574
|
+
const latestJob = getLatestConversationChatJob(conv.id);
|
|
3575
|
+
const jobStatus = latestJob?.status === 'running'
|
|
3576
|
+
? 'working'
|
|
3577
|
+
: latestJob?.status === 'completed'
|
|
3578
|
+
? 'done'
|
|
3579
|
+
: latestJob?.status ?? null;
|
|
3103
3580
|
// Get latest summary text
|
|
3104
3581
|
const summary = db.prepare('SELECT summary_text FROM conversation_summary WHERE conversation_id = ? ORDER BY turn_range_end DESC LIMIT 1').get(conv.id);
|
|
3105
3582
|
// Get first user message, truncated to 200 chars
|
|
@@ -3108,6 +3585,7 @@ function listTopicConversationsWithPreviews(topicId, opts) {
|
|
|
3108
3585
|
...conv,
|
|
3109
3586
|
summary_text: summary?.summary_text ?? null,
|
|
3110
3587
|
first_user_message: firstMsg ? firstMsg.content.slice(0, 200) : null,
|
|
3588
|
+
job_status: jobStatus,
|
|
3111
3589
|
};
|
|
3112
3590
|
});
|
|
3113
3591
|
}
|
|
@@ -3159,6 +3637,10 @@ function updateWorkflowTemplate(id, fields) {
|
|
|
3159
3637
|
const db = getDb();
|
|
3160
3638
|
const sets = [];
|
|
3161
3639
|
const vals = [];
|
|
3640
|
+
if (fields.projectId !== undefined) {
|
|
3641
|
+
sets.push('project_id = ?');
|
|
3642
|
+
vals.push(fields.projectId);
|
|
3643
|
+
}
|
|
3162
3644
|
if (fields.name !== undefined) {
|
|
3163
3645
|
sets.push('name = ?');
|
|
3164
3646
|
vals.push(fields.name);
|