ultra-builder-pro-cli 0.23.0 → 0.24.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/.ultra-template/docs/research/README.md +4 -4
- package/CHANGELOG.md +45 -0
- package/README.md +21 -18
- package/adapters/_shared/codex-assets.cjs +121 -65
- package/adapters/_shared/interaction-contract.cjs +7 -1
- package/adapters/_shared/provenance.cjs +7 -2
- package/adapters/_shared/runtime-assets.cjs +3 -2
- package/adapters/_shared/runtime-native.cjs +132 -0
- package/adapters/claude.js +60 -30
- package/adapters/codex.js +178 -81
- package/adapters/grok.js +372 -0
- package/adapters/kimi.js +7 -3
- package/adapters/opencode.js +210 -32
- package/agents/code-reviewer.md +4 -2
- package/agents/debugger.md +4 -1
- package/agents/review-code.md +7 -3
- package/agents/review-comments.md +7 -4
- package/agents/review-coordinator.md +11 -7
- package/agents/review-design.md +8 -4
- package/agents/review-errors.md +8 -4
- package/agents/review-spec.md +8 -5
- package/agents/review-tests.md +7 -4
- package/agents/tdd-runner.md +5 -2
- package/bin/install.js +9 -4
- package/bin/orchestrator.js +4 -13
- package/docs/AGENT-CONTEXT.md +141 -229
- package/docs/ARCHITECTURE.md +203 -494
- package/docs/ARTIFACT-AUTHORITY.md +26 -22
- package/docs/PLUGIN-ISOLATION-CONTRACT.md +12 -9
- package/docs/ROADMAP.md +85 -110
- package/docs/RUNTIME-COMPAT-MATRIX.md +139 -175
- package/docs/SKILL-AUTHORING.md +3 -3
- package/docs/STATE-DB-ACCESS-POLICY.md +13 -8
- package/docs/WORKFLOW-LIFECYCLE.md +174 -448
- package/hooks/README.md +11 -8
- package/hooks/active_task_context.py +16 -16
- package/hooks/adapters/grok.py +134 -0
- package/hooks/{context_spine.py → context_envelope.py} +26 -22
- package/hooks/health_check.py +19 -29
- package/hooks/pre_stop_check.py +22 -12
- package/hooks/subagent_tracker.py +1 -1
- package/hooks/workflow_checkpoint.py +14 -13
- package/hooks/workflow_context.py +11 -12
- package/hooks/workflow_resume.py +14 -13
- package/mcp-server/{breadcrumb.cjs → hook-context.cjs} +7 -9
- package/mcp-server/lib/baseline-workflow.cjs +36 -38
- package/mcp-server/lib/canonical-json.cjs +19 -0
- package/mcp-server/lib/change-workflow.cjs +596 -44
- package/mcp-server/lib/context-envelope.cjs +508 -0
- package/mcp-server/lib/decision-records.cjs +337 -0
- package/mcp-server/lib/doctor.cjs +91 -33
- package/mcp-server/lib/init-project.cjs +78 -39
- package/mcp-server/lib/managed-file-write.cjs +116 -0
- package/mcp-server/lib/plan-checkpoint.cjs +177 -0
- package/mcp-server/lib/project-context.cjs +178 -0
- package/mcp-server/lib/stage-checkpoints.cjs +309 -0
- package/mcp-server/lib/state-db.cjs +155 -24
- package/mcp-server/lib/task-ledger.cjs +522 -13
- package/mcp-server/lib/ultra-facade.cjs +630 -442
- package/mcp-server/lib/worker-packet.cjs +358 -0
- package/mcp-server/server.cjs +200 -1103
- package/orchestrator/daemon.cjs +27 -9
- package/orchestrator/parallel-orchestrator.cjs +111 -76
- package/orchestrator/session-runner.cjs +135 -34
- package/package.json +10 -3
- package/skills/ultra-change/SKILL.md +9 -8
- package/skills/ultra-deliver/SKILL.md +3 -3
- package/skills/ultra-dev/SKILL.md +18 -11
- package/skills/ultra-doctor/SKILL.md +2 -2
- package/skills/ultra-init/SKILL.md +6 -5
- package/skills/ultra-init/references/brownfield-adoption.md +11 -8
- package/skills/ultra-plan/SKILL.md +10 -8
- package/skills/ultra-research/SKILL.md +12 -7
- package/skills/ultra-review/SKILL.md +9 -6
- package/skills/ultra-review/references/unified-schema.md +2 -0
- package/skills/ultra-review/scripts/review_wait.py +8 -0
- package/skills/ultra-status/SKILL.md +2 -2
- package/skills/ultra-test/SKILL.md +4 -2
- package/skills/ultra-think/SKILL.md +7 -7
- package/skills/ultra-think/references/interaction-boundary.md +56 -0
- package/spec/cli-protocol.md +80 -199
- package/spec/mcp-tools.yaml +37 -2804
- package/spec/schemas/state-db.sql +107 -4
- package/spec/schemas/{task-ledger.v1.schema.json → task-ledger.v2.schema.json} +70 -6
- package/ultra-tools/commands/status.cjs +369 -243
- package/mcp-server/lib/context-spine.cjs +0 -1208
- package/mcp-server/lib/decision-dialogue.cjs +0 -1128
- package/mcp-server/lib/project-breadcrumb.cjs +0 -173
- package/mcp-server/lib/spec-learning.cjs +0 -284
- package/mcp-server/lib/workflow-state.cjs +0 -3129
- package/skills/ultra-think/references/decision-dialogue.md +0 -155
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const Ajv = require('ajv');
|
|
4
|
+
|
|
5
|
+
const canonical = require('./canonical-json.cjs');
|
|
6
|
+
const contextEnvelope = require('./context-envelope.cjs');
|
|
7
|
+
const decisionRecords = require('./decision-records.cjs');
|
|
8
|
+
const { writeManagedJson } = require('./managed-file-write.cjs');
|
|
9
|
+
const ops = require('./state-ops.cjs');
|
|
10
|
+
const {
|
|
11
|
+
normalizeProjectRelative,
|
|
12
|
+
readStableProjectFile,
|
|
13
|
+
} = require('./safe-project-file.cjs');
|
|
14
|
+
const taskLedger = require('./task-ledger.cjs');
|
|
15
|
+
|
|
16
|
+
class WorkerPacketError extends Error {
|
|
17
|
+
constructor(code, message, details) {
|
|
18
|
+
super(message);
|
|
19
|
+
this.name = 'WorkerPacketError';
|
|
20
|
+
this.code = code;
|
|
21
|
+
if (details) this.details = details;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function requiredText(value, field) {
|
|
26
|
+
const normalized = typeof value === 'string' ? value.trim() : '';
|
|
27
|
+
if (!normalized) throw new WorkerPacketError('VALIDATION_ERROR', `${field} is required`);
|
|
28
|
+
return normalized;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function packetValueFromDocument(document) {
|
|
32
|
+
if (!document || typeof document !== 'object' || Array.isArray(document)) return null;
|
|
33
|
+
const { packet_digest: _packetDigest, ...value } = document;
|
|
34
|
+
return value;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function readWorkerPacket(db, id, { rootDir = process.cwd() } = {}) {
|
|
38
|
+
const row = db.prepare('SELECT * FROM worker_packets WHERE id = ?').get(id);
|
|
39
|
+
if (!row) {
|
|
40
|
+
throw new WorkerPacketError('WORKER_PACKET_NOT_FOUND', `Worker Packet not found: ${id}`);
|
|
41
|
+
}
|
|
42
|
+
const read = readStableProjectFile(rootDir, row.packet_path, { encoding: 'utf8' });
|
|
43
|
+
if (!row.file_digest || read.digest !== row.file_digest) {
|
|
44
|
+
throw new WorkerPacketError(
|
|
45
|
+
'WORKER_PACKET_FILE_DRIFT',
|
|
46
|
+
`Worker Packet bytes no longer match ${id}`,
|
|
47
|
+
{
|
|
48
|
+
path: row.packet_path,
|
|
49
|
+
expected: row.file_digest || null,
|
|
50
|
+
actual: read.digest,
|
|
51
|
+
},
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
let document;
|
|
55
|
+
try {
|
|
56
|
+
document = JSON.parse(read.text);
|
|
57
|
+
} catch (cause) {
|
|
58
|
+
throw new WorkerPacketError(
|
|
59
|
+
'WORKER_PACKET_INVALID',
|
|
60
|
+
`Worker Packet is not valid JSON: ${row.packet_path}`,
|
|
61
|
+
{ cause: cause.message },
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
const computed = canonical.digest(packetValueFromDocument(document));
|
|
65
|
+
if (document.packet_digest !== row.packet_digest || computed !== row.packet_digest) {
|
|
66
|
+
throw new WorkerPacketError(
|
|
67
|
+
'WORKER_PACKET_DIGEST_MISMATCH',
|
|
68
|
+
`Worker Packet authority does not match its database record: ${id}`,
|
|
69
|
+
{
|
|
70
|
+
expected: row.packet_digest,
|
|
71
|
+
document: document.packet_digest || null,
|
|
72
|
+
computed,
|
|
73
|
+
},
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
contextEnvelope.readEnvelope(db, row.context_envelope_id, { rootDir });
|
|
77
|
+
return {
|
|
78
|
+
...document,
|
|
79
|
+
id: row.id,
|
|
80
|
+
status: row.status,
|
|
81
|
+
packet_path: row.packet_path,
|
|
82
|
+
file_digest: row.file_digest,
|
|
83
|
+
context_digest: row.context_digest,
|
|
84
|
+
task_digest: row.task_digest,
|
|
85
|
+
decision_digest: row.decision_digest,
|
|
86
|
+
output_path: row.output_path,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function markWorkerPacketAssigned(db, id) {
|
|
91
|
+
return ops.tx(db, () => {
|
|
92
|
+
const row = db.prepare('SELECT * FROM worker_packets WHERE id = ?').get(id);
|
|
93
|
+
if (!row) {
|
|
94
|
+
throw new WorkerPacketError('WORKER_PACKET_NOT_FOUND', `Worker Packet not found: ${id}`);
|
|
95
|
+
}
|
|
96
|
+
if (row.status === 'assigned') return row;
|
|
97
|
+
if (row.status !== 'pending') {
|
|
98
|
+
throw new WorkerPacketError(
|
|
99
|
+
'WORKER_PACKET_STATUS_CONFLICT',
|
|
100
|
+
`Worker Packet ${id} is ${row.status}`,
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
db.prepare(
|
|
104
|
+
`UPDATE worker_packets
|
|
105
|
+
SET status = 'assigned',
|
|
106
|
+
assigned_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now'),
|
|
107
|
+
abandoned_at = NULL,
|
|
108
|
+
abandon_reason = NULL
|
|
109
|
+
WHERE id = ? AND status = 'pending'`,
|
|
110
|
+
).run(id);
|
|
111
|
+
ops.appendEventInTx(db, {
|
|
112
|
+
type: 'worker_packet_assigned',
|
|
113
|
+
task_id: row.scope_type === 'task' ? row.scope_id : null,
|
|
114
|
+
payload: { packet_id: id, packet_digest: row.packet_digest },
|
|
115
|
+
});
|
|
116
|
+
return db.prepare('SELECT * FROM worker_packets WHERE id = ?').get(id);
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function abandonWorkerPacket(db, id, reason) {
|
|
121
|
+
return ops.tx(db, () => {
|
|
122
|
+
const row = db.prepare('SELECT * FROM worker_packets WHERE id = ?').get(id);
|
|
123
|
+
if (!row || row.status === 'abandoned') return row || null;
|
|
124
|
+
if (row.status !== 'pending') return row;
|
|
125
|
+
db.prepare(
|
|
126
|
+
`UPDATE worker_packets
|
|
127
|
+
SET status = 'abandoned',
|
|
128
|
+
abandoned_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now'),
|
|
129
|
+
abandon_reason = ?
|
|
130
|
+
WHERE id = ? AND status = 'pending'`,
|
|
131
|
+
).run(String(reason || 'session acquisition failed'), id);
|
|
132
|
+
ops.appendEventInTx(db, {
|
|
133
|
+
type: 'worker_packet_abandoned',
|
|
134
|
+
task_id: row.scope_type === 'task' ? row.scope_id : null,
|
|
135
|
+
payload: {
|
|
136
|
+
packet_id: id,
|
|
137
|
+
packet_digest: row.packet_digest,
|
|
138
|
+
reason: String(reason || 'session acquisition failed'),
|
|
139
|
+
},
|
|
140
|
+
});
|
|
141
|
+
return db.prepare('SELECT * FROM worker_packets WHERE id = ?').get(id);
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function createWorkerPacket(db, input = {}, { rootDir = process.cwd() } = {}) {
|
|
146
|
+
const role = requiredText(input.role, 'role');
|
|
147
|
+
const taskId = requiredText(input.task_id, 'task_id');
|
|
148
|
+
const task = ops.readTask(db, taskId);
|
|
149
|
+
if (!task) throw new WorkerPacketError('TASK_NOT_FOUND', `task not found: ${taskId}`);
|
|
150
|
+
if (!task.change_id) {
|
|
151
|
+
throw new WorkerPacketError(
|
|
152
|
+
'TASK_CHANGE_REQUIRED',
|
|
153
|
+
`task ${taskId} must belong to an active Change`,
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
const change = db.prepare(
|
|
157
|
+
'SELECT id, artifact_root FROM changes WHERE id = ?',
|
|
158
|
+
).get(task.change_id);
|
|
159
|
+
if (!change) {
|
|
160
|
+
throw new WorkerPacketError('CHANGE_NOT_FOUND', `change not found: ${task.change_id}`);
|
|
161
|
+
}
|
|
162
|
+
for (const reference of task.context_refs || []) {
|
|
163
|
+
if (!reference?.required || !reference.expected_digest) continue;
|
|
164
|
+
const current = readStableProjectFile(rootDir, reference.ref, { encoding: null });
|
|
165
|
+
if (current.digest !== reference.expected_digest) {
|
|
166
|
+
throw new WorkerPacketError(
|
|
167
|
+
'CONTEXT_REQUIRED_REF_STALE',
|
|
168
|
+
`required Context reference changed after Plan acceptance: ${reference.ref}`,
|
|
169
|
+
{
|
|
170
|
+
ref: reference.ref,
|
|
171
|
+
expected_digest: reference.expected_digest,
|
|
172
|
+
actual_digest: current.digest,
|
|
173
|
+
},
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
const context = contextEnvelope.persistEnvelope(db, {
|
|
178
|
+
stage: role === 'review' ? 'review' : 'dev',
|
|
179
|
+
scope: { change_id: task.change_id, task_id: taskId },
|
|
180
|
+
}, { rootDir, runtime: input.runtime || 'unknown' });
|
|
181
|
+
const durableTask = taskLedger.durableTask(task);
|
|
182
|
+
const acceptedDecisions = [
|
|
183
|
+
...decisionRecords.listAcceptedDecisions(
|
|
184
|
+
db,
|
|
185
|
+
{ change_id: task.change_id },
|
|
186
|
+
{ limit: 200, rootDir, validateFiles: true },
|
|
187
|
+
),
|
|
188
|
+
];
|
|
189
|
+
const decisionDigest = canonical.digest(
|
|
190
|
+
acceptedDecisions.map((decision) => ({
|
|
191
|
+
id: decision.id,
|
|
192
|
+
digest: decision.digest,
|
|
193
|
+
status: decision.status,
|
|
194
|
+
})),
|
|
195
|
+
);
|
|
196
|
+
const outputPath = normalizeProjectRelative(requiredText(input.output_path, 'output_path'));
|
|
197
|
+
const packetValue = {
|
|
198
|
+
packet_version: '1.0',
|
|
199
|
+
role,
|
|
200
|
+
runtime: input.runtime || 'unknown',
|
|
201
|
+
scope: { change_id: task.change_id, task_id: taskId },
|
|
202
|
+
context_envelope: {
|
|
203
|
+
id: context.id,
|
|
204
|
+
path: context.artifact_path,
|
|
205
|
+
digest: context.digest,
|
|
206
|
+
},
|
|
207
|
+
accepted_decisions: acceptedDecisions.map((decision) => ({
|
|
208
|
+
id: decision.id,
|
|
209
|
+
selection: decision.selection,
|
|
210
|
+
effects: decision.effects,
|
|
211
|
+
artifact_path: decision.artifact_path,
|
|
212
|
+
digest: decision.digest,
|
|
213
|
+
})),
|
|
214
|
+
git: {
|
|
215
|
+
head: db.prepare(
|
|
216
|
+
`SELECT repository_revision FROM baselines
|
|
217
|
+
WHERE status <> 'superseded' ORDER BY updated_at DESC, rowid DESC LIMIT 1`,
|
|
218
|
+
).get()?.repository_revision || null,
|
|
219
|
+
diff_range: input.diff_range || null,
|
|
220
|
+
changed_files: Array.isArray(input.changed_files) ? input.changed_files : [],
|
|
221
|
+
},
|
|
222
|
+
task: {
|
|
223
|
+
id: task.id,
|
|
224
|
+
title: task.title,
|
|
225
|
+
outcome: task.outcome,
|
|
226
|
+
acceptance: task.acceptance,
|
|
227
|
+
verification_command: task.verification_command,
|
|
228
|
+
digest: durableTask.digest,
|
|
229
|
+
},
|
|
230
|
+
evidence_refs: Array.isArray(input.evidence_refs) ? input.evidence_refs : [],
|
|
231
|
+
output: {
|
|
232
|
+
path: outputPath,
|
|
233
|
+
schema: input.output_schema && typeof input.output_schema === 'object'
|
|
234
|
+
? input.output_schema
|
|
235
|
+
: { type: 'object', required: ['packet_digest'] },
|
|
236
|
+
},
|
|
237
|
+
};
|
|
238
|
+
const packetDigest = canonical.digest(packetValue);
|
|
239
|
+
const packet = { ...packetValue, packet_digest: packetDigest };
|
|
240
|
+
const id = `worker-${packetDigest.slice(0, 24)}`;
|
|
241
|
+
const packetPath = `.ultra/.runtime/worker-packets/${id}.json`;
|
|
242
|
+
const existing = db.prepare(
|
|
243
|
+
'SELECT * FROM worker_packets WHERE packet_digest = ?',
|
|
244
|
+
).get(packetDigest);
|
|
245
|
+
if (existing) {
|
|
246
|
+
readWorkerPacket(db, existing.id, { rootDir });
|
|
247
|
+
if (existing.status === 'abandoned') {
|
|
248
|
+
db.prepare(
|
|
249
|
+
`UPDATE worker_packets
|
|
250
|
+
SET status = 'pending', assigned_at = NULL, abandoned_at = NULL,
|
|
251
|
+
abandon_reason = NULL
|
|
252
|
+
WHERE id = ? AND status = 'abandoned'`,
|
|
253
|
+
).run(existing.id);
|
|
254
|
+
}
|
|
255
|
+
return {
|
|
256
|
+
...packet,
|
|
257
|
+
id: existing.id,
|
|
258
|
+
packet_path: existing.packet_path,
|
|
259
|
+
context_digest: existing.context_digest,
|
|
260
|
+
task_digest: existing.task_digest,
|
|
261
|
+
decision_digest: existing.decision_digest,
|
|
262
|
+
status: existing.status === 'abandoned' ? 'pending' : existing.status,
|
|
263
|
+
file_digest: existing.file_digest,
|
|
264
|
+
created: false,
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
const published = writeManagedJson(rootDir, packetPath, packet);
|
|
268
|
+
ops.tx(db, () => {
|
|
269
|
+
db.prepare(
|
|
270
|
+
`INSERT INTO worker_packets
|
|
271
|
+
(id, role, scope_type, scope_id, context_envelope_id, context_digest,
|
|
272
|
+
task_digest, decision_digest, packet_digest, file_digest, status,
|
|
273
|
+
packet_path, output_path)
|
|
274
|
+
VALUES (?, ?, 'task', ?, ?, ?, ?, ?, ?, ?, 'pending', ?, ?)`,
|
|
275
|
+
).run(
|
|
276
|
+
id,
|
|
277
|
+
role,
|
|
278
|
+
taskId,
|
|
279
|
+
context.id,
|
|
280
|
+
context.digest,
|
|
281
|
+
durableTask.digest,
|
|
282
|
+
decisionDigest,
|
|
283
|
+
packetDigest,
|
|
284
|
+
published.digest,
|
|
285
|
+
packetPath,
|
|
286
|
+
outputPath,
|
|
287
|
+
);
|
|
288
|
+
ops.appendEventInTx(db, {
|
|
289
|
+
type: 'worker_packet_created',
|
|
290
|
+
change_id: task.change_id,
|
|
291
|
+
task_id: taskId,
|
|
292
|
+
payload: {
|
|
293
|
+
packet_id: id,
|
|
294
|
+
packet_digest: packetDigest,
|
|
295
|
+
context_digest: context.digest,
|
|
296
|
+
task_digest: durableTask.digest,
|
|
297
|
+
output_path: outputPath,
|
|
298
|
+
},
|
|
299
|
+
});
|
|
300
|
+
});
|
|
301
|
+
return {
|
|
302
|
+
...packet,
|
|
303
|
+
id,
|
|
304
|
+
packet_path: packetPath,
|
|
305
|
+
context_digest: context.digest,
|
|
306
|
+
task_digest: durableTask.digest,
|
|
307
|
+
decision_digest: decisionDigest,
|
|
308
|
+
file_digest: published.digest,
|
|
309
|
+
status: 'pending',
|
|
310
|
+
created: true,
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function verifyWorkerResult(packet, result = {}) {
|
|
315
|
+
if (!packet || typeof packet !== 'object') {
|
|
316
|
+
throw new WorkerPacketError('VALIDATION_ERROR', 'packet is required');
|
|
317
|
+
}
|
|
318
|
+
if (result.packet_digest !== packet.packet_digest) {
|
|
319
|
+
throw new WorkerPacketError(
|
|
320
|
+
'WORKER_PACKET_DIGEST_MISMATCH',
|
|
321
|
+
'worker result does not reference the exact assigned packet',
|
|
322
|
+
{
|
|
323
|
+
expected: packet.packet_digest,
|
|
324
|
+
actual: result.packet_digest || null,
|
|
325
|
+
},
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
const schema = packet.output?.schema;
|
|
329
|
+
if (schema && typeof schema === 'object' && !Array.isArray(schema)) {
|
|
330
|
+
let validate;
|
|
331
|
+
try {
|
|
332
|
+
validate = new Ajv({ allErrors: true, strict: false }).compile(schema);
|
|
333
|
+
} catch (cause) {
|
|
334
|
+
throw new WorkerPacketError(
|
|
335
|
+
'WORKER_OUTPUT_SCHEMA_INVALID',
|
|
336
|
+
'worker packet contains an invalid output schema',
|
|
337
|
+
{ cause: cause.message },
|
|
338
|
+
);
|
|
339
|
+
}
|
|
340
|
+
if (!validate(result)) {
|
|
341
|
+
throw new WorkerPacketError(
|
|
342
|
+
'WORKER_OUTPUT_INVALID',
|
|
343
|
+
'worker result does not satisfy the assigned output schema',
|
|
344
|
+
{ errors: validate.errors || [] },
|
|
345
|
+
);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
return true;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
module.exports = {
|
|
352
|
+
WorkerPacketError,
|
|
353
|
+
createWorkerPacket,
|
|
354
|
+
readWorkerPacket,
|
|
355
|
+
markWorkerPacketAssigned,
|
|
356
|
+
abandonWorkerPacket,
|
|
357
|
+
verifyWorkerResult,
|
|
358
|
+
};
|