funolio-agent 1.0.7 → 1.0.48
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/agent-config.d.ts +9 -1
- package/dist/agent-config.d.ts.map +1 -1
- package/dist/agent-config.js +4 -1
- package/dist/agent-config.js.map +1 -1
- 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/auto-organizer.d.ts.map +1 -1
- package/dist/auto-organizer.js +4 -3
- package/dist/auto-organizer.js.map +1 -1
- package/dist/backfill.d.ts.map +1 -1
- package/dist/backfill.js +34 -30
- package/dist/backfill.js.map +1 -1
- package/dist/bot-manager.d.ts +4 -8
- package/dist/bot-manager.d.ts.map +1 -1
- package/dist/bot-manager.js +31 -160
- package/dist/bot-manager.js.map +1 -1
- package/dist/clerk-model.d.ts +15 -7
- package/dist/clerk-model.d.ts.map +1 -1
- package/dist/clerk-model.js +78 -43
- package/dist/clerk-model.js.map +1 -1
- package/dist/cli-session-epoch.d.ts +10 -0
- package/dist/cli-session-epoch.d.ts.map +1 -0
- package/dist/cli-session-epoch.js +61 -0
- package/dist/cli-session-epoch.js.map +1 -0
- 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/init.d.ts.map +1 -1
- package/dist/commands/init.js +30 -1
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/pool.js +1 -1
- package/dist/commands/pool.js.map +1 -1
- package/dist/commands/setup.d.ts +37 -0
- package/dist/commands/setup.d.ts.map +1 -1
- package/dist/commands/setup.js +146 -43
- package/dist/commands/setup.js.map +1 -1
- package/dist/commands/start.d.ts.map +1 -1
- package/dist/commands/start.js +117 -255
- package/dist/commands/start.js.map +1 -1
- package/dist/config-cleanup.d.ts.map +1 -1
- package/dist/config-cleanup.js +2 -1
- package/dist/config-cleanup.js.map +1 -1
- package/dist/config.d.ts +6 -9
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +7 -18
- package/dist/config.js.map +1 -1
- package/dist/context-window.d.ts +33 -5
- package/dist/context-window.d.ts.map +1 -1
- package/dist/context-window.js +122 -21
- package/dist/context-window.js.map +1 -1
- package/dist/eval/orchestrator-front-door-replay.js +1 -1
- package/dist/eval/orchestrator-front-door-replay.js.map +1 -1
- package/dist/eval/policy-detection-replay.js +1 -1
- package/dist/eval/policy-detection-replay.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/integration-tokens.d.ts +1 -6
- package/dist/integration-tokens.d.ts.map +1 -1
- package/dist/integration-tokens.js +38 -40
- package/dist/integration-tokens.js.map +1 -1
- package/dist/local-cli-pty-manager.d.ts +50 -0
- package/dist/local-cli-pty-manager.d.ts.map +1 -0
- package/dist/local-cli-pty-manager.js +645 -0
- package/dist/local-cli-pty-manager.js.map +1 -0
- package/dist/local-data.d.ts +89 -6
- package/dist/local-data.d.ts.map +1 -1
- package/dist/local-data.js +600 -63
- package/dist/local-data.js.map +1 -1
- package/dist/local-db.d.ts.map +1 -1
- package/dist/local-db.js +74 -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 +22 -30
- 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 +1 -0
- package/dist/local-memory-search.d.ts.map +1 -1
- package/dist/local-memory-search.js +107 -21
- package/dist/local-memory-search.js.map +1 -1
- package/dist/local-server.d.ts +21 -0
- package/dist/local-server.d.ts.map +1 -1
- package/dist/local-server.js +1057 -501
- package/dist/local-server.js.map +1 -1
- package/dist/mcp/bridge-server.d.ts.map +1 -1
- package/dist/mcp/bridge-server.js +2 -1
- package/dist/mcp/bridge-server.js.map +1 -1
- package/dist/mcp/local-memory-server.d.ts +6 -1
- package/dist/mcp/local-memory-server.d.ts.map +1 -1
- package/dist/mcp/local-memory-server.js +38 -13
- package/dist/mcp/local-memory-server.js.map +1 -1
- package/dist/mcp/manager.d.ts +3 -22
- package/dist/mcp/manager.d.ts.map +1 -1
- package/dist/mcp/manager.js +66 -320
- package/dist/mcp/manager.js.map +1 -1
- package/dist/memory-extraction.d.ts +2 -0
- package/dist/memory-extraction.d.ts.map +1 -1
- package/dist/memory-extraction.js +3 -1
- package/dist/memory-extraction.js.map +1 -1
- package/dist/message-loop.d.ts +1 -3
- package/dist/message-loop.d.ts.map +1 -1
- package/dist/message-loop.js +220 -437
- package/dist/message-loop.js.map +1 -1
- package/dist/mqtt-client.d.ts +2 -28
- package/dist/mqtt-client.d.ts.map +1 -1
- package/dist/mqtt-client.js +2 -2
- package/dist/mqtt-client.js.map +1 -1
- package/dist/oauth.d.ts +6 -0
- package/dist/oauth.d.ts.map +1 -1
- package/dist/oauth.js +91 -0
- package/dist/oauth.js.map +1 -1
- package/dist/orchestration/front-door-policy.d.ts +5 -2
- package/dist/orchestration/front-door-policy.d.ts.map +1 -1
- package/dist/orchestration/front-door-policy.js +25 -28
- package/dist/orchestration/front-door-policy.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 +12 -1
- 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 +9 -7
- package/dist/orchestration/orchestrator-final-response-prompt.js.map +1 -1
- package/dist/orchestration/orchestrator-operating-prompt.d.ts +11 -0
- package/dist/orchestration/orchestrator-operating-prompt.d.ts.map +1 -1
- package/dist/orchestration/orchestrator-operating-prompt.js +67 -44
- package/dist/orchestration/orchestrator-operating-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 +41 -2
- package/dist/orchestration/worker-operating-prompt.js.map +1 -1
- package/dist/orchestrator.d.ts +17 -0
- package/dist/orchestrator.d.ts.map +1 -1
- package/dist/orchestrator.js +328 -166
- package/dist/orchestrator.js.map +1 -1
- package/dist/prompt-template.js +3 -3
- package/dist/prompt-template.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-prompt.d.ts.map +1 -1
- package/dist/providers/claude-cli-prompt.js +22 -6
- package/dist/providers/claude-cli-prompt.js.map +1 -1
- package/dist/providers/claude-cli.d.ts.map +1 -1
- package/dist/providers/claude-cli.js +36 -142
- 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 +148 -74
- 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 +13 -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/runtime-context.d.ts +10 -0
- package/dist/runtime-context.d.ts.map +1 -0
- package/dist/runtime-context.js +30 -0
- package/dist/runtime-context.js.map +1 -0
- 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/subagent/queue.d.ts.map +1 -1
- package/dist/subagent/queue.js +1 -0
- package/dist/subagent/queue.js.map +1 -1
- package/dist/summarization-pipeline.d.ts +10 -0
- package/dist/summarization-pipeline.d.ts.map +1 -1
- package/dist/summarization-pipeline.js +147 -34
- package/dist/summarization-pipeline.js.map +1 -1
- package/dist/tool-permissions.d.ts +2 -0
- package/dist/tool-permissions.d.ts.map +1 -0
- package/dist/tool-permissions.js +25 -0
- package/dist/tool-permissions.js.map +1 -0
- 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 +7 -8
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +106 -60
- 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/search-memory.d.ts.map +1 -1
- package/dist/tools/search-memory.js +9 -3
- package/dist/tools/search-memory.js.map +1 -1
- package/dist/tools/spawn-subagent.d.ts.map +1 -1
- package/dist/tools/spawn-subagent.js +1 -0
- 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 +5 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +0 -3
- package/dist/types.js.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 +88 -99
- package/dist/wizard-support.js.map +1 -1
- package/dist/workflow-engine.d.ts +9 -3
- package/dist/workflow-engine.d.ts.map +1 -1
- package/dist/workflow-engine.js +378 -82
- package/dist/workflow-engine.js.map +1 -1
- package/package.json +2 -1
package/dist/local-data.js
CHANGED
|
@@ -58,15 +58,30 @@ 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.getCliSessionEpoch = getCliSessionEpoch;
|
|
70
|
+
exports.upsertCliSessionEpoch = upsertCliSessionEpoch;
|
|
71
|
+
exports.deleteCliSessionEpoch = deleteCliSessionEpoch;
|
|
72
|
+
exports.getChatJob = getChatJob;
|
|
73
|
+
exports.getLatestConversationChatJob = getLatestConversationChatJob;
|
|
74
|
+
exports.listQueuedChatJobs = listQueuedChatJobs;
|
|
75
|
+
exports.countRunningChatJobs = countRunningChatJobs;
|
|
76
|
+
exports.updateChatJob = updateChatJob;
|
|
77
|
+
exports.markRunningChatJobsInterrupted = markRunningChatJobsInterrupted;
|
|
64
78
|
exports.createMessageActivity = createMessageActivity;
|
|
65
79
|
exports.attachMessageActivitiesToMessage = attachMessageActivitiesToMessage;
|
|
66
80
|
exports.listMessageActivities = listMessageActivities;
|
|
67
81
|
exports.deleteExpiredMessageActivities = deleteExpiredMessageActivities;
|
|
68
82
|
exports.getMessage = getMessage;
|
|
69
83
|
exports.getMessages = getMessages;
|
|
84
|
+
exports.getMessagesInRange = getMessagesInRange;
|
|
70
85
|
exports.getLastMessages = getLastMessages;
|
|
71
86
|
exports.getRecentMessageRounds = getRecentMessageRounds;
|
|
72
87
|
exports.getMessageRoundsBefore = getMessageRoundsBefore;
|
|
@@ -104,6 +119,7 @@ exports.setSetting = setSetting;
|
|
|
104
119
|
exports.setJsonSetting = setJsonSetting;
|
|
105
120
|
exports.getAllSettings = getAllSettings;
|
|
106
121
|
exports.deleteSetting = deleteSetting;
|
|
122
|
+
exports.purgeLegacyExtractionDataOnce = purgeLegacyExtractionDataOnce;
|
|
107
123
|
exports.defaultOrchestrationPolicy = defaultOrchestrationPolicy;
|
|
108
124
|
exports.resolveCurrentUserIdentity = resolveCurrentUserIdentity;
|
|
109
125
|
exports.getUserOrchestrationPolicy = getUserOrchestrationPolicy;
|
|
@@ -211,6 +227,7 @@ const memory_extraction_1 = require("./memory-extraction");
|
|
|
211
227
|
const orchestrator_blocked_prompt_1 = require("./orchestration/orchestrator-blocked-prompt");
|
|
212
228
|
const crypto = __importStar(require("crypto"));
|
|
213
229
|
const default_tool_profile_1 = require("./default-tool-profile");
|
|
230
|
+
const approval_1 = require("./approval");
|
|
214
231
|
let _db = null;
|
|
215
232
|
/** Get or open the shared database connection */
|
|
216
233
|
function getDb() {
|
|
@@ -249,7 +266,7 @@ function createAgentProfile(p) {
|
|
|
249
266
|
if (p.isOrchestrator) {
|
|
250
267
|
db.prepare("UPDATE agent_profile SET is_orchestrator = 0, updated_at = datetime('now') WHERE is_orchestrator = 1").run();
|
|
251
268
|
}
|
|
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);
|
|
269
|
+
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
270
|
});
|
|
254
271
|
transaction();
|
|
255
272
|
return db.prepare('SELECT * FROM agent_profile WHERE id = ?').get(id);
|
|
@@ -286,7 +303,8 @@ function migrateAgentProfilesToDefaultToolProfile() {
|
|
|
286
303
|
catch {
|
|
287
304
|
tools = [];
|
|
288
305
|
}
|
|
289
|
-
const
|
|
306
|
+
const normalizedPermission = (0, approval_1.normalizePermissionMode)(row.permission_mode);
|
|
307
|
+
const needsPermission = !row.permission_mode || row.permission_mode !== normalizedPermission;
|
|
290
308
|
const needsTools = tools.length === 0;
|
|
291
309
|
if (!needsPermission && !needsTools)
|
|
292
310
|
continue;
|
|
@@ -294,7 +312,7 @@ function migrateAgentProfilesToDefaultToolProfile() {
|
|
|
294
312
|
const vals = [now()];
|
|
295
313
|
if (needsPermission) {
|
|
296
314
|
sets.push('permission_mode = ?');
|
|
297
|
-
vals.push(
|
|
315
|
+
vals.push(normalizedPermission);
|
|
298
316
|
}
|
|
299
317
|
if (needsTools) {
|
|
300
318
|
sets.push('enabled_builtin_tools_json = ?');
|
|
@@ -347,7 +365,7 @@ function updateAgentProfile(id, fields) {
|
|
|
347
365
|
}
|
|
348
366
|
if (fields.permissionMode !== undefined) {
|
|
349
367
|
sets.push('permission_mode = ?');
|
|
350
|
-
vals.push(fields.permissionMode);
|
|
368
|
+
vals.push((0, approval_1.normalizePermissionMode)(fields.permissionMode));
|
|
351
369
|
}
|
|
352
370
|
if (fields.isDefault !== undefined) {
|
|
353
371
|
sets.push('is_default = ?');
|
|
@@ -660,37 +678,207 @@ function createConversation(agentId, title, source, opts) {
|
|
|
660
678
|
`).run(id, agentId, title ?? null, source ?? 'local', projectId, projectName, ts, ts);
|
|
661
679
|
return db.prepare('SELECT * FROM conversation WHERE id = ?').get(id);
|
|
662
680
|
}
|
|
681
|
+
function latestConversationSummarySql(conversationAlias) {
|
|
682
|
+
return `(
|
|
683
|
+
SELECT cs.summary_text
|
|
684
|
+
FROM conversation_summary cs
|
|
685
|
+
WHERE cs.conversation_id = ${conversationAlias}.id
|
|
686
|
+
AND coalesce(cs.summary_kind, 'rolling') = 'rolling'
|
|
687
|
+
ORDER BY cs.turn_range_end DESC, cs.created_at DESC
|
|
688
|
+
LIMIT 1
|
|
689
|
+
)`;
|
|
690
|
+
}
|
|
691
|
+
function primaryConversationTopicTitleSql(conversationAlias) {
|
|
692
|
+
return `(
|
|
693
|
+
SELECT t.title
|
|
694
|
+
FROM topic_segment ts
|
|
695
|
+
INNER JOIN topic t ON t.id = ts.topic_id
|
|
696
|
+
WHERE ts.conversation_id = ${conversationAlias}.id
|
|
697
|
+
ORDER BY ts.created_at ASC
|
|
698
|
+
LIMIT 1
|
|
699
|
+
)`;
|
|
700
|
+
}
|
|
701
|
+
function firstConversationUserMessageSql(conversationAlias, maxLength = 120) {
|
|
702
|
+
return `(
|
|
703
|
+
SELECT substr(trim(m.content), 1, ${maxLength})
|
|
704
|
+
FROM message m
|
|
705
|
+
WHERE m.conversation_id = ${conversationAlias}.id
|
|
706
|
+
AND m.role = 'user'
|
|
707
|
+
AND trim(coalesce(m.content, '')) <> ''
|
|
708
|
+
AND trim(coalesce(m.content, '')) NOT LIKE '[System Instructions]%'
|
|
709
|
+
AND trim(coalesce(m.content, '')) NOT LIKE '# AGENTS.md%'
|
|
710
|
+
AND trim(coalesce(m.content, '')) NOT LIKE '<INSTRUCTIONS%'
|
|
711
|
+
AND trim(coalesce(m.content, '')) NOT LIKE '<environment_context%'
|
|
712
|
+
AND trim(coalesce(m.content, '')) NOT LIKE 'CURRENT TODO ID:%'
|
|
713
|
+
ORDER BY m.seq ASC
|
|
714
|
+
LIMIT 1
|
|
715
|
+
)`;
|
|
716
|
+
}
|
|
717
|
+
function firstConversationAssistantMessageSql(conversationAlias, maxLength = 120) {
|
|
718
|
+
return `(
|
|
719
|
+
SELECT substr(trim(m.content), 1, ${maxLength})
|
|
720
|
+
FROM message m
|
|
721
|
+
WHERE m.conversation_id = ${conversationAlias}.id
|
|
722
|
+
AND m.role = 'assistant'
|
|
723
|
+
AND trim(coalesce(m.content, '')) <> ''
|
|
724
|
+
ORDER BY m.seq ASC
|
|
725
|
+
LIMIT 1
|
|
726
|
+
)`;
|
|
727
|
+
}
|
|
728
|
+
function conversationSelectSql(conversationAlias) {
|
|
729
|
+
const latestSummary = latestConversationSummarySql(conversationAlias);
|
|
730
|
+
const topicTitle = primaryConversationTopicTitleSql(conversationAlias);
|
|
731
|
+
const firstUserMessage = firstConversationUserMessageSql(conversationAlias);
|
|
732
|
+
const firstAssistantMessage = firstConversationAssistantMessageSql(conversationAlias);
|
|
733
|
+
return `
|
|
734
|
+
SELECT
|
|
735
|
+
${conversationAlias}.*,
|
|
736
|
+
COALESCE(
|
|
737
|
+
CASE
|
|
738
|
+
WHEN ${conversationAlias}.title IS NOT NULL
|
|
739
|
+
AND trim(${conversationAlias}.title) <> ''
|
|
740
|
+
AND trim(${conversationAlias}.title) <> 'New Conversation'
|
|
741
|
+
AND trim(${conversationAlias}.title) NOT LIKE '[System Instructions]%'
|
|
742
|
+
AND trim(${conversationAlias}.title) NOT LIKE '# AGENTS.md%'
|
|
743
|
+
AND trim(${conversationAlias}.title) NOT LIKE '<INSTRUCTIONS%'
|
|
744
|
+
AND trim(${conversationAlias}.title) NOT LIKE '<environment_context%'
|
|
745
|
+
AND trim(${conversationAlias}.title) NOT LIKE 'CURRENT TODO ID:%'
|
|
746
|
+
THEN ${conversationAlias}.title
|
|
747
|
+
ELSE NULL
|
|
748
|
+
END,
|
|
749
|
+
${topicTitle},
|
|
750
|
+
${firstUserMessage},
|
|
751
|
+
${firstAssistantMessage},
|
|
752
|
+
${conversationAlias}.title
|
|
753
|
+
) AS resolved_title,
|
|
754
|
+
COALESCE(${latestSummary}, ${conversationAlias}.summary) AS resolved_summary,
|
|
755
|
+
CASE
|
|
756
|
+
WHEN ${conversationAlias}.source = 'import'
|
|
757
|
+
AND ${conversationAlias}.conversation_started_at IS NOT NULL
|
|
758
|
+
THEN ${conversationAlias}.conversation_started_at
|
|
759
|
+
ELSE ${conversationAlias}.created_at
|
|
760
|
+
END AS effective_created_at,
|
|
761
|
+
CASE
|
|
762
|
+
WHEN ${conversationAlias}.source = 'import'
|
|
763
|
+
AND ${conversationAlias}.conversation_ended_at IS NOT NULL
|
|
764
|
+
THEN ${conversationAlias}.conversation_ended_at
|
|
765
|
+
ELSE ${conversationAlias}.updated_at
|
|
766
|
+
END AS effective_updated_at
|
|
767
|
+
FROM conversation ${conversationAlias}
|
|
768
|
+
`;
|
|
769
|
+
}
|
|
770
|
+
function hydrateConversationRow(row) {
|
|
771
|
+
if (!row)
|
|
772
|
+
return undefined;
|
|
773
|
+
return {
|
|
774
|
+
...row,
|
|
775
|
+
title: row.resolved_title ?? row.title,
|
|
776
|
+
summary: row.resolved_summary ?? row.summary,
|
|
777
|
+
created_at: row.effective_created_at ?? row.created_at,
|
|
778
|
+
updated_at: row.effective_updated_at ?? row.updated_at,
|
|
779
|
+
};
|
|
780
|
+
}
|
|
663
781
|
function getConversation(id) {
|
|
664
|
-
return getDb().prepare('
|
|
782
|
+
return hydrateConversationRow(getDb().prepare(`${conversationSelectSql('c')} WHERE c.id = ?`).get(id));
|
|
665
783
|
}
|
|
666
784
|
function listConversations(opts) {
|
|
667
785
|
const db = getDb();
|
|
668
786
|
const limit = opts?.limit ?? 50;
|
|
669
787
|
const offset = opts?.offset ?? 0;
|
|
670
|
-
let sql = '
|
|
788
|
+
let sql = conversationSelectSql('c');
|
|
671
789
|
const params = [];
|
|
672
790
|
const wheres = [];
|
|
673
791
|
if (opts?.agentId) {
|
|
674
|
-
wheres.push(
|
|
675
|
-
|
|
792
|
+
wheres.push(`(
|
|
793
|
+
c.agent_id = ?
|
|
794
|
+
OR EXISTS (
|
|
795
|
+
SELECT 1
|
|
796
|
+
FROM message m
|
|
797
|
+
WHERE m.conversation_id = c.id
|
|
798
|
+
AND m.bot_id = ?
|
|
799
|
+
)
|
|
800
|
+
)`);
|
|
801
|
+
params.push(opts.agentId, opts.agentId);
|
|
676
802
|
}
|
|
677
803
|
if (opts?.search) {
|
|
678
|
-
wheres.push(
|
|
679
|
-
|
|
804
|
+
wheres.push(`(
|
|
805
|
+
coalesce(c.title, '') LIKE ?
|
|
806
|
+
OR coalesce(c.summary, '') LIKE ?
|
|
807
|
+
OR coalesce(${latestConversationSummarySql('c')}, '') LIKE ?
|
|
808
|
+
OR coalesce(${primaryConversationTopicTitleSql('c')}, '') LIKE ?
|
|
809
|
+
OR coalesce(c.project_name, '') LIKE ?
|
|
810
|
+
OR coalesce(c.description, '') LIKE ?
|
|
811
|
+
OR coalesce(c.tags, '') LIKE ?
|
|
812
|
+
OR coalesce(c.topics_discussed, '') LIKE ?
|
|
813
|
+
OR coalesce(c.tech_stack, '') LIKE ?
|
|
814
|
+
OR coalesce(c.category, '') LIKE ?
|
|
815
|
+
)`);
|
|
816
|
+
const needle = `%${opts.search}%`;
|
|
817
|
+
params.push(needle, needle, needle, needle, needle, needle, needle, needle, needle, needle);
|
|
680
818
|
}
|
|
681
819
|
if (wheres.length)
|
|
682
820
|
sql += ' WHERE ' + wheres.join(' AND ');
|
|
683
|
-
sql += ' ORDER BY updated_at DESC LIMIT ? OFFSET ?';
|
|
821
|
+
sql += ' ORDER BY c.updated_at DESC LIMIT ? OFFSET ?';
|
|
684
822
|
params.push(limit, offset);
|
|
685
|
-
return db.prepare(sql).all(...params);
|
|
823
|
+
return db.prepare(sql).all(...params).map((row) => hydrateConversationRow(row));
|
|
686
824
|
}
|
|
687
825
|
function countConversations(agentId) {
|
|
688
826
|
const db = getDb();
|
|
689
827
|
if (agentId) {
|
|
690
|
-
return db.prepare(
|
|
828
|
+
return db.prepare(`
|
|
829
|
+
SELECT count(DISTINCT c.id) as cnt
|
|
830
|
+
FROM conversation c
|
|
831
|
+
WHERE c.agent_id = ?
|
|
832
|
+
OR EXISTS (
|
|
833
|
+
SELECT 1
|
|
834
|
+
FROM message m
|
|
835
|
+
WHERE m.conversation_id = c.id
|
|
836
|
+
AND m.bot_id = ?
|
|
837
|
+
)
|
|
838
|
+
`).get(agentId, agentId).cnt;
|
|
691
839
|
}
|
|
692
840
|
return db.prepare('SELECT count(*) as cnt FROM conversation').get().cnt;
|
|
693
841
|
}
|
|
842
|
+
function countMessagesForBot(botId) {
|
|
843
|
+
return getDb().prepare(`
|
|
844
|
+
SELECT count(*) as cnt
|
|
845
|
+
FROM message
|
|
846
|
+
WHERE bot_id = ?
|
|
847
|
+
`).get(botId).cnt;
|
|
848
|
+
}
|
|
849
|
+
function listBotConversationActivity(botId, limit = 10) {
|
|
850
|
+
const db = getDb();
|
|
851
|
+
return db.prepare(`
|
|
852
|
+
SELECT
|
|
853
|
+
conv.*,
|
|
854
|
+
COALESCE((
|
|
855
|
+
SELECT count(*)
|
|
856
|
+
FROM message bm
|
|
857
|
+
WHERE bm.conversation_id = conv.id
|
|
858
|
+
AND bm.bot_id = ?
|
|
859
|
+
), 0) AS bot_message_count,
|
|
860
|
+
(
|
|
861
|
+
SELECT max(bm.created_at)
|
|
862
|
+
FROM message bm
|
|
863
|
+
WHERE bm.conversation_id = conv.id
|
|
864
|
+
AND bm.bot_id = ?
|
|
865
|
+
) AS bot_last_message_at
|
|
866
|
+
FROM (
|
|
867
|
+
${conversationSelectSql('c')}
|
|
868
|
+
WHERE (
|
|
869
|
+
c.agent_id = ?
|
|
870
|
+
OR EXISTS (
|
|
871
|
+
SELECT 1
|
|
872
|
+
FROM message m
|
|
873
|
+
WHERE m.conversation_id = c.id
|
|
874
|
+
AND m.bot_id = ?
|
|
875
|
+
)
|
|
876
|
+
)
|
|
877
|
+
) conv
|
|
878
|
+
ORDER BY COALESCE(bot_last_message_at, conv.updated_at, conv.created_at) DESC
|
|
879
|
+
LIMIT ?
|
|
880
|
+
`).all(botId, botId, botId, botId, limit).map((row) => hydrateConversationRow(row));
|
|
881
|
+
}
|
|
694
882
|
function updateConversation(id, fields) {
|
|
695
883
|
const db = getDb();
|
|
696
884
|
const sets = [];
|
|
@@ -731,6 +919,8 @@ function updateConversation(id, fields) {
|
|
|
731
919
|
processingError: ['processing_error', fields.processingError],
|
|
732
920
|
botId: ['bot_id', fields.botId],
|
|
733
921
|
forkedFromId: ['forked_from_id', fields.forkedFromId],
|
|
922
|
+
createdAt: ['created_at', fields.createdAt],
|
|
923
|
+
updatedAt: ['updated_at', fields.updatedAt],
|
|
734
924
|
};
|
|
735
925
|
for (const [key, [col, val]] of Object.entries(map)) {
|
|
736
926
|
if (fields[key] !== undefined) {
|
|
@@ -748,17 +938,30 @@ function updateConversation(id, fields) {
|
|
|
748
938
|
}
|
|
749
939
|
if (sets.length === 0)
|
|
750
940
|
return;
|
|
751
|
-
|
|
941
|
+
if (fields.updatedAt === undefined) {
|
|
942
|
+
sets.push("updated_at = datetime('now')");
|
|
943
|
+
}
|
|
752
944
|
vals.push(id);
|
|
753
945
|
db.prepare(`UPDATE conversation SET ${sets.join(', ')} WHERE id = ?`).run(...vals);
|
|
754
946
|
}
|
|
947
|
+
function touchConversationActivity(id, ts) {
|
|
948
|
+
const stamp = ts?.trim() || now();
|
|
949
|
+
getDb().prepare(`
|
|
950
|
+
UPDATE conversation
|
|
951
|
+
SET updated_at = CASE
|
|
952
|
+
WHEN datetime(updated_at) > datetime(?) THEN updated_at
|
|
953
|
+
ELSE ?
|
|
954
|
+
END
|
|
955
|
+
WHERE id = ?
|
|
956
|
+
`).run(stamp, stamp, id);
|
|
957
|
+
}
|
|
755
958
|
function deleteConversation(id) {
|
|
756
959
|
return getDb().prepare('DELETE FROM conversation WHERE id = ?').run(id).changes > 0;
|
|
757
960
|
}
|
|
758
|
-
function addMessage(conversationId, role, content, model, toolCallsJson, botId, agentName) {
|
|
961
|
+
function addMessage(conversationId, role, content, model, toolCallsJson, botId, agentName, createdAt) {
|
|
759
962
|
const db = getDb();
|
|
760
963
|
const id = newId();
|
|
761
|
-
const ts = now();
|
|
964
|
+
const ts = createdAt?.trim() || now();
|
|
762
965
|
// Get next sequence number
|
|
763
966
|
const maxSeq = db.prepare('SELECT COALESCE(MAX(seq), 0) as m FROM message WHERE conversation_id = ?').get(conversationId);
|
|
764
967
|
const seq = maxSeq.m + 1;
|
|
@@ -767,9 +970,197 @@ function addMessage(conversationId, role, content, model, toolCallsJson, botId,
|
|
|
767
970
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
768
971
|
`).run(id, conversationId, seq, role, content, model ?? null, toolCallsJson ?? null, botId ?? null, agentName ?? null, ts);
|
|
769
972
|
// Update conversation message_count and updated_at
|
|
770
|
-
db.prepare(
|
|
973
|
+
db.prepare(`
|
|
974
|
+
UPDATE conversation
|
|
975
|
+
SET message_count = message_count + 1,
|
|
976
|
+
updated_at = CASE
|
|
977
|
+
WHEN datetime(updated_at) > datetime(?) THEN updated_at
|
|
978
|
+
ELSE ?
|
|
979
|
+
END
|
|
980
|
+
WHERE id = ?
|
|
981
|
+
`).run(ts, ts, conversationId);
|
|
982
|
+
return db.prepare('SELECT * FROM message WHERE id = ?').get(id);
|
|
983
|
+
}
|
|
984
|
+
function updateMessage(id, fields) {
|
|
985
|
+
const db = getDb();
|
|
986
|
+
const row = db.prepare('SELECT * FROM message WHERE id = ?').get(id);
|
|
987
|
+
if (!row)
|
|
988
|
+
return undefined;
|
|
989
|
+
const sets = [];
|
|
990
|
+
const vals = [];
|
|
991
|
+
if (fields.content !== undefined) {
|
|
992
|
+
sets.push('content = ?');
|
|
993
|
+
vals.push(fields.content);
|
|
994
|
+
}
|
|
995
|
+
if (fields.model !== undefined) {
|
|
996
|
+
sets.push('model = ?');
|
|
997
|
+
vals.push(fields.model);
|
|
998
|
+
}
|
|
999
|
+
if (fields.toolCallsJson !== undefined) {
|
|
1000
|
+
sets.push('tool_calls_json = ?');
|
|
1001
|
+
vals.push(fields.toolCallsJson);
|
|
1002
|
+
}
|
|
1003
|
+
if (fields.botId !== undefined) {
|
|
1004
|
+
sets.push('bot_id = ?');
|
|
1005
|
+
vals.push(fields.botId);
|
|
1006
|
+
}
|
|
1007
|
+
if (fields.agentName !== undefined) {
|
|
1008
|
+
sets.push('agent_name = ?');
|
|
1009
|
+
vals.push(fields.agentName);
|
|
1010
|
+
}
|
|
1011
|
+
if (fields.resultArtifact !== undefined) {
|
|
1012
|
+
sets.push('result_artifact = ?');
|
|
1013
|
+
vals.push(fields.resultArtifact);
|
|
1014
|
+
}
|
|
1015
|
+
if (fields.resultSummary !== undefined) {
|
|
1016
|
+
sets.push('result_summary = ?');
|
|
1017
|
+
vals.push(fields.resultSummary);
|
|
1018
|
+
}
|
|
1019
|
+
if (fields.resultStatus !== undefined) {
|
|
1020
|
+
sets.push('result_status = ?');
|
|
1021
|
+
vals.push(fields.resultStatus);
|
|
1022
|
+
}
|
|
1023
|
+
if (sets.length === 0)
|
|
1024
|
+
return row;
|
|
1025
|
+
vals.push(id);
|
|
1026
|
+
db.prepare(`UPDATE message SET ${sets.join(', ')} WHERE id = ?`).run(...vals);
|
|
771
1027
|
return db.prepare('SELECT * FROM message WHERE id = ?').get(id);
|
|
772
1028
|
}
|
|
1029
|
+
function createChatJob(input) {
|
|
1030
|
+
const db = getDb();
|
|
1031
|
+
const id = newId();
|
|
1032
|
+
const ts = now();
|
|
1033
|
+
db.prepare(`
|
|
1034
|
+
INSERT INTO chat_job (
|
|
1035
|
+
id, conversation_id, user_message_id, assistant_message_id, bot_id,
|
|
1036
|
+
status, request_json, created_at, updated_at
|
|
1037
|
+
)
|
|
1038
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1039
|
+
`).run(id, input.conversationId, input.userMessageId, input.assistantMessageId, input.botId, input.status ?? 'queued', input.requestJson ?? null, ts, ts);
|
|
1040
|
+
return db.prepare('SELECT * FROM chat_job WHERE id = ?').get(id);
|
|
1041
|
+
}
|
|
1042
|
+
function getCliSessionEpoch(conversationId, botId) {
|
|
1043
|
+
return getDb().prepare(`
|
|
1044
|
+
SELECT *
|
|
1045
|
+
FROM cli_session_epoch
|
|
1046
|
+
WHERE conversation_id = ? AND bot_id = ?
|
|
1047
|
+
LIMIT 1
|
|
1048
|
+
`).get(conversationId, botId);
|
|
1049
|
+
}
|
|
1050
|
+
function upsertCliSessionEpoch(input) {
|
|
1051
|
+
const db = getDb();
|
|
1052
|
+
const existing = getCliSessionEpoch(input.conversationId, input.botId);
|
|
1053
|
+
const id = existing?.id || newId();
|
|
1054
|
+
const epochStartedAt = input.epochStartedAt || existing?.epoch_started_at || now();
|
|
1055
|
+
const lastUsedAt = input.lastUsedAt || now();
|
|
1056
|
+
db.prepare(`
|
|
1057
|
+
INSERT INTO cli_session_epoch (
|
|
1058
|
+
id, conversation_id, bot_id, provider, session_id, epoch_turn_count,
|
|
1059
|
+
last_input_tokens, last_output_tokens, epoch_started_at, last_used_at,
|
|
1060
|
+
reset_reason, created_at, updated_at
|
|
1061
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1062
|
+
ON CONFLICT(conversation_id, bot_id) DO UPDATE SET
|
|
1063
|
+
provider = excluded.provider,
|
|
1064
|
+
session_id = excluded.session_id,
|
|
1065
|
+
epoch_turn_count = excluded.epoch_turn_count,
|
|
1066
|
+
last_input_tokens = excluded.last_input_tokens,
|
|
1067
|
+
last_output_tokens = excluded.last_output_tokens,
|
|
1068
|
+
epoch_started_at = excluded.epoch_started_at,
|
|
1069
|
+
last_used_at = excluded.last_used_at,
|
|
1070
|
+
reset_reason = excluded.reset_reason,
|
|
1071
|
+
updated_at = excluded.updated_at
|
|
1072
|
+
`).run(id, input.conversationId, input.botId, input.provider, input.sessionId, input.epochTurnCount, input.lastInputTokens ?? null, input.lastOutputTokens ?? null, epochStartedAt, lastUsedAt, input.resetReason ?? null, existing?.created_at || now(), now());
|
|
1073
|
+
return getCliSessionEpoch(input.conversationId, input.botId);
|
|
1074
|
+
}
|
|
1075
|
+
function deleteCliSessionEpoch(conversationId, botId) {
|
|
1076
|
+
return getDb().prepare(`
|
|
1077
|
+
DELETE FROM cli_session_epoch
|
|
1078
|
+
WHERE conversation_id = ? AND bot_id = ?
|
|
1079
|
+
`).run(conversationId, botId).changes > 0;
|
|
1080
|
+
}
|
|
1081
|
+
function getChatJob(id) {
|
|
1082
|
+
return getDb().prepare('SELECT * FROM chat_job WHERE id = ?').get(id);
|
|
1083
|
+
}
|
|
1084
|
+
function getLatestConversationChatJob(conversationId) {
|
|
1085
|
+
return getDb().prepare(`
|
|
1086
|
+
SELECT *
|
|
1087
|
+
FROM chat_job
|
|
1088
|
+
WHERE conversation_id = ?
|
|
1089
|
+
ORDER BY datetime(created_at) DESC, rowid DESC
|
|
1090
|
+
LIMIT 1
|
|
1091
|
+
`).get(conversationId);
|
|
1092
|
+
}
|
|
1093
|
+
function listQueuedChatJobs(limit = 50) {
|
|
1094
|
+
return getDb().prepare(`
|
|
1095
|
+
SELECT *
|
|
1096
|
+
FROM chat_job
|
|
1097
|
+
WHERE status = 'queued'
|
|
1098
|
+
ORDER BY datetime(created_at) ASC, rowid ASC
|
|
1099
|
+
LIMIT ?
|
|
1100
|
+
`).all(limit);
|
|
1101
|
+
}
|
|
1102
|
+
function countRunningChatJobs() {
|
|
1103
|
+
return getDb().prepare(`
|
|
1104
|
+
SELECT count(*) AS cnt
|
|
1105
|
+
FROM chat_job
|
|
1106
|
+
WHERE status = 'running'
|
|
1107
|
+
`).get().cnt;
|
|
1108
|
+
}
|
|
1109
|
+
function updateChatJob(id, fields) {
|
|
1110
|
+
const db = getDb();
|
|
1111
|
+
const row = db.prepare('SELECT * FROM chat_job WHERE id = ?').get(id);
|
|
1112
|
+
if (!row)
|
|
1113
|
+
return undefined;
|
|
1114
|
+
const sets = [];
|
|
1115
|
+
const vals = [];
|
|
1116
|
+
if (fields.status !== undefined) {
|
|
1117
|
+
sets.push('status = ?');
|
|
1118
|
+
vals.push(fields.status);
|
|
1119
|
+
}
|
|
1120
|
+
if (fields.error !== undefined) {
|
|
1121
|
+
sets.push('error = ?');
|
|
1122
|
+
vals.push(fields.error);
|
|
1123
|
+
}
|
|
1124
|
+
if (fields.requestJson !== undefined) {
|
|
1125
|
+
sets.push('request_json = ?');
|
|
1126
|
+
vals.push(fields.requestJson);
|
|
1127
|
+
}
|
|
1128
|
+
if (fields.startedAt !== undefined) {
|
|
1129
|
+
sets.push('started_at = ?');
|
|
1130
|
+
vals.push(fields.startedAt);
|
|
1131
|
+
}
|
|
1132
|
+
if (fields.completedAt !== undefined) {
|
|
1133
|
+
sets.push('completed_at = ?');
|
|
1134
|
+
vals.push(fields.completedAt);
|
|
1135
|
+
}
|
|
1136
|
+
if (fields.cancelledAt !== undefined) {
|
|
1137
|
+
sets.push('cancelled_at = ?');
|
|
1138
|
+
vals.push(fields.cancelledAt);
|
|
1139
|
+
}
|
|
1140
|
+
if (sets.length === 0)
|
|
1141
|
+
return row;
|
|
1142
|
+
sets.push('updated_at = ?');
|
|
1143
|
+
vals.push(now());
|
|
1144
|
+
vals.push(id);
|
|
1145
|
+
db.prepare(`UPDATE chat_job SET ${sets.join(', ')} WHERE id = ?`).run(...vals);
|
|
1146
|
+
return db.prepare('SELECT * FROM chat_job WHERE id = ?').get(id);
|
|
1147
|
+
}
|
|
1148
|
+
function markRunningChatJobsInterrupted(reason = 'Interrupted when app closed') {
|
|
1149
|
+
const db = getDb();
|
|
1150
|
+
const ts = now();
|
|
1151
|
+
const result = db.prepare(`
|
|
1152
|
+
UPDATE chat_job
|
|
1153
|
+
SET status = 'failed',
|
|
1154
|
+
error = CASE
|
|
1155
|
+
WHEN error IS NULL OR trim(error) = '' THEN ?
|
|
1156
|
+
ELSE error
|
|
1157
|
+
END,
|
|
1158
|
+
completed_at = COALESCE(completed_at, ?),
|
|
1159
|
+
updated_at = ?
|
|
1160
|
+
WHERE status = 'running'
|
|
1161
|
+
`).run(reason, ts, ts);
|
|
1162
|
+
return result.changes;
|
|
1163
|
+
}
|
|
773
1164
|
function createMessageActivity(activity) {
|
|
774
1165
|
const db = getDb();
|
|
775
1166
|
const id = newId();
|
|
@@ -829,6 +1220,12 @@ function getMessages(conversationId, opts) {
|
|
|
829
1220
|
return db.prepare('SELECT * FROM message WHERE conversation_id = ? ORDER BY seq ASC LIMIT ? OFFSET ?')
|
|
830
1221
|
.all(conversationId, limit, offset);
|
|
831
1222
|
}
|
|
1223
|
+
function getMessagesInRange(conversationId, startSeq, endSeq) {
|
|
1224
|
+
const db = getDb();
|
|
1225
|
+
const start = Math.max(1, Math.floor(startSeq));
|
|
1226
|
+
const end = Math.max(start, Math.floor(endSeq));
|
|
1227
|
+
return db.prepare('SELECT * FROM message WHERE conversation_id = ? AND seq >= ? AND seq <= ? ORDER BY seq ASC').all(conversationId, start, end);
|
|
1228
|
+
}
|
|
832
1229
|
function getLastMessages(conversationId, count) {
|
|
833
1230
|
const db = getDb();
|
|
834
1231
|
return db.prepare(`
|
|
@@ -1198,11 +1595,6 @@ function listMemoryFacts(opts) {
|
|
|
1198
1595
|
function deleteMemoryFact(id) {
|
|
1199
1596
|
return getDb().prepare('DELETE FROM memory_fact WHERE id = ?').run(id).changes > 0;
|
|
1200
1597
|
}
|
|
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
1598
|
function getUserProfileFacts(agentId, limit = 15) {
|
|
1207
1599
|
const db = getDb();
|
|
1208
1600
|
return db.prepare(`
|
|
@@ -1398,6 +1790,19 @@ function getAllSettings() {
|
|
|
1398
1790
|
function deleteSetting(key) {
|
|
1399
1791
|
return getDb().prepare('DELETE FROM settings WHERE key = ?').run(key).changes > 0;
|
|
1400
1792
|
}
|
|
1793
|
+
function purgeLegacyExtractionDataOnce() {
|
|
1794
|
+
if (getSetting('legacy_extraction_cleanup_v1') === 'done')
|
|
1795
|
+
return false;
|
|
1796
|
+
const db = getDb();
|
|
1797
|
+
const tx = db.transaction(() => {
|
|
1798
|
+
for (const table of ['entity_relationship', 'entity', 'memory_fact', 'decision', 'action_item']) {
|
|
1799
|
+
db.prepare(`DELETE FROM ${table}`).run();
|
|
1800
|
+
}
|
|
1801
|
+
setSetting('legacy_extraction_cleanup_v1', 'done');
|
|
1802
|
+
});
|
|
1803
|
+
tx();
|
|
1804
|
+
return true;
|
|
1805
|
+
}
|
|
1401
1806
|
const AUTH_SESSION_KEY = 'auth.session';
|
|
1402
1807
|
function defaultOrchestrationPolicy() {
|
|
1403
1808
|
return {
|
|
@@ -1852,6 +2257,36 @@ function inferTaskTypeForAgent(agent, requested) {
|
|
|
1852
2257
|
return explicit;
|
|
1853
2258
|
return roleClassToTaskType(agent?.role_class) || roleClassToTaskType(agent?.role_label) || null;
|
|
1854
2259
|
}
|
|
2260
|
+
function buildQueuedTaskContext(task) {
|
|
2261
|
+
const lines = [
|
|
2262
|
+
'NEXT TASK CONTEXT',
|
|
2263
|
+
`Title: ${task.title}`,
|
|
2264
|
+
task.task_type ? `Task type: ${task.task_type}` : null,
|
|
2265
|
+
task.details?.trim() ? `Details: ${task.details.trim()}` : null,
|
|
2266
|
+
task.success_criteria?.trim() ? `Success criteria: ${task.success_criteria.trim()}` : null,
|
|
2267
|
+
].filter(Boolean);
|
|
2268
|
+
return lines.join('\n');
|
|
2269
|
+
}
|
|
2270
|
+
function injectWorkerHandoffIntoPrompt(existingPrompt, handoffText, nextTask) {
|
|
2271
|
+
const taskReplacement = [
|
|
2272
|
+
'Task:',
|
|
2273
|
+
'PREVIOUS WORKER HANDOFF',
|
|
2274
|
+
handoffText,
|
|
2275
|
+
'',
|
|
2276
|
+
buildQueuedTaskContext(nextTask),
|
|
2277
|
+
'',
|
|
2278
|
+
'RUNTIME CONTEXT',
|
|
2279
|
+
].join('\n');
|
|
2280
|
+
if (existingPrompt.includes('\nRUNTIME CONTEXT\n')) {
|
|
2281
|
+
return existingPrompt.replace(/Task:\s*[\s\S]*?\nRUNTIME CONTEXT\n/, `${taskReplacement}\n`);
|
|
2282
|
+
}
|
|
2283
|
+
return [
|
|
2284
|
+
'PREVIOUS WORKER HANDOFF',
|
|
2285
|
+
handoffText,
|
|
2286
|
+
'',
|
|
2287
|
+
buildQueuedTaskContext(nextTask),
|
|
2288
|
+
].join('\n');
|
|
2289
|
+
}
|
|
1855
2290
|
function normalizeArtifactRow(row) {
|
|
1856
2291
|
return {
|
|
1857
2292
|
id: Number(row.id),
|
|
@@ -1982,6 +2417,44 @@ function resolveAllowedWorkerNamesForInsertion(currentTask) {
|
|
|
1982
2417
|
allowed.add(previous.owner_name.trim().toLowerCase());
|
|
1983
2418
|
return Array.from(allowed);
|
|
1984
2419
|
}
|
|
2420
|
+
function resolvePreviousWorkerName(currentTask) {
|
|
2421
|
+
const db = getDb();
|
|
2422
|
+
const previous = currentTask.project_id
|
|
2423
|
+
? db.prepare(`
|
|
2424
|
+
SELECT owner_name
|
|
2425
|
+
FROM (
|
|
2426
|
+
SELECT owner_name, created_order, internal_only FROM todo_active WHERE project_id = ?
|
|
2427
|
+
UNION ALL
|
|
2428
|
+
SELECT owner_name, created_order, internal_only FROM todo_completed WHERE project_id = ?
|
|
2429
|
+
)
|
|
2430
|
+
WHERE created_order < ? AND COALESCE(internal_only, 0) = 0 AND owner_name IS NOT NULL
|
|
2431
|
+
ORDER BY created_order DESC
|
|
2432
|
+
LIMIT 1
|
|
2433
|
+
`).get(currentTask.project_id, currentTask.project_id, currentTask.position)
|
|
2434
|
+
: db.prepare(`
|
|
2435
|
+
SELECT owner_name
|
|
2436
|
+
FROM (
|
|
2437
|
+
SELECT owner_name, created_order, internal_only FROM todo_active WHERE project_id IS NULL
|
|
2438
|
+
UNION ALL
|
|
2439
|
+
SELECT owner_name, created_order, internal_only FROM todo_completed WHERE project_id IS NULL
|
|
2440
|
+
)
|
|
2441
|
+
WHERE created_order < ? AND COALESCE(internal_only, 0) = 0 AND owner_name IS NOT NULL
|
|
2442
|
+
ORDER BY created_order DESC
|
|
2443
|
+
LIMIT 1
|
|
2444
|
+
`).get(currentTask.position);
|
|
2445
|
+
return previous?.owner_name?.trim() || null;
|
|
2446
|
+
}
|
|
2447
|
+
function shouldAutoCreateQaFixTask(task, outputSummary, handoffPrompt) {
|
|
2448
|
+
if (String(task.task_type || '').trim().toLowerCase() !== 'qa')
|
|
2449
|
+
return false;
|
|
2450
|
+
const combined = `${outputSummary || ''}\n${handoffPrompt || ''}`.trim().toLowerCase();
|
|
2451
|
+
if (!combined)
|
|
2452
|
+
return false;
|
|
2453
|
+
if (/(^|\b)(qa passed|all checks passed|no issues found|looks good to ship)(\b|$)/i.test(combined)) {
|
|
2454
|
+
return false;
|
|
2455
|
+
}
|
|
2456
|
+
return /\b(fix|defect|issue|problem|error|not ready|fails?|failed|remaining problem|remaining issue|validator|re-qa)\b/i.test(combined);
|
|
2457
|
+
}
|
|
1985
2458
|
function normalizeArtifactInput(ref) {
|
|
1986
2459
|
if (typeof ref === 'string') {
|
|
1987
2460
|
const trimmed = ref.trim();
|
|
@@ -2424,10 +2897,49 @@ function completeTodoTaskByWorker(taskId, params) {
|
|
|
2424
2897
|
}
|
|
2425
2898
|
}
|
|
2426
2899
|
else {
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
const
|
|
2430
|
-
|
|
2900
|
+
if (shouldAutoCreateQaFixTask(completedTask, outputSummary, handoffPrompt)) {
|
|
2901
|
+
const previousWorkerName = resolvePreviousWorkerName(completedTask);
|
|
2902
|
+
const previousWorker = resolveAgentByName(previousWorkerName);
|
|
2903
|
+
if (previousWorkerName && previousWorker) {
|
|
2904
|
+
const participants = [previousWorker.name];
|
|
2905
|
+
insertedTask = insertTodoTaskTx(db, {
|
|
2906
|
+
projectId: completedTask.project_id,
|
|
2907
|
+
conversationId: completedTask.conversation_id,
|
|
2908
|
+
title: 'Fix QA findings',
|
|
2909
|
+
details: handoffPrompt || outputSummary || 'Address the QA findings and update the implementation.',
|
|
2910
|
+
participants,
|
|
2911
|
+
successCriteria: 'Address the QA findings and return the updated work for re-QA.',
|
|
2912
|
+
ownerBotId: previousWorker.id,
|
|
2913
|
+
ownerName: previousWorker.name,
|
|
2914
|
+
taskType: inferTaskTypeForAgent(previousWorker, null),
|
|
2915
|
+
profileName: completedTask.profile_name || 'Programming',
|
|
2916
|
+
nextWorkerBotId: currentTask.owner_bot_id,
|
|
2917
|
+
nextWorkerName: currentTask.owner_name,
|
|
2918
|
+
nextWorkerRole: actorBot?.role_label || actorBot?.role_class || null,
|
|
2919
|
+
taskPrompt: handoffPrompt || outputSummary || 'Address the QA findings and keep the approved parts unchanged.',
|
|
2920
|
+
artifactIds,
|
|
2921
|
+
insertAfterTaskId: completedTask.id,
|
|
2922
|
+
actor: params.actor,
|
|
2923
|
+
});
|
|
2924
|
+
logTodoAuditTx(db, insertedTask.id, 'active', 'worker_insert', params.actor, {
|
|
2925
|
+
insertedAfterTaskId: completedTask.id,
|
|
2926
|
+
requestedWorkerName: previousWorker.name,
|
|
2927
|
+
autoCreatedFromQaFailure: true,
|
|
2928
|
+
});
|
|
2929
|
+
}
|
|
2930
|
+
else {
|
|
2931
|
+
returnedToOrchestrator = true;
|
|
2932
|
+
logTodoAuditTx(db, taskId, 'completed', 'return_to_orchestrator', params.actor, {
|
|
2933
|
+
handoffPrompt,
|
|
2934
|
+
missingPreviousWorkerForQaFix: true,
|
|
2935
|
+
});
|
|
2936
|
+
}
|
|
2937
|
+
}
|
|
2938
|
+
else {
|
|
2939
|
+
const nextWorkerName = completedTask.next_worker_name?.trim();
|
|
2940
|
+
if (nextWorkerName) {
|
|
2941
|
+
const nextRow = completedTask.next_worker_bot_id
|
|
2942
|
+
? db.prepare(`
|
|
2431
2943
|
SELECT * FROM todo_active
|
|
2432
2944
|
WHERE conversation_id = ?
|
|
2433
2945
|
AND id <> ?
|
|
@@ -2437,7 +2949,7 @@ function completeTodoTaskByWorker(taskId, params) {
|
|
|
2437
2949
|
ORDER BY created_order ASC, id ASC
|
|
2438
2950
|
LIMIT 1
|
|
2439
2951
|
`).get(completedTask.conversation_id, completedTask.id, completedTask.next_worker_bot_id, completedTask.created_order)
|
|
2440
|
-
|
|
2952
|
+
: db.prepare(`
|
|
2441
2953
|
SELECT * FROM todo_active
|
|
2442
2954
|
WHERE conversation_id = ?
|
|
2443
2955
|
AND id <> ?
|
|
@@ -2447,21 +2959,15 @@ function completeTodoTaskByWorker(taskId, params) {
|
|
|
2447
2959
|
ORDER BY created_order ASC, id ASC
|
|
2448
2960
|
LIMIT 1
|
|
2449
2961
|
`).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(`
|
|
2962
|
+
if (nextRow) {
|
|
2963
|
+
const nextTask = normalizeTodoRow(nextRow, 'active');
|
|
2964
|
+
const handoffText = handoffPrompt?.trim()
|
|
2965
|
+
|| outputSummary?.trim()
|
|
2966
|
+
|| `Continue from the completed work summary: ${completedTask.title}`;
|
|
2967
|
+
const existingPrompt = nextTask.task_prompt?.trim() || nextTask.details || nextTask.title;
|
|
2968
|
+
const mergedArtifactIds = Array.from(new Set([...(nextTask.artifact_ids || []), ...artifactIds]));
|
|
2969
|
+
const nextPrompt = injectWorkerHandoffIntoPrompt(existingPrompt, handoffText, nextTask);
|
|
2970
|
+
db.prepare(`
|
|
2465
2971
|
UPDATE todo_active
|
|
2466
2972
|
SET task_prompt = ?,
|
|
2467
2973
|
artifact_ids_json = ?,
|
|
@@ -2469,26 +2975,27 @@ function completeTodoTaskByWorker(taskId, params) {
|
|
|
2469
2975
|
version = version + 1
|
|
2470
2976
|
WHERE id = ?
|
|
2471
2977
|
`).run(nextPrompt, JSON.stringify(mergedArtifactIds), now(), nextTask.id);
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2978
|
+
handedOffTask = normalizeTodoRow(db.prepare('SELECT * FROM todo_active WHERE id = ?').get(nextTask.id), 'active');
|
|
2979
|
+
logTodoAuditTx(db, nextTask.id, 'active', 'edit', params.actor, {
|
|
2980
|
+
previousTaskId: completedTask.id,
|
|
2981
|
+
receivedHandoff: true,
|
|
2982
|
+
});
|
|
2983
|
+
}
|
|
2984
|
+
else {
|
|
2985
|
+
returnedToOrchestrator = true;
|
|
2986
|
+
logTodoAuditTx(db, taskId, 'completed', 'return_to_orchestrator', params.actor, {
|
|
2987
|
+
handoffPrompt,
|
|
2988
|
+
missingQueuedNextWorker: nextWorkerName,
|
|
2989
|
+
});
|
|
2990
|
+
}
|
|
2477
2991
|
}
|
|
2478
2992
|
else {
|
|
2479
2993
|
returnedToOrchestrator = true;
|
|
2480
2994
|
logTodoAuditTx(db, taskId, 'completed', 'return_to_orchestrator', params.actor, {
|
|
2481
2995
|
handoffPrompt,
|
|
2482
|
-
missingQueuedNextWorker: nextWorkerName,
|
|
2483
2996
|
});
|
|
2484
2997
|
}
|
|
2485
2998
|
}
|
|
2486
|
-
else {
|
|
2487
|
-
returnedToOrchestrator = true;
|
|
2488
|
-
logTodoAuditTx(db, taskId, 'completed', 'return_to_orchestrator', params.actor, {
|
|
2489
|
-
handoffPrompt,
|
|
2490
|
-
});
|
|
2491
|
-
}
|
|
2492
2999
|
}
|
|
2493
3000
|
return {
|
|
2494
3001
|
completedTask,
|
|
@@ -3040,13 +3547,20 @@ function listTopicConversations(topicId, opts) {
|
|
|
3040
3547
|
const db = getDb();
|
|
3041
3548
|
const limit = opts?.limit ?? 50;
|
|
3042
3549
|
const offset = opts?.offset ?? 0;
|
|
3043
|
-
|
|
3044
|
-
SELECT DISTINCT
|
|
3045
|
-
|
|
3550
|
+
const rows = db.prepare(`
|
|
3551
|
+
SELECT DISTINCT conv.*
|
|
3552
|
+
FROM (
|
|
3553
|
+
${conversationSelectSql('c')}
|
|
3554
|
+
) conv
|
|
3555
|
+
INNER JOIN topic_segment ts ON ts.conversation_id = conv.id
|
|
3046
3556
|
WHERE ts.topic_id = ?
|
|
3047
|
-
ORDER BY
|
|
3557
|
+
ORDER BY datetime(COALESCE(conv.effective_updated_at, conv.updated_at)) DESC,
|
|
3558
|
+
datetime(COALESCE(conv.effective_created_at, conv.created_at)) DESC
|
|
3048
3559
|
LIMIT ? OFFSET ?
|
|
3049
3560
|
`).all(topicId, limit, offset);
|
|
3561
|
+
return rows
|
|
3562
|
+
.map((row) => hydrateConversationRow(row))
|
|
3563
|
+
.filter((row) => !!row);
|
|
3050
3564
|
}
|
|
3051
3565
|
function getPrimaryTopicIdForConversation(conversationId) {
|
|
3052
3566
|
const db = getDb();
|
|
@@ -3090,24 +3604,43 @@ function listTopicConversationsWithPreviews(topicId, opts) {
|
|
|
3090
3604
|
const limit = opts?.limit ?? 50;
|
|
3091
3605
|
const offset = opts?.offset ?? 0;
|
|
3092
3606
|
// Primary: find conversations via topic_segment links
|
|
3093
|
-
|
|
3094
|
-
SELECT DISTINCT
|
|
3095
|
-
|
|
3607
|
+
const rawConvs = db.prepare(`
|
|
3608
|
+
SELECT DISTINCT conv.*
|
|
3609
|
+
FROM (
|
|
3610
|
+
${conversationSelectSql('c')}
|
|
3611
|
+
) conv
|
|
3612
|
+
INNER JOIN topic_segment ts ON ts.conversation_id = conv.id
|
|
3096
3613
|
WHERE ts.topic_id = ?
|
|
3097
|
-
ORDER BY
|
|
3614
|
+
ORDER BY datetime(COALESCE(conv.effective_updated_at, conv.updated_at)) DESC,
|
|
3615
|
+
datetime(COALESCE(conv.effective_created_at, conv.created_at)) DESC
|
|
3098
3616
|
LIMIT ? OFFSET ?
|
|
3099
3617
|
`).all(topicId, limit, offset);
|
|
3618
|
+
const convs = rawConvs
|
|
3619
|
+
.map((row) => hydrateConversationRow(row))
|
|
3620
|
+
.filter((row) => !!row);
|
|
3100
3621
|
// No fallback — topics with no segments have no conversations yet.
|
|
3101
3622
|
// Backfill/import must create topic_segment rows explicitly.
|
|
3102
3623
|
return convs.map(conv => {
|
|
3624
|
+
const latestJob = getLatestConversationChatJob(conv.id);
|
|
3625
|
+
const jobStatus = latestJob?.status === 'running'
|
|
3626
|
+
? 'working'
|
|
3627
|
+
: latestJob?.status === 'completed'
|
|
3628
|
+
? 'done'
|
|
3629
|
+
: latestJob?.status ?? null;
|
|
3103
3630
|
// Get latest summary text
|
|
3104
|
-
const summary = db.prepare(
|
|
3631
|
+
const summary = db.prepare(`SELECT summary_text
|
|
3632
|
+
FROM conversation_summary
|
|
3633
|
+
WHERE conversation_id = ?
|
|
3634
|
+
AND coalesce(summary_kind, 'rolling') = 'rolling'
|
|
3635
|
+
ORDER BY turn_range_end DESC, created_at DESC
|
|
3636
|
+
LIMIT 1`).get(conv.id);
|
|
3105
3637
|
// Get first user message, truncated to 200 chars
|
|
3106
3638
|
const firstMsg = db.prepare("SELECT content FROM message WHERE conversation_id = ? AND role = 'user' ORDER BY seq ASC LIMIT 1").get(conv.id);
|
|
3107
3639
|
return {
|
|
3108
3640
|
...conv,
|
|
3109
3641
|
summary_text: summary?.summary_text ?? null,
|
|
3110
3642
|
first_user_message: firstMsg ? firstMsg.content.slice(0, 200) : null,
|
|
3643
|
+
job_status: jobStatus,
|
|
3111
3644
|
};
|
|
3112
3645
|
});
|
|
3113
3646
|
}
|
|
@@ -3159,6 +3692,10 @@ function updateWorkflowTemplate(id, fields) {
|
|
|
3159
3692
|
const db = getDb();
|
|
3160
3693
|
const sets = [];
|
|
3161
3694
|
const vals = [];
|
|
3695
|
+
if (fields.projectId !== undefined) {
|
|
3696
|
+
sets.push('project_id = ?');
|
|
3697
|
+
vals.push(fields.projectId);
|
|
3698
|
+
}
|
|
3162
3699
|
if (fields.name !== undefined) {
|
|
3163
3700
|
sets.push('name = ?');
|
|
3164
3701
|
vals.push(fields.name);
|