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