@bbigbang/core 0.1.0
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/config.js +380 -0
- package/dist/execution/executionDispatcher.js +3810 -0
- package/dist/main.js +90 -0
- package/dist/nodeEventHistory.js +206 -0
- package/dist/scheduler/dreamLogic.js +50 -0
- package/dist/scheduler/dreamScheduler.js +65 -0
- package/dist/services/agentFileAccessService.js +1913 -0
- package/dist/services/agentRuntimeCleanupBroker.js +62 -0
- package/dist/services/agentSkillsBroker.js +118 -0
- package/dist/services/agentSkillsService.js +83 -0
- package/dist/services/agentWorkspaceBroker.js +937 -0
- package/dist/services/agentWorkspaceService.js +70 -0
- package/dist/services/appVersion.js +14 -0
- package/dist/services/auth.js +586 -0
- package/dist/services/claudeControlBroker.js +154 -0
- package/dist/services/claudeTranscriptBroker.js +100 -0
- package/dist/services/claudeTranscriptService.js +359 -0
- package/dist/services/codexAppServerBroker.js +155 -0
- package/dist/services/codexTranscriptBroker.js +98 -0
- package/dist/services/codexTranscriptService.js +961 -0
- package/dist/services/droidMissionBroker.js +124 -0
- package/dist/services/droidMissionImporter.js +630 -0
- package/dist/services/droidModelOptions.js +165 -0
- package/dist/services/hubServerRegistrationService.js +268 -0
- package/dist/services/libraryManifest.js +43 -0
- package/dist/services/libraryScaffold.js +26 -0
- package/dist/services/libraryService.js +2263 -0
- package/dist/services/memoryService.js +386 -0
- package/dist/services/missionEvidence.js +377 -0
- package/dist/services/missionService.js +2361 -0
- package/dist/services/missionTrace.js +158 -0
- package/dist/services/nativeMissionBriefParser.js +120 -0
- package/dist/services/nativeMissionOrchestrator.js +2045 -0
- package/dist/services/nativeMissionReportGenerator.js +227 -0
- package/dist/services/nativeMissionValidationRunner.js +452 -0
- package/dist/services/nativeMissionWorkerBroker.js +190 -0
- package/dist/services/nodeRegistry.js +34 -0
- package/dist/services/nodeStateReconciler.js +97 -0
- package/dist/services/panelMediaScanner.js +119 -0
- package/dist/services/persistentRuntimeJsonlClient.js +153 -0
- package/dist/services/platformAgentPolicy.js +180 -0
- package/dist/services/platformAgentService.js +2041 -0
- package/dist/services/projectAccessResolver.js +93 -0
- package/dist/services/projectService.js +392 -0
- package/dist/services/resourceSpaceService.js +140 -0
- package/dist/services/scenarioRuntimeService.js +1130 -0
- package/dist/services/suggestedPlannerService.js +868 -0
- package/dist/services/workbenchGitBroker.js +161 -0
- package/dist/services/workbenchGitService.js +69 -0
- package/dist/services/workbenchInspectBroker.js +65 -0
- package/dist/services/workbenchNodePathService.js +79 -0
- package/dist/services/workbenchRegistryService.js +240 -0
- package/dist/services/workbenchRootService.js +181 -0
- package/dist/services/workbenchTerminalBroker.js +378 -0
- package/dist/services/workspaceRunOwnership.js +60 -0
- package/dist/services/workspaceScaffold.js +105 -0
- package/dist/services/workspaceSessionRuntimeService.js +576 -0
- package/dist/services/workspaceSessionService.js +245 -0
- package/dist/services/workspaceToolActionRunner.js +1582 -0
- package/dist/services/workspaceToolErrors.js +10 -0
- package/dist/services/workspaceToolExecutionUtils.js +895 -0
- package/dist/services/workspaceToolLatestStateProjector.js +91 -0
- package/dist/services/workspaceToolManifest.js +572 -0
- package/dist/services/workspaceToolMutationQueue.js +43 -0
- package/dist/services/workspaceToolPanelProjection.js +460 -0
- package/dist/services/workspaceToolPromotion.js +255 -0
- package/dist/services/workspaceToolPromotionState.js +224 -0
- package/dist/services/workspaceToolPublishDiagnostics.js +189 -0
- package/dist/services/workspaceToolPublishIdentityResolver.js +146 -0
- package/dist/services/workspaceToolReadModel.js +378 -0
- package/dist/services/workspaceToolRunLedger.js +239 -0
- package/dist/services/workspaceToolService.js +3067 -0
- package/dist/services/workspaceToolSnapshotPanelSync.js +293 -0
- package/dist/services/workspaceToolTerminalLifecycle.js +283 -0
- package/dist/services/workspaceToolTypes.js +1 -0
- package/dist/services/workspaceToolUploadMaterializer.js +228 -0
- package/dist/web/actionCardRoutes.js +129 -0
- package/dist/web/actionCards.js +469 -0
- package/dist/web/activationContext.js +684 -0
- package/dist/web/agentChannelGuards.js +48 -0
- package/dist/web/agentMentionCooldowns.js +32 -0
- package/dist/web/agentReminders.js +1668 -0
- package/dist/web/agentRuntimePresence.js +197 -0
- package/dist/web/agentSelfState.js +494 -0
- package/dist/web/agentTaskLinks.js +26 -0
- package/dist/web/agentVisibility.js +79 -0
- package/dist/web/assets.js +95 -0
- package/dist/web/channelActivationPrompt.js +395 -0
- package/dist/web/channelMemoryNotes.js +127 -0
- package/dist/web/channelMentions.js +10 -0
- package/dist/web/channelMessageSequences.js +19 -0
- package/dist/web/channelSubscriptions.js +26 -0
- package/dist/web/clearedTaskRoots.js +10 -0
- package/dist/web/collaborationPromptGuidance.js +36 -0
- package/dist/web/collaborationSurfaceState.js +140 -0
- package/dist/web/contextBundleRanking.js +154 -0
- package/dist/web/contextBundleResolver.js +488 -0
- package/dist/web/conversationBuiltinSkillRoots.js +50 -0
- package/dist/web/conversationControls.js +232 -0
- package/dist/web/conversationHandoffs.js +612 -0
- package/dist/web/conversationManager.js +2511 -0
- package/dist/web/conversationSummaries.js +876 -0
- package/dist/web/conversationSurfaceKinds.js +17 -0
- package/dist/web/conversationTargets.js +173 -0
- package/dist/web/directActivationPrompt.js +122 -0
- package/dist/web/directReplyTargets.js +69 -0
- package/dist/web/directThreadResolver.js +129 -0
- package/dist/web/dmTaskHandoffPrompt.js +120 -0
- package/dist/web/dmTaskThreadStatusProjection.js +229 -0
- package/dist/web/ftsQuery.js +33 -0
- package/dist/web/internalAgentRouter.js +11341 -0
- package/dist/web/libraryCuratorScheduler.js +58 -0
- package/dist/web/libraryDocumentPromptGuidance.js +8 -0
- package/dist/web/messageCheckpoints.js +19 -0
- package/dist/web/nodeWsHandler.js +2495 -0
- package/dist/web/notificationRounds.js +1061 -0
- package/dist/web/panelActionMessages.js +108 -0
- package/dist/web/panelActivationPrompt.js +18 -0
- package/dist/web/panelAudit.js +273 -0
- package/dist/web/panelLifecycle.js +222 -0
- package/dist/web/panelMediaPolicy.js +43 -0
- package/dist/web/panelPathPolicy.js +63 -0
- package/dist/web/panelPreviews.js +175 -0
- package/dist/web/panelQueryHandles.js +2749 -0
- package/dist/web/panelRoutes.js +2147 -0
- package/dist/web/panels.js +904 -0
- package/dist/web/peerInboxAggregates.js +1247 -0
- package/dist/web/planApprovalState.js +92 -0
- package/dist/web/platformAgentScheduler.js +66 -0
- package/dist/web/proactiveOpportunities.js +452 -0
- package/dist/web/promptContextSections.js +242 -0
- package/dist/web/promptHistorySanitizer.js +26 -0
- package/dist/web/promptSlashCommands.js +158 -0
- package/dist/web/rollingConversationSummary.js +453 -0
- package/dist/web/routeHelpers.js +11 -0
- package/dist/web/routes/handoff.js +288 -0
- package/dist/web/routes/history.js +345 -0
- package/dist/web/routes/memory.js +258 -0
- package/dist/web/routes/selfState.js +171 -0
- package/dist/web/routes/workspace.js +154 -0
- package/dist/web/runSurfaceWatermarks.js +431 -0
- package/dist/web/runtimeCapabilities.js +48 -0
- package/dist/web/sameAgentHandoffs.js +494 -0
- package/dist/web/server.js +15567 -0
- package/dist/web/sharedCollaborationCapsules.js +163 -0
- package/dist/web/soloSessionRelay.js +42 -0
- package/dist/web/soloWsHandler.js +138 -0
- package/dist/web/suggestedPlannerScheduler.js +56 -0
- package/dist/web/surfaceActivationPolicy.js +108 -0
- package/dist/web/surfaceCollaborators.js +61 -0
- package/dist/web/surfaceSystemStatus.js +263 -0
- package/dist/web/targetParticipants.js +77 -0
- package/dist/web/taskEvents.js +49 -0
- package/dist/web/taskLifecycleMessages.js +165 -0
- package/dist/web/taskLoops.js +732 -0
- package/dist/web/taskMemoryNotes.js +224 -0
- package/dist/web/taskNumbers.js +16 -0
- package/dist/web/taskOwnerGuards.js +49 -0
- package/dist/web/taskParticipantResolver.js +42 -0
- package/dist/web/taskParticipants.js +97 -0
- package/dist/web/taskSourceDetails.js +20 -0
- package/dist/web/taskStateViews.js +210 -0
- package/dist/web/taskStatusTransitions.js +9 -0
- package/dist/web/taskThreadFollowups.js +599 -0
- package/dist/web/taskThreadRuntimeClosure.js +685 -0
- package/dist/web/taskUpdateDelivery.js +104 -0
- package/dist/web/threadReplyContentHeuristics.js +30 -0
- package/dist/web/threadRoots.js +61 -0
- package/dist/web/threadTaskBindings.js +365 -0
- package/dist/web/uiPanelPromptGuidance.js +27 -0
- package/dist/web/workspaceMemoryHints.js +143 -0
- package/dist/web/workspaceToolPromptGuidance.js +30 -0
- package/dist/web/wsHandler.js +397 -0
- package/dist/web/wsSink.js +116 -0
- package/package.json +54 -0
|
@@ -0,0 +1,732 @@
|
|
|
1
|
+
import { exec as execCallback } from 'node:child_process';
|
|
2
|
+
import { randomUUID } from 'node:crypto';
|
|
3
|
+
import { promisify } from 'node:util';
|
|
4
|
+
import { buildThreadShortId } from '@bbigbang/protocol';
|
|
5
|
+
import { log } from '@bbigbang/runtime-acp';
|
|
6
|
+
import { z } from 'zod';
|
|
7
|
+
import { appendTaskEvent, buildTaskEventThreadTarget } from './taskEvents.js';
|
|
8
|
+
const execAsync = promisify(execCallback);
|
|
9
|
+
export const TASK_LOOP_POLL_INTERVAL_MS = 5_000;
|
|
10
|
+
export const taskLoopApprovalPolicySchema = z.enum(['none', 'before_start', 'before_each_iteration']);
|
|
11
|
+
export const taskLoopTemplateSchema = z.object({
|
|
12
|
+
goalPrompt: z.string().trim().min(1).max(20_000).optional(),
|
|
13
|
+
verifyChecks: z.array(z.string().trim().min(1).max(4_000)).max(20).default([]),
|
|
14
|
+
maxIterations: z.number().int().min(1).max(50).default(3),
|
|
15
|
+
maxTimeMs: z.number().int().min(1_000).max(24 * 60 * 60 * 1000).default(15 * 60 * 1000),
|
|
16
|
+
approvalPolicy: taskLoopApprovalPolicySchema.default('none'),
|
|
17
|
+
}).strict();
|
|
18
|
+
function parseTemplate(templateJson) {
|
|
19
|
+
return taskLoopTemplateSchema.parse(JSON.parse(templateJson));
|
|
20
|
+
}
|
|
21
|
+
function toTaskLoopInfo(row) {
|
|
22
|
+
return {
|
|
23
|
+
loopId: row.loopId,
|
|
24
|
+
taskId: row.taskId,
|
|
25
|
+
workerAgentId: row.workerAgentId,
|
|
26
|
+
rootConversationId: row.rootConversationId,
|
|
27
|
+
workerConversationId: row.workerConversationId,
|
|
28
|
+
goalPrompt: row.goalPrompt,
|
|
29
|
+
verifyChecks: JSON.parse(row.verifyChecksJson),
|
|
30
|
+
maxIterations: row.maxIterations,
|
|
31
|
+
maxTimeMs: row.maxTimeMs,
|
|
32
|
+
approvalPolicy: row.approvalPolicy,
|
|
33
|
+
status: row.status,
|
|
34
|
+
stopReason: row.stopReason,
|
|
35
|
+
currentIteration: row.currentIteration,
|
|
36
|
+
lastError: row.lastError,
|
|
37
|
+
template: parseTemplate(row.templateJson),
|
|
38
|
+
createdAt: row.createdAt,
|
|
39
|
+
updatedAt: row.updatedAt,
|
|
40
|
+
startedAt: row.startedAt,
|
|
41
|
+
completedAt: row.completedAt,
|
|
42
|
+
stopRequestedAt: row.stopRequestedAt,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
function toTaskLoopIterationInfo(row) {
|
|
46
|
+
return {
|
|
47
|
+
iterationId: row.iterationId,
|
|
48
|
+
loopId: row.loopId,
|
|
49
|
+
iterationIndex: row.iterationIndex,
|
|
50
|
+
status: row.status,
|
|
51
|
+
workerRunId: row.workerRunId,
|
|
52
|
+
verifyChecksPassed: row.verifyChecksPassed == null ? null : row.verifyChecksPassed === 1,
|
|
53
|
+
verifyChecksOutput: row.verifyChecksOutputJson ? JSON.parse(row.verifyChecksOutputJson) : [],
|
|
54
|
+
failureReason: row.failureReason,
|
|
55
|
+
decision: row.decision,
|
|
56
|
+
startedAt: row.startedAt,
|
|
57
|
+
endedAt: row.endedAt,
|
|
58
|
+
createdAt: row.createdAt,
|
|
59
|
+
updatedAt: row.updatedAt,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
function nextTaskLoopEventSeq(db, loopId) {
|
|
63
|
+
const row = db.prepare(`SELECT COALESCE(MAX(seq), 0) as maxSeq
|
|
64
|
+
FROM task_loop_events
|
|
65
|
+
WHERE loop_id = ?`).get(loopId);
|
|
66
|
+
return (row?.maxSeq ?? 0) + 1;
|
|
67
|
+
}
|
|
68
|
+
export function appendTaskLoopEvent(db, params) {
|
|
69
|
+
db.prepare(`INSERT INTO task_loop_events(event_id, loop_id, iteration_index, seq, event_type, payload_json, created_at)
|
|
70
|
+
VALUES(?, ?, ?, ?, ?, ?, ?)`).run(randomUUID(), params.loopId, params.iterationIndex ?? null, nextTaskLoopEventSeq(db, params.loopId), params.eventType, params.payload ? JSON.stringify(params.payload) : null, params.createdAt ?? Date.now());
|
|
71
|
+
}
|
|
72
|
+
export function getActiveTaskLoopForTask(db, taskId) {
|
|
73
|
+
const row = db.prepare(`SELECT l.loop_id as loopId,
|
|
74
|
+
l.task_id as taskId,
|
|
75
|
+
l.worker_agent_id as workerAgentId,
|
|
76
|
+
l.root_conversation_id as rootConversationId,
|
|
77
|
+
l.worker_conversation_id as workerConversationId,
|
|
78
|
+
l.goal_prompt as goalPrompt,
|
|
79
|
+
l.verify_checks_json as verifyChecksJson,
|
|
80
|
+
l.max_iterations as maxIterations,
|
|
81
|
+
l.max_time_ms as maxTimeMs,
|
|
82
|
+
l.approval_policy as approvalPolicy,
|
|
83
|
+
l.status,
|
|
84
|
+
l.stop_reason as stopReason,
|
|
85
|
+
l.current_iteration as currentIteration,
|
|
86
|
+
l.last_error as lastError,
|
|
87
|
+
l.template_json as templateJson,
|
|
88
|
+
l.created_at as createdAt,
|
|
89
|
+
l.updated_at as updatedAt,
|
|
90
|
+
l.started_at as startedAt,
|
|
91
|
+
l.completed_at as completedAt,
|
|
92
|
+
l.stop_requested_at as stopRequestedAt
|
|
93
|
+
FROM task_loops l
|
|
94
|
+
JOIN tasks t ON t.active_loop_id = l.loop_id
|
|
95
|
+
WHERE t.task_id = ?
|
|
96
|
+
LIMIT 1`).get(taskId);
|
|
97
|
+
return row ? toTaskLoopInfo(row) : null;
|
|
98
|
+
}
|
|
99
|
+
export function getTaskLoopById(db, loopId) {
|
|
100
|
+
const row = db.prepare(`SELECT loop_id as loopId,
|
|
101
|
+
task_id as taskId,
|
|
102
|
+
worker_agent_id as workerAgentId,
|
|
103
|
+
root_conversation_id as rootConversationId,
|
|
104
|
+
worker_conversation_id as workerConversationId,
|
|
105
|
+
goal_prompt as goalPrompt,
|
|
106
|
+
verify_checks_json as verifyChecksJson,
|
|
107
|
+
max_iterations as maxIterations,
|
|
108
|
+
max_time_ms as maxTimeMs,
|
|
109
|
+
approval_policy as approvalPolicy,
|
|
110
|
+
status,
|
|
111
|
+
stop_reason as stopReason,
|
|
112
|
+
current_iteration as currentIteration,
|
|
113
|
+
last_error as lastError,
|
|
114
|
+
template_json as templateJson,
|
|
115
|
+
created_at as createdAt,
|
|
116
|
+
updated_at as updatedAt,
|
|
117
|
+
started_at as startedAt,
|
|
118
|
+
completed_at as completedAt,
|
|
119
|
+
stop_requested_at as stopRequestedAt
|
|
120
|
+
FROM task_loops
|
|
121
|
+
WHERE loop_id = ?
|
|
122
|
+
LIMIT 1`).get(loopId);
|
|
123
|
+
return row ? toTaskLoopInfo(row) : null;
|
|
124
|
+
}
|
|
125
|
+
export function listTaskLoopIterations(db, loopId) {
|
|
126
|
+
const rows = db.prepare(`SELECT iteration_id as iterationId,
|
|
127
|
+
loop_id as loopId,
|
|
128
|
+
iteration_index as iterationIndex,
|
|
129
|
+
status,
|
|
130
|
+
worker_run_id as workerRunId,
|
|
131
|
+
verify_checks_passed as verifyChecksPassed,
|
|
132
|
+
verify_checks_output_json as verifyChecksOutputJson,
|
|
133
|
+
failure_reason as failureReason,
|
|
134
|
+
decision,
|
|
135
|
+
started_at as startedAt,
|
|
136
|
+
ended_at as endedAt,
|
|
137
|
+
created_at as createdAt,
|
|
138
|
+
updated_at as updatedAt
|
|
139
|
+
FROM task_loop_iterations
|
|
140
|
+
WHERE loop_id = ?
|
|
141
|
+
ORDER BY iteration_index ASC`).all(loopId);
|
|
142
|
+
return rows.map(toTaskLoopIterationInfo);
|
|
143
|
+
}
|
|
144
|
+
export function listTaskLoopEvents(db, loopId) {
|
|
145
|
+
const rows = db.prepare(`SELECT event_id as eventId,
|
|
146
|
+
iteration_index as iterationIndex,
|
|
147
|
+
seq,
|
|
148
|
+
event_type as eventType,
|
|
149
|
+
payload_json as payloadJson,
|
|
150
|
+
created_at as createdAt
|
|
151
|
+
FROM task_loop_events
|
|
152
|
+
WHERE loop_id = ?
|
|
153
|
+
ORDER BY seq ASC`).all(loopId);
|
|
154
|
+
return rows.map((row) => ({
|
|
155
|
+
eventId: row.eventId,
|
|
156
|
+
iterationIndex: row.iterationIndex,
|
|
157
|
+
seq: row.seq,
|
|
158
|
+
eventType: row.eventType,
|
|
159
|
+
payload: row.payloadJson ? JSON.parse(row.payloadJson) : null,
|
|
160
|
+
createdAt: row.createdAt,
|
|
161
|
+
}));
|
|
162
|
+
}
|
|
163
|
+
function loadTaskForLoop(db, taskId) {
|
|
164
|
+
const row = db.prepare(`SELECT task_id as taskId,
|
|
165
|
+
channel_id as channelId,
|
|
166
|
+
task_number as taskNumber,
|
|
167
|
+
title,
|
|
168
|
+
description,
|
|
169
|
+
status,
|
|
170
|
+
claimed_by_agent_id as claimedByAgentId,
|
|
171
|
+
claimed_by_name as claimedByName,
|
|
172
|
+
message_id as messageId,
|
|
173
|
+
active_loop_id as activeLoopId,
|
|
174
|
+
execution_mode as executionMode
|
|
175
|
+
FROM tasks
|
|
176
|
+
WHERE task_id = ?
|
|
177
|
+
LIMIT 1`).get(taskId);
|
|
178
|
+
return row ?? null;
|
|
179
|
+
}
|
|
180
|
+
function getLoopIterationRow(db, loopId, iterationIndex) {
|
|
181
|
+
const row = db.prepare(`SELECT iteration_id as iterationId,
|
|
182
|
+
loop_id as loopId,
|
|
183
|
+
iteration_index as iterationIndex,
|
|
184
|
+
status,
|
|
185
|
+
worker_run_id as workerRunId,
|
|
186
|
+
verify_checks_passed as verifyChecksPassed,
|
|
187
|
+
verify_checks_output_json as verifyChecksOutputJson,
|
|
188
|
+
failure_reason as failureReason,
|
|
189
|
+
decision,
|
|
190
|
+
started_at as startedAt,
|
|
191
|
+
ended_at as endedAt,
|
|
192
|
+
created_at as createdAt,
|
|
193
|
+
updated_at as updatedAt
|
|
194
|
+
FROM task_loop_iterations
|
|
195
|
+
WHERE loop_id = ? AND iteration_index = ?
|
|
196
|
+
LIMIT 1`).get(loopId, iterationIndex);
|
|
197
|
+
return row ?? null;
|
|
198
|
+
}
|
|
199
|
+
function ensureLoopIteration(db, loopId, iterationIndex, now) {
|
|
200
|
+
const existing = getLoopIterationRow(db, loopId, iterationIndex);
|
|
201
|
+
if (existing)
|
|
202
|
+
return existing;
|
|
203
|
+
const iterationId = randomUUID();
|
|
204
|
+
db.prepare(`INSERT INTO task_loop_iterations(
|
|
205
|
+
iteration_id, loop_id, iteration_index, status, created_at, updated_at
|
|
206
|
+
)
|
|
207
|
+
VALUES(?, ?, ?, 'pending', ?, ?)`).run(iterationId, loopId, iterationIndex, now, now);
|
|
208
|
+
return getLoopIterationRow(db, loopId, iterationIndex);
|
|
209
|
+
}
|
|
210
|
+
function buildLoopIterationPrompt(params) {
|
|
211
|
+
const lines = [
|
|
212
|
+
'[Loop task iteration]',
|
|
213
|
+
`Task: #${params.task.taskNumber} ${params.task.title}`,
|
|
214
|
+
`Iteration: ${params.iterationIndex}/${params.loop.maxIterations}`,
|
|
215
|
+
'',
|
|
216
|
+
'Goal:',
|
|
217
|
+
params.loop.goalPrompt,
|
|
218
|
+
];
|
|
219
|
+
if (params.task.description?.trim()) {
|
|
220
|
+
lines.push('', 'Task brief:', params.task.description.trim());
|
|
221
|
+
}
|
|
222
|
+
if (params.previousFailureReason?.trim()) {
|
|
223
|
+
lines.push('', 'Previous verification failure:', params.previousFailureReason.trim());
|
|
224
|
+
}
|
|
225
|
+
if (params.loop.verifyChecks.length > 0) {
|
|
226
|
+
lines.push('', 'Verification checks that must pass:');
|
|
227
|
+
for (const check of params.loop.verifyChecks) {
|
|
228
|
+
lines.push(`- ${check}`);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
lines.push('', 'Rules:', '- Continue working in this task thread.', '- When the task is ready, the platform will run verification checks automatically.', '- Do not mark the task done yourself; the platform will move it to in_review after successful verification.');
|
|
232
|
+
return lines.join('\n');
|
|
233
|
+
}
|
|
234
|
+
function canAttemptLoopDispatch(db, nodeRegistry, conversationId) {
|
|
235
|
+
const row = db.prepare(`SELECT session_key as sessionKey,
|
|
236
|
+
node_id as nodeId,
|
|
237
|
+
status
|
|
238
|
+
FROM conversations
|
|
239
|
+
WHERE id = ?
|
|
240
|
+
LIMIT 1`).get(conversationId);
|
|
241
|
+
if (!row?.nodeId)
|
|
242
|
+
return false;
|
|
243
|
+
if (nodeRegistry && !nodeRegistry.getNode(row.nodeId))
|
|
244
|
+
return false;
|
|
245
|
+
if (row.status === 'queued' || row.status === 'active' || row.status === 'recovering' || row.status === 'awaiting_approval') {
|
|
246
|
+
return false;
|
|
247
|
+
}
|
|
248
|
+
return !db.prepare(`SELECT 1
|
|
249
|
+
FROM runs
|
|
250
|
+
WHERE session_key = ? AND ended_at IS NULL
|
|
251
|
+
LIMIT 1`).get(row.sessionKey);
|
|
252
|
+
}
|
|
253
|
+
function loadRun(db, runId) {
|
|
254
|
+
const row = db.prepare(`SELECT ended_at as endedAt, stop_reason as stopReason, error
|
|
255
|
+
FROM runs
|
|
256
|
+
WHERE run_id = ?
|
|
257
|
+
LIMIT 1`).get(runId);
|
|
258
|
+
return row ?? null;
|
|
259
|
+
}
|
|
260
|
+
function isCancelStopReason(stopReason) {
|
|
261
|
+
return stopReason === 'cancelled' || stopReason === 'canceled';
|
|
262
|
+
}
|
|
263
|
+
function shouldRetry(loop, iterationIndex, startedAt, now) {
|
|
264
|
+
if (iterationIndex >= loop.maxIterations)
|
|
265
|
+
return false;
|
|
266
|
+
if (startedAt != null && now - startedAt >= loop.maxTimeMs)
|
|
267
|
+
return false;
|
|
268
|
+
return true;
|
|
269
|
+
}
|
|
270
|
+
function needsIterationApproval(loop) {
|
|
271
|
+
return loop.approvalPolicy === 'before_each_iteration';
|
|
272
|
+
}
|
|
273
|
+
async function runVerifyChecks(params) {
|
|
274
|
+
const results = [];
|
|
275
|
+
for (const command of params.commands) {
|
|
276
|
+
try {
|
|
277
|
+
const { stdout, stderr } = await execAsync(command, {
|
|
278
|
+
cwd: params.cwd,
|
|
279
|
+
shell: '/bin/bash',
|
|
280
|
+
maxBuffer: 1024 * 1024,
|
|
281
|
+
});
|
|
282
|
+
results.push({
|
|
283
|
+
command,
|
|
284
|
+
passed: true,
|
|
285
|
+
exitCode: 0,
|
|
286
|
+
stdout,
|
|
287
|
+
stderr,
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
catch (error) {
|
|
291
|
+
const execError = error;
|
|
292
|
+
const exitCode = typeof execError.code === 'number' ? execError.code : null;
|
|
293
|
+
const stdout = execError.stdout ?? '';
|
|
294
|
+
const stderr = execError.stderr ?? execError.message ?? '';
|
|
295
|
+
results.push({
|
|
296
|
+
command,
|
|
297
|
+
passed: false,
|
|
298
|
+
exitCode,
|
|
299
|
+
stdout,
|
|
300
|
+
stderr,
|
|
301
|
+
});
|
|
302
|
+
return {
|
|
303
|
+
passed: false,
|
|
304
|
+
results,
|
|
305
|
+
failureReason: `Verify check failed: ${command}`,
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
return { passed: true, results, failureReason: null };
|
|
310
|
+
}
|
|
311
|
+
function resolveLoopCheckCwd(db, conversationId) {
|
|
312
|
+
const row = db.prepare(`SELECT c.workspace_path as workspacePath,
|
|
313
|
+
c.active_project_id as activeProjectId,
|
|
314
|
+
p.root_path as activeProjectRoot
|
|
315
|
+
FROM conversations c
|
|
316
|
+
LEFT JOIN projects p ON p.project_id = c.active_project_id
|
|
317
|
+
WHERE c.id = ?
|
|
318
|
+
LIMIT 1`).get(conversationId);
|
|
319
|
+
return row?.activeProjectRoot ?? row?.workspacePath ?? null;
|
|
320
|
+
}
|
|
321
|
+
function updateLoopStatus(db, params) {
|
|
322
|
+
db.prepare(`UPDATE task_loops
|
|
323
|
+
SET status = ?,
|
|
324
|
+
stop_reason = ?,
|
|
325
|
+
last_error = ?,
|
|
326
|
+
started_at = COALESCE(?, started_at),
|
|
327
|
+
completed_at = ?,
|
|
328
|
+
stop_requested_at = ?,
|
|
329
|
+
current_iteration = COALESCE(?, current_iteration),
|
|
330
|
+
worker_conversation_id = COALESCE(?, worker_conversation_id),
|
|
331
|
+
worker_agent_id = COALESCE(?, worker_agent_id),
|
|
332
|
+
updated_at = ?
|
|
333
|
+
WHERE loop_id = ?`).run(params.status, params.stopReason ?? null, params.lastError ?? null, params.startedAt ?? null, params.completedAt ?? null, params.stopRequestedAt ?? null, params.currentIteration ?? null, params.workerConversationId ?? null, params.workerAgentId ?? null, Date.now(), params.loopId);
|
|
334
|
+
}
|
|
335
|
+
function resolveWorkerConversation(db, manager, task) {
|
|
336
|
+
if (!task.claimedByAgentId || !task.messageId)
|
|
337
|
+
return null;
|
|
338
|
+
const threadRootId = buildThreadShortId(task.messageId);
|
|
339
|
+
const conversation = manager.openAgentChannelThread(task.claimedByAgentId, task.channelId, threadRootId);
|
|
340
|
+
if (!conversation)
|
|
341
|
+
return null;
|
|
342
|
+
return {
|
|
343
|
+
conversationId: conversation.id,
|
|
344
|
+
replyTarget: conversation.replyTarget ?? null,
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
async function processRunningLoop(params) {
|
|
348
|
+
const { db, conversationManager, nodeRegistry, loop, task, now } = params;
|
|
349
|
+
let statusLifecycleEvent = null;
|
|
350
|
+
if (task.status === 'done') {
|
|
351
|
+
updateLoopStatus(db, {
|
|
352
|
+
loopId: loop.loopId,
|
|
353
|
+
status: 'cancelled',
|
|
354
|
+
stopReason: 'task_done',
|
|
355
|
+
completedAt: now,
|
|
356
|
+
lastError: null,
|
|
357
|
+
});
|
|
358
|
+
appendTaskLoopEvent(db, {
|
|
359
|
+
loopId: loop.loopId,
|
|
360
|
+
eventType: 'loop.cancelled',
|
|
361
|
+
payload: { reason: 'task_done' },
|
|
362
|
+
createdAt: now,
|
|
363
|
+
});
|
|
364
|
+
params.onLoopChanged?.(task.channelId);
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
if (!task.claimedByAgentId) {
|
|
368
|
+
updateLoopStatus(db, {
|
|
369
|
+
loopId: loop.loopId,
|
|
370
|
+
status: 'stopped',
|
|
371
|
+
stopReason: 'task_unclaimed',
|
|
372
|
+
completedAt: now,
|
|
373
|
+
lastError: null,
|
|
374
|
+
});
|
|
375
|
+
appendTaskLoopEvent(db, {
|
|
376
|
+
loopId: loop.loopId,
|
|
377
|
+
eventType: 'loop.stopped',
|
|
378
|
+
payload: { reason: 'task_unclaimed' },
|
|
379
|
+
createdAt: now,
|
|
380
|
+
});
|
|
381
|
+
params.onLoopChanged?.(task.channelId);
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
const iterationIndex = Math.max(1, loop.currentIteration || 1);
|
|
385
|
+
const iteration = ensureLoopIteration(db, loop.loopId, iterationIndex, now);
|
|
386
|
+
const previousIteration = iterationIndex > 1 ? getLoopIterationRow(db, loop.loopId, iterationIndex - 1) : null;
|
|
387
|
+
if (iteration.status === 'pending') {
|
|
388
|
+
const workerConversation = resolveWorkerConversation(db, conversationManager, task);
|
|
389
|
+
if (!workerConversation)
|
|
390
|
+
return;
|
|
391
|
+
if (!canAttemptLoopDispatch(db, nodeRegistry, workerConversation.conversationId))
|
|
392
|
+
return;
|
|
393
|
+
const promptText = buildLoopIterationPrompt({
|
|
394
|
+
task,
|
|
395
|
+
loop,
|
|
396
|
+
iterationIndex,
|
|
397
|
+
previousFailureReason: previousIteration?.failureReason ?? null,
|
|
398
|
+
});
|
|
399
|
+
try {
|
|
400
|
+
const result = await conversationManager.submitPrompt(workerConversation.conversationId, promptText, {
|
|
401
|
+
recordAsUserMessage: false,
|
|
402
|
+
});
|
|
403
|
+
if (result.queued || !result.runId)
|
|
404
|
+
return;
|
|
405
|
+
db.prepare(`UPDATE task_loop_iterations
|
|
406
|
+
SET status = 'running',
|
|
407
|
+
worker_run_id = ?,
|
|
408
|
+
started_at = ?,
|
|
409
|
+
updated_at = ?
|
|
410
|
+
WHERE iteration_id = ?`).run(result.runId, now, now, iteration.iterationId);
|
|
411
|
+
updateLoopStatus(db, {
|
|
412
|
+
loopId: loop.loopId,
|
|
413
|
+
status: 'running',
|
|
414
|
+
startedAt: loop.startedAt ?? now,
|
|
415
|
+
currentIteration: iterationIndex,
|
|
416
|
+
workerConversationId: workerConversation.conversationId,
|
|
417
|
+
workerAgentId: task.claimedByAgentId,
|
|
418
|
+
});
|
|
419
|
+
appendTaskLoopEvent(db, {
|
|
420
|
+
loopId: loop.loopId,
|
|
421
|
+
iterationIndex,
|
|
422
|
+
eventType: 'iteration.started',
|
|
423
|
+
payload: {
|
|
424
|
+
workerRunId: result.runId,
|
|
425
|
+
workerConversationId: workerConversation.conversationId,
|
|
426
|
+
},
|
|
427
|
+
createdAt: now,
|
|
428
|
+
});
|
|
429
|
+
params.onLoopChanged?.(task.channelId);
|
|
430
|
+
}
|
|
431
|
+
catch (error) {
|
|
432
|
+
log.warn('[task-loop] failed to dispatch iteration', {
|
|
433
|
+
loopId: loop.loopId,
|
|
434
|
+
taskId: task.taskId,
|
|
435
|
+
error: String(error?.message ?? error),
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
if (iteration.status !== 'running' || !iteration.workerRunId)
|
|
441
|
+
return;
|
|
442
|
+
const run = loadRun(db, iteration.workerRunId);
|
|
443
|
+
if (!run?.endedAt)
|
|
444
|
+
return;
|
|
445
|
+
if (run.error) {
|
|
446
|
+
db.prepare(`UPDATE task_loop_iterations
|
|
447
|
+
SET status = 'failed',
|
|
448
|
+
failure_reason = ?,
|
|
449
|
+
decision = 'stop',
|
|
450
|
+
ended_at = ?,
|
|
451
|
+
updated_at = ?
|
|
452
|
+
WHERE iteration_id = ?`).run(run.error, now, now, iteration.iterationId);
|
|
453
|
+
updateLoopStatus(db, {
|
|
454
|
+
loopId: loop.loopId,
|
|
455
|
+
status: 'failed',
|
|
456
|
+
stopReason: 'worker_error',
|
|
457
|
+
lastError: run.error,
|
|
458
|
+
completedAt: now,
|
|
459
|
+
});
|
|
460
|
+
appendTaskLoopEvent(db, {
|
|
461
|
+
loopId: loop.loopId,
|
|
462
|
+
iterationIndex,
|
|
463
|
+
eventType: 'iteration.failed',
|
|
464
|
+
payload: {
|
|
465
|
+
reason: run.error,
|
|
466
|
+
source: 'worker_error',
|
|
467
|
+
},
|
|
468
|
+
createdAt: now,
|
|
469
|
+
});
|
|
470
|
+
params.onLoopChanged?.(task.channelId);
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
if (isCancelStopReason(run.stopReason)) {
|
|
474
|
+
db.prepare(`UPDATE task_loop_iterations
|
|
475
|
+
SET status = 'cancelled',
|
|
476
|
+
failure_reason = ?,
|
|
477
|
+
decision = 'stop',
|
|
478
|
+
ended_at = ?,
|
|
479
|
+
updated_at = ?
|
|
480
|
+
WHERE iteration_id = ?`).run('Worker run was cancelled', now, now, iteration.iterationId);
|
|
481
|
+
updateLoopStatus(db, {
|
|
482
|
+
loopId: loop.loopId,
|
|
483
|
+
status: 'cancelled',
|
|
484
|
+
stopReason: 'worker_cancelled',
|
|
485
|
+
lastError: null,
|
|
486
|
+
completedAt: now,
|
|
487
|
+
});
|
|
488
|
+
appendTaskLoopEvent(db, {
|
|
489
|
+
loopId: loop.loopId,
|
|
490
|
+
iterationIndex,
|
|
491
|
+
eventType: 'loop.cancelled',
|
|
492
|
+
payload: {
|
|
493
|
+
reason: 'worker_cancelled',
|
|
494
|
+
},
|
|
495
|
+
createdAt: now,
|
|
496
|
+
});
|
|
497
|
+
params.onLoopChanged?.(task.channelId);
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
const checkCwd = loop.workerConversationId ? resolveLoopCheckCwd(db, loop.workerConversationId) : null;
|
|
501
|
+
const verification = loop.verifyChecks.length === 0
|
|
502
|
+
? { passed: true, results: [], failureReason: null }
|
|
503
|
+
: await runVerifyChecks({
|
|
504
|
+
commands: loop.verifyChecks,
|
|
505
|
+
cwd: checkCwd ?? process.cwd(),
|
|
506
|
+
});
|
|
507
|
+
if (verification.passed) {
|
|
508
|
+
db.transaction(() => {
|
|
509
|
+
db.prepare(`UPDATE task_loop_iterations
|
|
510
|
+
SET status = 'succeeded',
|
|
511
|
+
verify_checks_passed = 1,
|
|
512
|
+
verify_checks_output_json = ?,
|
|
513
|
+
decision = 'finish',
|
|
514
|
+
ended_at = ?,
|
|
515
|
+
updated_at = ?
|
|
516
|
+
WHERE iteration_id = ?`).run(JSON.stringify(verification.results), now, now, iteration.iterationId);
|
|
517
|
+
updateLoopStatus(db, {
|
|
518
|
+
loopId: loop.loopId,
|
|
519
|
+
status: 'succeeded',
|
|
520
|
+
stopReason: 'passed',
|
|
521
|
+
lastError: null,
|
|
522
|
+
completedAt: now,
|
|
523
|
+
});
|
|
524
|
+
if (task.status === 'in_progress') {
|
|
525
|
+
db.prepare(`UPDATE tasks
|
|
526
|
+
SET status = 'in_review', updated_at = ?
|
|
527
|
+
WHERE task_id = ?`).run(now, task.taskId);
|
|
528
|
+
appendTaskEvent(db, {
|
|
529
|
+
taskId: task.taskId,
|
|
530
|
+
agentTaskRef: null,
|
|
531
|
+
channelId: task.channelId,
|
|
532
|
+
taskNumber: task.taskNumber,
|
|
533
|
+
eventType: 'status_changed',
|
|
534
|
+
actorType: 'system',
|
|
535
|
+
actorName: 'system',
|
|
536
|
+
fromStatus: task.status,
|
|
537
|
+
toStatus: 'in_review',
|
|
538
|
+
claimedByAgentIdAfter: task.claimedByAgentId,
|
|
539
|
+
claimedByNameAfter: task.claimedByName,
|
|
540
|
+
messageId: task.messageId,
|
|
541
|
+
threadTarget: buildTaskEventThreadTarget({
|
|
542
|
+
sourceTarget: `#${conversationManager.getChannel(task.channelId)?.name ?? task.channelId}`,
|
|
543
|
+
messageId: task.messageId,
|
|
544
|
+
}),
|
|
545
|
+
createdAt: now,
|
|
546
|
+
});
|
|
547
|
+
statusLifecycleEvent = {
|
|
548
|
+
channelId: task.channelId,
|
|
549
|
+
taskId: task.taskId,
|
|
550
|
+
taskNumber: task.taskNumber,
|
|
551
|
+
title: task.title,
|
|
552
|
+
taskStatus: 'in_review',
|
|
553
|
+
taskAssigneeName: task.claimedByName,
|
|
554
|
+
fromStatus: task.status,
|
|
555
|
+
toStatus: 'in_review',
|
|
556
|
+
actorName: 'system',
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
})();
|
|
560
|
+
appendTaskLoopEvent(db, {
|
|
561
|
+
loopId: loop.loopId,
|
|
562
|
+
iterationIndex,
|
|
563
|
+
eventType: 'loop.succeeded',
|
|
564
|
+
payload: {
|
|
565
|
+
verifyChecks: verification.results,
|
|
566
|
+
},
|
|
567
|
+
createdAt: now,
|
|
568
|
+
});
|
|
569
|
+
params.onTaskStatusChanged?.(task.channelId);
|
|
570
|
+
if (statusLifecycleEvent) {
|
|
571
|
+
params.onTaskStatusLifecycle?.(statusLifecycleEvent);
|
|
572
|
+
}
|
|
573
|
+
params.onLoopChanged?.(task.channelId);
|
|
574
|
+
return;
|
|
575
|
+
}
|
|
576
|
+
const nextIterationIndex = iterationIndex + 1;
|
|
577
|
+
const canRetry = shouldRetry(loop, iterationIndex, loop.startedAt, now);
|
|
578
|
+
const requiresApprovalForNextIteration = canRetry && needsIterationApproval(loop);
|
|
579
|
+
db.transaction(() => {
|
|
580
|
+
db.prepare(`UPDATE task_loop_iterations
|
|
581
|
+
SET status = 'failed',
|
|
582
|
+
verify_checks_passed = 0,
|
|
583
|
+
verify_checks_output_json = ?,
|
|
584
|
+
failure_reason = ?,
|
|
585
|
+
decision = ?,
|
|
586
|
+
ended_at = ?,
|
|
587
|
+
updated_at = ?
|
|
588
|
+
WHERE iteration_id = ?`).run(JSON.stringify(verification.results), verification.failureReason, canRetry ? 'retry' : 'stop', now, now, iteration.iterationId);
|
|
589
|
+
if (canRetry) {
|
|
590
|
+
updateLoopStatus(db, {
|
|
591
|
+
loopId: loop.loopId,
|
|
592
|
+
status: requiresApprovalForNextIteration ? 'awaiting_approval' : 'running',
|
|
593
|
+
currentIteration: nextIterationIndex,
|
|
594
|
+
lastError: verification.failureReason,
|
|
595
|
+
});
|
|
596
|
+
ensureLoopIteration(db, loop.loopId, nextIterationIndex, now);
|
|
597
|
+
}
|
|
598
|
+
else {
|
|
599
|
+
const stopReason = loop.startedAt != null && now - loop.startedAt >= loop.maxTimeMs
|
|
600
|
+
? 'max_time'
|
|
601
|
+
: iterationIndex >= loop.maxIterations
|
|
602
|
+
? 'max_iterations'
|
|
603
|
+
: 'verify_failed';
|
|
604
|
+
updateLoopStatus(db, {
|
|
605
|
+
loopId: loop.loopId,
|
|
606
|
+
status: 'failed',
|
|
607
|
+
stopReason,
|
|
608
|
+
lastError: verification.failureReason,
|
|
609
|
+
completedAt: now,
|
|
610
|
+
});
|
|
611
|
+
}
|
|
612
|
+
})();
|
|
613
|
+
appendTaskLoopEvent(db, {
|
|
614
|
+
loopId: loop.loopId,
|
|
615
|
+
iterationIndex,
|
|
616
|
+
eventType: canRetry ? 'iteration.retry_scheduled' : 'loop.failed',
|
|
617
|
+
payload: {
|
|
618
|
+
reason: verification.failureReason,
|
|
619
|
+
verifyChecks: verification.results,
|
|
620
|
+
nextIterationIndex: canRetry ? nextIterationIndex : null,
|
|
621
|
+
requiresApproval: requiresApprovalForNextIteration,
|
|
622
|
+
},
|
|
623
|
+
createdAt: now,
|
|
624
|
+
});
|
|
625
|
+
if (requiresApprovalForNextIteration) {
|
|
626
|
+
appendTaskLoopEvent(db, {
|
|
627
|
+
loopId: loop.loopId,
|
|
628
|
+
iterationIndex: nextIterationIndex,
|
|
629
|
+
eventType: 'loop.awaiting_approval',
|
|
630
|
+
payload: {
|
|
631
|
+
approvalPolicy: loop.approvalPolicy,
|
|
632
|
+
scope: 'iteration',
|
|
633
|
+
iterationIndex: nextIterationIndex,
|
|
634
|
+
},
|
|
635
|
+
createdAt: now,
|
|
636
|
+
});
|
|
637
|
+
}
|
|
638
|
+
params.onLoopChanged?.(task.channelId);
|
|
639
|
+
}
|
|
640
|
+
export async function processDueTaskLoops(params) {
|
|
641
|
+
const now = params.now ?? Date.now();
|
|
642
|
+
const rows = params.db.prepare(`SELECT loop_id as loopId,
|
|
643
|
+
task_id as taskId,
|
|
644
|
+
worker_agent_id as workerAgentId,
|
|
645
|
+
root_conversation_id as rootConversationId,
|
|
646
|
+
worker_conversation_id as workerConversationId,
|
|
647
|
+
goal_prompt as goalPrompt,
|
|
648
|
+
verify_checks_json as verifyChecksJson,
|
|
649
|
+
max_iterations as maxIterations,
|
|
650
|
+
max_time_ms as maxTimeMs,
|
|
651
|
+
approval_policy as approvalPolicy,
|
|
652
|
+
status,
|
|
653
|
+
stop_reason as stopReason,
|
|
654
|
+
current_iteration as currentIteration,
|
|
655
|
+
last_error as lastError,
|
|
656
|
+
template_json as templateJson,
|
|
657
|
+
created_at as createdAt,
|
|
658
|
+
updated_at as updatedAt,
|
|
659
|
+
started_at as startedAt,
|
|
660
|
+
completed_at as completedAt,
|
|
661
|
+
stop_requested_at as stopRequestedAt
|
|
662
|
+
FROM task_loops
|
|
663
|
+
WHERE status IN ('pending', 'running')
|
|
664
|
+
AND started_at IS NOT NULL
|
|
665
|
+
ORDER BY created_at ASC`).all();
|
|
666
|
+
for (const row of rows) {
|
|
667
|
+
const loop = toTaskLoopInfo(row);
|
|
668
|
+
const task = loadTaskForLoop(params.db, loop.taskId);
|
|
669
|
+
if (!task) {
|
|
670
|
+
updateLoopStatus(params.db, {
|
|
671
|
+
loopId: loop.loopId,
|
|
672
|
+
status: 'cancelled',
|
|
673
|
+
stopReason: 'task_deleted',
|
|
674
|
+
completedAt: now,
|
|
675
|
+
});
|
|
676
|
+
appendTaskLoopEvent(params.db, {
|
|
677
|
+
loopId: loop.loopId,
|
|
678
|
+
eventType: 'loop.cancelled',
|
|
679
|
+
payload: { reason: 'task_deleted' },
|
|
680
|
+
createdAt: now,
|
|
681
|
+
});
|
|
682
|
+
continue;
|
|
683
|
+
}
|
|
684
|
+
await processRunningLoop({
|
|
685
|
+
db: params.db,
|
|
686
|
+
conversationManager: params.conversationManager,
|
|
687
|
+
nodeRegistry: params.nodeRegistry,
|
|
688
|
+
loop,
|
|
689
|
+
task,
|
|
690
|
+
now,
|
|
691
|
+
onTaskStatusChanged: params.onTaskStatusChanged,
|
|
692
|
+
onTaskStatusLifecycle: params.onTaskStatusLifecycle,
|
|
693
|
+
onLoopChanged: params.onLoopChanged,
|
|
694
|
+
});
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
export function startTaskLoopService(params) {
|
|
698
|
+
const intervalMs = Math.max(2_000, params.intervalMs ?? TASK_LOOP_POLL_INTERVAL_MS);
|
|
699
|
+
let timer = null;
|
|
700
|
+
let running = false;
|
|
701
|
+
const tick = async () => {
|
|
702
|
+
if (running)
|
|
703
|
+
return;
|
|
704
|
+
running = true;
|
|
705
|
+
try {
|
|
706
|
+
await processDueTaskLoops({
|
|
707
|
+
db: params.db,
|
|
708
|
+
conversationManager: params.conversationManager,
|
|
709
|
+
nodeRegistry: params.nodeRegistry,
|
|
710
|
+
onTaskStatusChanged: params.onTaskStatusChanged,
|
|
711
|
+
onTaskStatusLifecycle: params.onTaskStatusLifecycle,
|
|
712
|
+
onLoopChanged: params.onLoopChanged,
|
|
713
|
+
});
|
|
714
|
+
}
|
|
715
|
+
finally {
|
|
716
|
+
running = false;
|
|
717
|
+
}
|
|
718
|
+
};
|
|
719
|
+
timer = setInterval(() => {
|
|
720
|
+
void tick();
|
|
721
|
+
}, intervalMs);
|
|
722
|
+
timer.unref?.();
|
|
723
|
+
return {
|
|
724
|
+
tick,
|
|
725
|
+
stop: () => {
|
|
726
|
+
if (!timer)
|
|
727
|
+
return;
|
|
728
|
+
clearInterval(timer);
|
|
729
|
+
timer = null;
|
|
730
|
+
},
|
|
731
|
+
};
|
|
732
|
+
}
|