@adhdev/daemon-core 0.9.82-rc.8 → 0.9.82-rc.81

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.
Files changed (74) hide show
  1. package/dist/cli-adapters/provider-cli-adapter.d.ts +2 -0
  2. package/dist/cli-adapters/provider-cli-parse.d.ts +1 -0
  3. package/dist/cli-adapters/provider-cli-shared.d.ts +2 -0
  4. package/dist/commands/router.d.ts +22 -0
  5. package/dist/config/mesh-config.d.ts +66 -1
  6. package/dist/git/git-commands.d.ts +1 -0
  7. package/dist/git/git-status.d.ts +5 -0
  8. package/dist/git/git-types.d.ts +10 -0
  9. package/dist/index.d.ts +13 -6
  10. package/dist/index.js +5074 -1177
  11. package/dist/index.js.map +1 -1
  12. package/dist/index.mjs +5038 -1163
  13. package/dist/index.mjs.map +1 -1
  14. package/dist/installer.d.ts +1 -4
  15. package/dist/launch.d.ts +1 -1
  16. package/dist/logging/async-batch-writer.d.ts +10 -0
  17. package/dist/mesh/beads-db.d.ts +18 -0
  18. package/dist/mesh/mesh-active-work.d.ts +60 -0
  19. package/dist/mesh/mesh-events.d.ts +29 -5
  20. package/dist/mesh/mesh-fast-forward.d.ts +39 -0
  21. package/dist/mesh/mesh-host-ownership.d.ts +9 -0
  22. package/dist/mesh/mesh-ledger.d.ts +38 -1
  23. package/dist/mesh/mesh-work-queue.d.ts +27 -5
  24. package/dist/mesh/refine-config.d.ts +119 -0
  25. package/dist/providers/chat-message-normalization.d.ts +1 -0
  26. package/dist/providers/cli-provider-instance.d.ts +2 -1
  27. package/dist/repo-mesh-types.d.ts +39 -0
  28. package/dist/status/reporter.d.ts +2 -0
  29. package/package.json +3 -1
  30. package/src/boot/daemon-lifecycle.ts +1 -0
  31. package/src/cli-adapters/provider-cli-adapter.ts +91 -3
  32. package/src/cli-adapters/provider-cli-parse.d.ts +1 -0
  33. package/src/cli-adapters/provider-cli-parse.ts +4 -0
  34. package/src/cli-adapters/provider-cli-runtime.ts +3 -1
  35. package/src/cli-adapters/provider-cli-shared.d.ts +2 -0
  36. package/src/cli-adapters/provider-cli-shared.ts +20 -10
  37. package/src/commands/chat-commands.ts +310 -12
  38. package/src/commands/cli-manager.ts +101 -0
  39. package/src/commands/handler.ts +8 -1
  40. package/src/commands/mesh-coordinator.ts +13 -143
  41. package/src/commands/router.ts +2435 -414
  42. package/src/config/chat-history.ts +9 -7
  43. package/src/config/mesh-config.ts +244 -1
  44. package/src/daemon/dev-cli-debug.ts +10 -1
  45. package/src/detection/ide-detector.ts +26 -16
  46. package/src/git/git-commands.ts +3 -3
  47. package/src/git/git-status.ts +97 -6
  48. package/src/git/git-summary.ts +3 -0
  49. package/src/git/git-types.ts +11 -0
  50. package/src/index.ts +31 -5
  51. package/src/installer.d.ts +1 -1
  52. package/src/installer.ts +8 -6
  53. package/src/launch.d.ts +1 -1
  54. package/src/launch.ts +37 -28
  55. package/src/logging/async-batch-writer.ts +55 -0
  56. package/src/logging/logger.ts +2 -1
  57. package/src/mesh/beads-db.ts +176 -0
  58. package/src/mesh/coordinator-prompt.ts +27 -7
  59. package/src/mesh/mesh-active-work.ts +243 -0
  60. package/src/mesh/mesh-events.ts +398 -46
  61. package/src/mesh/mesh-fast-forward.ts +430 -0
  62. package/src/mesh/mesh-host-ownership.ts +73 -0
  63. package/src/mesh/mesh-ledger.ts +138 -1
  64. package/src/mesh/mesh-work-queue.ts +199 -137
  65. package/src/mesh/refine-config.ts +306 -0
  66. package/src/providers/chat-message-normalization.ts +3 -1
  67. package/src/providers/cli-provider-instance.ts +91 -13
  68. package/src/providers/ide-provider-instance.ts +17 -3
  69. package/src/providers/provider-loader.ts +10 -4
  70. package/src/providers/read-chat-contract.ts +1 -1
  71. package/src/providers/version-archive.ts +38 -20
  72. package/src/repo-mesh-types.ts +43 -0
  73. package/src/status/reporter.ts +15 -0
  74. 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 { getLedgerDir } from './mesh-ledger.js';
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
- function getQueuePath(meshId: string): string {
49
- const safe = meshId.replace(/[^a-zA-Z0-9_-]/g, '_');
50
- return join(getLedgerDir(), `${safe}.queue.json`);
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
- const path = getQueuePath(meshId);
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
- const path = getQueuePath(meshId);
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
- const queue = readQueue(meshId);
78
- const entry: MeshWorkQueueEntry = {
79
- id: randomUUID(),
80
- meshId,
81
- message,
82
- status: 'pending',
83
- targetNodeId: opts?.targetNodeId,
84
- targetSessionId: opts?.targetSessionId,
85
- createdAt: new Date().toISOString(),
86
- updatedAt: new Date().toISOString(),
87
- };
88
- queue.push(entry);
89
- writeQueue(meshId, queue);
90
- return entry;
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
- const queue = readQueue(meshId);
110
-
111
- // A worker must finish or fail its current queued assignment before it can
112
- // claim another one. maxParallelTasks limits total mesh concurrency; it is
113
- // not permission for one node/session to accumulate multiple assigned items.
114
- const hasActiveAssignment = queue.some(q => q.status === 'assigned' && (
115
- q.assignedSessionId === sessionId || q.assignedNodeId === nodeId
116
- ));
117
- if (hasActiveAssignment) return null;
118
-
119
- // Find highest priority task:
120
- // 1. Pending tasks explicitly targeted at this runtime session
121
- // 2. Pending tasks explicitly targeted at this node (but not another session)
122
- // 3. Pending tasks with no target node/session
123
- let targetIdx = queue.findIndex(q => q.status === 'pending' && q.targetSessionId === sessionId);
124
- if (targetIdx === -1) {
125
- targetIdx = queue.findIndex(q => q.status === 'pending' && q.targetNodeId === nodeId && !q.targetSessionId);
126
- }
127
- if (targetIdx === -1) {
128
- targetIdx = queue.findIndex(q => q.status === 'pending' && !q.targetNodeId && !q.targetSessionId);
129
- }
130
-
131
- if (targetIdx === -1) return null;
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
- const queue = readQueue(meshId);
154
- const idx = queue.findIndex(q => q.id === taskId);
155
- if (idx === -1) return null;
156
-
157
- queue[idx].status = status;
158
- queue[idx].updatedAt = new Date().toISOString();
159
- writeQueue(meshId, queue);
160
- return queue[idx];
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
- const queue = readQueue(meshId);
169
- const idx = queue.findIndex(q => q.id === taskId);
170
- if (idx === -1) return null;
171
- const now = new Date().toISOString();
172
- queue[idx].autoLaunch = {
173
- ...autoLaunch,
174
- updatedAt: now,
175
- };
176
- queue[idx].updatedAt = now;
177
- writeQueue(meshId, queue);
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
- const queue = readQueue(meshId);
190
- const idx = queue.findIndex(q => q.id === taskId);
191
- if (idx === -1) return null;
192
-
193
- const now = new Date().toISOString();
194
- queue[idx].status = 'cancelled';
195
- queue[idx].updatedAt = now;
196
- queue[idx].cancelledAt = now;
197
- if (opts?.reason) queue[idx].cancelReason = opts.reason;
198
- writeQueue(meshId, queue);
199
- return queue[idx];
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
- const queue = readQueue(meshId);
218
- const idx = queue.findIndex(q => q.id === taskId);
219
- if (idx === -1) return null;
220
-
221
- const entry = queue[idx];
222
- const now = new Date().toISOString();
223
- entry.status = 'pending';
224
- delete entry.assignedNodeId;
225
- delete entry.assignedSessionId;
226
- delete entry.cancelledAt;
227
- delete entry.cancelReason;
228
- if (opts?.clearTargetNode) delete entry.targetNodeId;
229
- if (typeof opts?.targetNodeId === 'string') entry.targetNodeId = opts.targetNodeId;
230
- if (opts?.clearTargetSession !== false) delete entry.targetSessionId;
231
- if (typeof opts?.targetSessionId === 'string') entry.targetSessionId = opts.targetSessionId;
232
- entry.updatedAt = now;
233
- entry.requeuedAt = now;
234
- entry.requeueCount = (entry.requeueCount || 0) + 1;
235
- if (opts?.reason) entry.requeueReason = opts.reason;
236
- writeQueue(meshId, queue);
237
- return entry;
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
- const queue = readQueue(meshId);
249
- // Collect all assigned tasks for this session, then pick the one with the
250
- // most recent dispatchTimestamp (or updatedAt fallback for legacy entries).
251
- // This prevents completing the wrong task when multiple tasks were assigned
252
- // to the same session in rapid succession.
253
- let bestIdx = -1;
254
- let bestTime = 0;
255
- for (let i = queue.length - 1; i >= 0; i--) {
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 > bestTime) {
259
- bestTime = time;
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
- if (bestIdx === -1) return null;
265
-
266
- queue[bestIdx].status = status;
267
- queue[bestIdx].updatedAt = new Date().toISOString();
268
- writeQueue(meshId, queue);
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
+ }