@adhdev/daemon-core 0.9.82-rc.7 → 0.9.82-rc.71
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/boot/daemon-lifecycle.d.ts +2 -0
- package/dist/cli-adapters/provider-cli-adapter.d.ts +2 -0
- package/dist/cli-adapters/provider-cli-parse.d.ts +1 -0
- package/dist/cli-adapters/provider-cli-shared.d.ts +2 -0
- package/dist/commands/router.d.ts +24 -0
- package/dist/config/mesh-config.d.ts +66 -1
- package/dist/git/git-commands.d.ts +1 -0
- package/dist/git/git-status.d.ts +5 -0
- package/dist/git/git-types.d.ts +10 -0
- package/dist/index.d.ts +13 -6
- package/dist/index.js +4888 -1149
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +4852 -1135
- package/dist/index.mjs.map +1 -1
- package/dist/installer.d.ts +1 -4
- package/dist/launch.d.ts +1 -1
- package/dist/logging/async-batch-writer.d.ts +10 -0
- package/dist/mesh/beads-db.d.ts +18 -0
- package/dist/mesh/mesh-active-work.d.ts +48 -0
- package/dist/mesh/mesh-events.d.ts +28 -5
- package/dist/mesh/mesh-fast-forward.d.ts +39 -0
- package/dist/mesh/mesh-host-ownership.d.ts +9 -0
- package/dist/mesh/mesh-ledger.d.ts +38 -1
- package/dist/mesh/mesh-work-queue.d.ts +27 -5
- package/dist/mesh/refine-config.d.ts +119 -0
- package/dist/providers/chat-message-normalization.d.ts +1 -0
- package/dist/providers/cli-provider-instance.d.ts +1 -0
- package/dist/repo-mesh-types.d.ts +160 -0
- package/dist/status/reporter.d.ts +2 -0
- package/package.json +3 -1
- package/src/boot/daemon-lifecycle.ts +4 -0
- package/src/cli-adapters/provider-cli-adapter.ts +91 -3
- package/src/cli-adapters/provider-cli-parse.d.ts +1 -0
- package/src/cli-adapters/provider-cli-parse.ts +4 -0
- package/src/cli-adapters/provider-cli-runtime.ts +3 -1
- package/src/cli-adapters/provider-cli-shared.d.ts +2 -0
- package/src/cli-adapters/provider-cli-shared.ts +20 -10
- package/src/commands/chat-commands.ts +242 -7
- package/src/commands/cli-manager.ts +19 -0
- package/src/commands/handler.ts +8 -1
- package/src/commands/mesh-coordinator.ts +13 -143
- package/src/commands/router.ts +2518 -408
- package/src/config/chat-history.ts +9 -7
- package/src/config/mesh-config.ts +244 -1
- package/src/daemon/dev-cli-debug.ts +10 -1
- package/src/detection/ide-detector.ts +26 -16
- package/src/git/git-commands.ts +3 -3
- package/src/git/git-status.ts +97 -6
- package/src/git/git-summary.ts +3 -0
- package/src/git/git-types.ts +11 -0
- package/src/index.ts +39 -5
- package/src/installer.d.ts +1 -1
- package/src/installer.ts +8 -6
- package/src/launch.d.ts +1 -1
- package/src/launch.ts +37 -28
- package/src/logging/async-batch-writer.ts +55 -0
- package/src/logging/logger.ts +2 -1
- package/src/mesh/beads-db.ts +176 -0
- package/src/mesh/coordinator-prompt.ts +5 -2
- package/src/mesh/mesh-active-work.ts +205 -0
- package/src/mesh/mesh-events.ts +291 -38
- package/src/mesh/mesh-fast-forward.ts +430 -0
- package/src/mesh/mesh-host-ownership.ts +73 -0
- package/src/mesh/mesh-ledger.ts +138 -1
- package/src/mesh/mesh-work-queue.ts +199 -137
- package/src/mesh/refine-config.ts +306 -0
- package/src/providers/chat-message-normalization.ts +3 -1
- package/src/providers/cli-provider-instance.ts +68 -1
- package/src/providers/ide-provider-instance.ts +17 -3
- package/src/providers/provider-loader.ts +10 -4
- package/src/providers/version-archive.ts +38 -20
- package/src/repo-mesh-types.ts +174 -0
- package/src/status/reporter.ts +15 -0
- package/src/system/host-memory.ts +29 -12
|
@@ -1,20 +1,68 @@
|
|
|
1
|
-
import { existsSync, writeFileSync, readFileSync } from 'fs';
|
|
2
|
-
import { join } from 'path';
|
|
3
1
|
import { randomUUID } from 'crypto';
|
|
4
|
-
import {
|
|
2
|
+
import { requireMeshHostQueueOwner } from './mesh-host-ownership.js';
|
|
3
|
+
import type { RepoMeshDaemonRole } from '../repo-mesh-types.js';
|
|
4
|
+
import { BeadsDB } from './beads-db.js';
|
|
5
5
|
|
|
6
6
|
export type MeshTaskStatus = 'pending' | 'assigned' | 'completed' | 'failed' | 'cancelled';
|
|
7
7
|
export type MeshActiveTaskStatus = Extract<MeshTaskStatus, 'pending' | 'assigned'>;
|
|
8
8
|
export type MeshHistoricalTaskStatus = Extract<MeshTaskStatus, 'completed' | 'failed' | 'cancelled'>;
|
|
9
|
+
export type MeshTaskMode = 'code_change' | 'validation' | 'live_debug_readonly' | 'launch_app' | 'convergence';
|
|
9
10
|
|
|
10
11
|
export const ACTIVE_MESH_QUEUE_STATUSES: MeshActiveTaskStatus[] = ['pending', 'assigned'];
|
|
11
12
|
export const HISTORICAL_MESH_QUEUE_STATUSES: MeshHistoricalTaskStatus[] = ['completed', 'failed', 'cancelled'];
|
|
13
|
+
export const MESH_TASK_MODES: MeshTaskMode[] = ['code_change', 'validation', 'live_debug_readonly', 'launch_app', 'convergence'];
|
|
14
|
+
|
|
15
|
+
export interface MeshTaskModeValidationResult {
|
|
16
|
+
valid: boolean;
|
|
17
|
+
taskMode?: MeshTaskMode;
|
|
18
|
+
violations: string[];
|
|
19
|
+
allowedOperations?: string[];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const LIVE_DEBUG_READONLY_FORBIDDEN: Array<{ label: string; pattern: RegExp }> = [
|
|
23
|
+
{ label: 'source_edit', pattern: /\b(edit|modify|patch|apply\s+patch|write\s+(?:to\s+)?(?:file|source)|overwrite|delete\s+file|remove\s+file|create\s+file|touch\s+file)\b/i },
|
|
24
|
+
{ label: 'git_mutation', pattern: /\b(?:git\s+(?:add|commit|push|reset|rebase|clean|checkout|switch|merge|tag|restore|rm|mv)|push\b)/i },
|
|
25
|
+
{ label: 'checkpoint', pattern: /\b(checkpoint|mesh_checkpoint)\b/i },
|
|
26
|
+
{ label: 'deploy_or_version_bump', pattern: /\b(deploy|wrangler\s+deploy|version[-\s]?bump|npm\s+version|release)\b/i },
|
|
27
|
+
{ label: 'destructive_shell', pattern: /\b(rm\s+-rf|mv\s+\S+\s+\S+|truncate\s|tee\s+\S+|sed\s+-i)\b/i },
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
export function normalizeMeshTaskMode(value: unknown): MeshTaskMode | undefined {
|
|
31
|
+
if (typeof value !== 'string') return undefined;
|
|
32
|
+
const normalized = value.trim() as MeshTaskMode;
|
|
33
|
+
return (MESH_TASK_MODES as string[]).includes(normalized) ? normalized : undefined;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function validateMeshTaskModeRequest(mode: unknown, message: string): MeshTaskModeValidationResult {
|
|
37
|
+
const taskMode = normalizeMeshTaskMode(mode);
|
|
38
|
+
if (!taskMode) {
|
|
39
|
+
return { valid: true, violations: [] };
|
|
40
|
+
}
|
|
41
|
+
if (taskMode !== 'live_debug_readonly') {
|
|
42
|
+
return { valid: true, taskMode, violations: [] };
|
|
43
|
+
}
|
|
44
|
+
const violations = LIVE_DEBUG_READONLY_FORBIDDEN
|
|
45
|
+
.filter(rule => rule.pattern.test(message || ''))
|
|
46
|
+
.map(rule => rule.label);
|
|
47
|
+
return {
|
|
48
|
+
valid: violations.length === 0,
|
|
49
|
+
taskMode,
|
|
50
|
+
violations,
|
|
51
|
+
allowedOperations: [
|
|
52
|
+
'process/log/window/port/session inspection',
|
|
53
|
+
'read-only filesystem listing/reading',
|
|
54
|
+
'status probes and keep-running handle reporting',
|
|
55
|
+
'diagnostic summaries without source edits, commits, checkpoints, pushes, deploys, resets, rebases, or destructive cleanups',
|
|
56
|
+
],
|
|
57
|
+
};
|
|
58
|
+
}
|
|
12
59
|
|
|
13
60
|
export interface MeshWorkQueueEntry {
|
|
14
61
|
id: string;
|
|
15
62
|
meshId: string;
|
|
16
63
|
message: string;
|
|
17
64
|
status: MeshTaskStatus;
|
|
65
|
+
taskMode?: MeshTaskMode;
|
|
18
66
|
/** If specified, only this node can claim the task (used by legacy mesh_send_task) */
|
|
19
67
|
targetNodeId?: string;
|
|
20
68
|
/** If specified, only this runtime session can claim the task */
|
|
@@ -45,25 +93,20 @@ export interface MeshWorkQueueEntry {
|
|
|
45
93
|
updatedAt: string;
|
|
46
94
|
}
|
|
47
95
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
96
|
+
export interface MeshQueueMutationOptions {
|
|
97
|
+
ownerRole?: RepoMeshDaemonRole;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function withQueueLock<T>(_meshId: string, fn: () => T): T {
|
|
101
|
+
return BeadsDB.getInstance().transaction(fn);
|
|
51
102
|
}
|
|
52
103
|
|
|
53
104
|
function readQueue(meshId: string): MeshWorkQueueEntry[] {
|
|
54
|
-
|
|
55
|
-
if (!existsSync(path)) return [];
|
|
56
|
-
try {
|
|
57
|
-
const content = readFileSync(path, 'utf-8');
|
|
58
|
-
return JSON.parse(content) as MeshWorkQueueEntry[];
|
|
59
|
-
} catch {
|
|
60
|
-
return [];
|
|
61
|
-
}
|
|
105
|
+
return BeadsDB.getInstance().getQueueEntries(meshId);
|
|
62
106
|
}
|
|
63
107
|
|
|
64
108
|
function writeQueue(meshId: string, queue: MeshWorkQueueEntry[]): void {
|
|
65
|
-
|
|
66
|
-
writeFileSync(path, JSON.stringify(queue, null, 2), 'utf-8');
|
|
109
|
+
BeadsDB.getInstance().replaceQueue(meshId, queue);
|
|
67
110
|
}
|
|
68
111
|
|
|
69
112
|
/**
|
|
@@ -72,22 +115,30 @@ function writeQueue(meshId: string, queue: MeshWorkQueueEntry[]): void {
|
|
|
72
115
|
export function enqueueTask(
|
|
73
116
|
meshId: string,
|
|
74
117
|
message: string,
|
|
75
|
-
opts?: { targetNodeId?: string; targetSessionId?: string }
|
|
118
|
+
opts?: { targetNodeId?: string; targetSessionId?: string; taskMode?: MeshTaskMode | string } & MeshQueueMutationOptions,
|
|
76
119
|
): MeshWorkQueueEntry {
|
|
77
|
-
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
120
|
+
requireMeshHostQueueOwner(opts);
|
|
121
|
+
const modeValidation = validateMeshTaskModeRequest(opts?.taskMode, message);
|
|
122
|
+
if (!modeValidation.valid) {
|
|
123
|
+
throw new Error(`live_debug_readonly_guardrail_violation: forbidden operations (${modeValidation.violations.join(', ')})`);
|
|
124
|
+
}
|
|
125
|
+
return withQueueLock(meshId, () => {
|
|
126
|
+
const queue = readQueue(meshId);
|
|
127
|
+
const entry: MeshWorkQueueEntry = {
|
|
128
|
+
id: randomUUID(),
|
|
129
|
+
meshId,
|
|
130
|
+
message,
|
|
131
|
+
status: 'pending',
|
|
132
|
+
taskMode: modeValidation.taskMode,
|
|
133
|
+
targetNodeId: opts?.targetNodeId,
|
|
134
|
+
targetSessionId: opts?.targetSessionId,
|
|
135
|
+
createdAt: new Date().toISOString(),
|
|
136
|
+
updatedAt: new Date().toISOString(),
|
|
137
|
+
};
|
|
138
|
+
queue.push(entry);
|
|
139
|
+
writeQueue(meshId, queue);
|
|
140
|
+
return entry;
|
|
141
|
+
});
|
|
91
142
|
}
|
|
92
143
|
|
|
93
144
|
/**
|
|
@@ -102,43 +153,37 @@ export function getQueue(meshId: string, opts?: { status?: MeshTaskStatus[] }):
|
|
|
102
153
|
return queue;
|
|
103
154
|
}
|
|
104
155
|
|
|
156
|
+
export function getMeshQueueRevision(meshId: string): string {
|
|
157
|
+
return BeadsDB.getInstance().getQueueRevision(meshId);
|
|
158
|
+
}
|
|
159
|
+
|
|
105
160
|
/**
|
|
106
161
|
* Find the next pending task that this node is allowed to claim, and mark it as assigned.
|
|
107
162
|
*/
|
|
108
163
|
export function claimNextTask(meshId: string, nodeId: string, sessionId: string): MeshWorkQueueEntry | null {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
q.
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
const entry = queue[targetIdx];
|
|
134
|
-
entry.status = 'assigned';
|
|
135
|
-
entry.assignedNodeId = nodeId;
|
|
136
|
-
entry.assignedSessionId = sessionId;
|
|
137
|
-
entry.dispatchTimestamp = new Date().toISOString();
|
|
138
|
-
entry.updatedAt = new Date().toISOString();
|
|
139
|
-
|
|
140
|
-
writeQueue(meshId, queue);
|
|
141
|
-
return entry;
|
|
164
|
+
return withQueueLock(meshId, () => {
|
|
165
|
+
const queue = readQueue(meshId);
|
|
166
|
+
const hasActiveAssignment = queue.some(q => q.status === 'assigned' && (
|
|
167
|
+
q.assignedSessionId === sessionId || q.assignedNodeId === nodeId
|
|
168
|
+
));
|
|
169
|
+
if (hasActiveAssignment) return null;
|
|
170
|
+
let targetIdx = queue.findIndex(q => q.status === 'pending' && q.targetSessionId === sessionId);
|
|
171
|
+
if (targetIdx === -1) {
|
|
172
|
+
targetIdx = queue.findIndex(q => q.status === 'pending' && q.targetNodeId === nodeId && !q.targetSessionId);
|
|
173
|
+
}
|
|
174
|
+
if (targetIdx === -1) {
|
|
175
|
+
targetIdx = queue.findIndex(q => q.status === 'pending' && !q.targetNodeId && !q.targetSessionId);
|
|
176
|
+
}
|
|
177
|
+
if (targetIdx === -1) return null;
|
|
178
|
+
const entry = queue[targetIdx];
|
|
179
|
+
entry.status = 'assigned';
|
|
180
|
+
entry.assignedNodeId = nodeId;
|
|
181
|
+
entry.assignedSessionId = sessionId;
|
|
182
|
+
entry.dispatchTimestamp = new Date().toISOString();
|
|
183
|
+
entry.updatedAt = new Date().toISOString();
|
|
184
|
+
writeQueue(meshId, queue);
|
|
185
|
+
return entry;
|
|
186
|
+
});
|
|
142
187
|
}
|
|
143
188
|
|
|
144
189
|
/**
|
|
@@ -149,15 +194,18 @@ export function updateTaskStatus(
|
|
|
149
194
|
meshId: string,
|
|
150
195
|
taskId: string,
|
|
151
196
|
status: MeshTaskStatus,
|
|
197
|
+
opts?: MeshQueueMutationOptions,
|
|
152
198
|
): MeshWorkQueueEntry | null {
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
199
|
+
requireMeshHostQueueOwner(opts);
|
|
200
|
+
return withQueueLock(meshId, () => {
|
|
201
|
+
const queue = readQueue(meshId);
|
|
202
|
+
const idx = queue.findIndex(q => q.id === taskId);
|
|
203
|
+
if (idx === -1) return null;
|
|
204
|
+
queue[idx].status = status;
|
|
205
|
+
queue[idx].updatedAt = new Date().toISOString();
|
|
206
|
+
writeQueue(meshId, queue);
|
|
207
|
+
return queue[idx];
|
|
208
|
+
});
|
|
161
209
|
}
|
|
162
210
|
|
|
163
211
|
export function recordTaskAutoLaunch(
|
|
@@ -165,17 +213,16 @@ export function recordTaskAutoLaunch(
|
|
|
165
213
|
taskId: string,
|
|
166
214
|
autoLaunch: Omit<NonNullable<MeshWorkQueueEntry['autoLaunch']>, 'updatedAt'>,
|
|
167
215
|
): MeshWorkQueueEntry | null {
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
...autoLaunch,
|
|
174
|
-
updatedAt
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
return queue[idx];
|
|
216
|
+
return withQueueLock(meshId, () => {
|
|
217
|
+
const queue = readQueue(meshId);
|
|
218
|
+
const idx = queue.findIndex(q => q.id === taskId);
|
|
219
|
+
if (idx === -1) return null;
|
|
220
|
+
const now = new Date().toISOString();
|
|
221
|
+
queue[idx].autoLaunch = { ...autoLaunch, updatedAt: now };
|
|
222
|
+
queue[idx].updatedAt = now;
|
|
223
|
+
writeQueue(meshId, queue);
|
|
224
|
+
return queue[idx];
|
|
225
|
+
});
|
|
179
226
|
}
|
|
180
227
|
|
|
181
228
|
/**
|
|
@@ -184,19 +231,21 @@ export function recordTaskAutoLaunch(
|
|
|
184
231
|
export function cancelTask(
|
|
185
232
|
meshId: string,
|
|
186
233
|
taskId: string,
|
|
187
|
-
opts?: { reason?: string },
|
|
234
|
+
opts?: { reason?: string } & MeshQueueMutationOptions,
|
|
188
235
|
): MeshWorkQueueEntry | null {
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
236
|
+
requireMeshHostQueueOwner(opts);
|
|
237
|
+
return withQueueLock(meshId, () => {
|
|
238
|
+
const queue = readQueue(meshId);
|
|
239
|
+
const idx = queue.findIndex(q => q.id === taskId);
|
|
240
|
+
if (idx === -1) return null;
|
|
241
|
+
const now = new Date().toISOString();
|
|
242
|
+
queue[idx].status = 'cancelled';
|
|
243
|
+
queue[idx].updatedAt = now;
|
|
244
|
+
queue[idx].cancelledAt = now;
|
|
245
|
+
if (opts?.reason) queue[idx].cancelReason = opts.reason;
|
|
246
|
+
writeQueue(meshId, queue);
|
|
247
|
+
return queue[idx];
|
|
248
|
+
});
|
|
200
249
|
}
|
|
201
250
|
|
|
202
251
|
/**
|
|
@@ -212,29 +261,31 @@ export function requeueTask(
|
|
|
212
261
|
targetSessionId?: string;
|
|
213
262
|
clearTargetNode?: boolean;
|
|
214
263
|
clearTargetSession?: boolean;
|
|
215
|
-
},
|
|
264
|
+
} & MeshQueueMutationOptions,
|
|
216
265
|
): MeshWorkQueueEntry | null {
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
266
|
+
requireMeshHostQueueOwner(opts);
|
|
267
|
+
return withQueueLock(meshId, () => {
|
|
268
|
+
const queue = readQueue(meshId);
|
|
269
|
+
const idx = queue.findIndex(q => q.id === taskId);
|
|
270
|
+
if (idx === -1) return null;
|
|
271
|
+
const entry = queue[idx];
|
|
272
|
+
const now = new Date().toISOString();
|
|
273
|
+
entry.status = 'pending';
|
|
274
|
+
delete entry.assignedNodeId;
|
|
275
|
+
delete entry.assignedSessionId;
|
|
276
|
+
delete entry.cancelledAt;
|
|
277
|
+
delete entry.cancelReason;
|
|
278
|
+
if (opts?.clearTargetNode) delete entry.targetNodeId;
|
|
279
|
+
if (typeof opts?.targetNodeId === 'string') entry.targetNodeId = opts.targetNodeId;
|
|
280
|
+
if (opts?.clearTargetSession !== false) delete entry.targetSessionId;
|
|
281
|
+
if (typeof opts?.targetSessionId === 'string') entry.targetSessionId = opts.targetSessionId;
|
|
282
|
+
entry.updatedAt = now;
|
|
283
|
+
entry.requeuedAt = now;
|
|
284
|
+
entry.requeueCount = (entry.requeueCount || 0) + 1;
|
|
285
|
+
if (opts?.reason) entry.requeueReason = opts.reason;
|
|
286
|
+
writeQueue(meshId, queue);
|
|
287
|
+
return entry;
|
|
288
|
+
});
|
|
238
289
|
}
|
|
239
290
|
|
|
240
291
|
/**
|
|
@@ -244,29 +295,26 @@ export function updateSessionTaskStatus(
|
|
|
244
295
|
meshId: string,
|
|
245
296
|
sessionId: string,
|
|
246
297
|
status: MeshTaskStatus,
|
|
298
|
+
opts?: { occurredAt?: string },
|
|
247
299
|
): MeshWorkQueueEntry | null {
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
if (queue[i].assignedSessionId === sessionId && queue[i].status === 'assigned') {
|
|
300
|
+
return withQueueLock(meshId, () => {
|
|
301
|
+
const queue = readQueue(meshId);
|
|
302
|
+
const occurredAtTime = opts?.occurredAt ? new Date(opts.occurredAt).getTime() : Number.NaN;
|
|
303
|
+
const hasOccurredAt = Number.isFinite(occurredAtTime);
|
|
304
|
+
let bestIdx = -1;
|
|
305
|
+
let bestTime = 0;
|
|
306
|
+
for (let i = queue.length - 1; i >= 0; i--) {
|
|
307
|
+
if (queue[i].assignedSessionId !== sessionId || queue[i].status !== 'assigned') continue;
|
|
257
308
|
const time = new Date(queue[i].dispatchTimestamp || queue[i].updatedAt).getTime();
|
|
258
|
-
if (time >
|
|
259
|
-
|
|
260
|
-
bestIdx = i;
|
|
261
|
-
}
|
|
309
|
+
if (hasOccurredAt && Number.isFinite(time) && time > occurredAtTime) continue;
|
|
310
|
+
if (time > bestTime) { bestTime = time; bestIdx = i; }
|
|
262
311
|
}
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
return queue[bestIdx];
|
|
312
|
+
if (bestIdx === -1) return null;
|
|
313
|
+
queue[bestIdx].status = status;
|
|
314
|
+
queue[bestIdx].updatedAt = new Date().toISOString();
|
|
315
|
+
writeQueue(meshId, queue);
|
|
316
|
+
return queue[bestIdx];
|
|
317
|
+
});
|
|
270
318
|
}
|
|
271
319
|
|
|
272
320
|
export interface MeshWorkQueueStats {
|
|
@@ -328,3 +376,17 @@ export function getMeshQueueStats(meshId: string): MeshWorkQueueStats {
|
|
|
328
376
|
})),
|
|
329
377
|
};
|
|
330
378
|
}
|
|
379
|
+
|
|
380
|
+
export function __replaceMeshQueueForTests(meshId: string, queue: MeshWorkQueueEntry[]): void {
|
|
381
|
+
BeadsDB.getInstance().transaction(() => {
|
|
382
|
+
BeadsDB.getInstance().replaceQueue(meshId, queue);
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
export function __clearMeshQueueForTests(meshId: string): void {
|
|
387
|
+
BeadsDB.getInstance().deleteQueue(meshId);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
export function __resetBeadsDBForTests(): void {
|
|
391
|
+
BeadsDB.resetForTests();
|
|
392
|
+
}
|