@smartmemory/compose 0.2.6-beta → 0.2.8-beta
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/bin/compose.js +45 -3
- package/bin/git-hooks/pre-push.template +41 -13
- package/contracts/gsd-stuck.json +141 -0
- package/dist/assets/{App-j8fWZcGr.js → App-D3ehVPvi.js} +4 -4
- package/dist/assets/{arc-BFqOo_jJ.js → arc-Dmf69iHG.js} +1 -1
- package/dist/assets/{architectureDiagram-3BPJPVTR-D722w0RE.js → architectureDiagram-3BPJPVTR-xYo993Yw.js} +1 -1
- package/dist/assets/{blockDiagram-GPEHLZMM-B4w0mOAJ.js → blockDiagram-GPEHLZMM-UX4EF98O.js} +1 -1
- package/dist/assets/{c4Diagram-AAUBKEIU-D6LE8-j8.js → c4Diagram-AAUBKEIU-DaP9CGWb.js} +1 -1
- package/dist/assets/channel-D_RXsFFT.js +1 -0
- package/dist/assets/{chunk-2J33WTMH-CrazA7xu.js → chunk-2J33WTMH-CKk_RN3A.js} +1 -1
- package/dist/assets/{chunk-4BX2VUAB-Cp90GiCM.js → chunk-4BX2VUAB-DboAwYKw.js} +1 -1
- package/dist/assets/{chunk-55IACEB6-Bnais1SK.js → chunk-55IACEB6-Dsy9RYvI.js} +1 -1
- package/dist/assets/{chunk-727SXJPM-kD07Sqp5.js → chunk-727SXJPM-fAH0QO9v.js} +1 -1
- package/dist/assets/{chunk-AQP2D5EJ-DmIxhJc8.js → chunk-AQP2D5EJ-DyZYerFP.js} +1 -1
- package/dist/assets/{chunk-FMBD7UC4-Jti_und8.js → chunk-FMBD7UC4-BnboGO5t.js} +1 -1
- package/dist/assets/{chunk-ND2GUHAM-Ipx3noKz.js → chunk-ND2GUHAM-Di9tYXme.js} +1 -1
- package/dist/assets/{chunk-QZHKN3VN-CeblRnPF.js → chunk-QZHKN3VN-zRPRlAIL.js} +1 -1
- package/dist/assets/classDiagram-4FO5ZUOK-K6wdB4ic.js +1 -0
- package/dist/assets/classDiagram-v2-Q7XG4LA2-K6wdB4ic.js +1 -0
- package/dist/assets/{cose-bilkent-S5V4N54A-fNQlSmHt.js → cose-bilkent-S5V4N54A-C7Hqukaf.js} +1 -1
- package/dist/assets/{dagre-BM42HDAG-D27D6YAL.js → dagre-BM42HDAG-B-cR-BjI.js} +1 -1
- package/dist/assets/{diagram-2AECGRRQ-CtXeohzN.js → diagram-2AECGRRQ-B6-5onDk.js} +1 -1
- package/dist/assets/{diagram-5GNKFQAL-C_BqZkx0.js → diagram-5GNKFQAL-DoZZgFAM.js} +1 -1
- package/dist/assets/{diagram-KO2AKTUF-B29ynQz4.js → diagram-KO2AKTUF-77jEGlJh.js} +1 -1
- package/dist/assets/{diagram-LMA3HP47-DAYJMc2I.js → diagram-LMA3HP47-D3S7XDRD.js} +1 -1
- package/dist/assets/{diagram-OG6HWLK6-CBJMis3l.js → diagram-OG6HWLK6-KbYL9aCY.js} +1 -1
- package/dist/assets/{erDiagram-TEJ5UH35-nd3GWiPn.js → erDiagram-TEJ5UH35-DezFbJP-.js} +1 -1
- package/dist/assets/{flowDiagram-I6XJVG4X-HFUno_nV.js → flowDiagram-I6XJVG4X-4x31cK9j.js} +1 -1
- package/dist/assets/{ganttDiagram-6RSMTGT7-CPPAAjwR.js → ganttDiagram-6RSMTGT7-FopfSTyZ.js} +1 -1
- package/dist/assets/{gitGraphDiagram-PVQCEYII-NBq1F6K2.js → gitGraphDiagram-PVQCEYII-DSiQGKbN.js} +1 -1
- package/dist/assets/graph-Cs_vqCR0.js +331 -0
- package/dist/assets/{index-uHKnp74B.js → index-ClX6LVAf.js} +2 -2
- package/dist/assets/{infoDiagram-5YYISTIA-D-TOBtCq.js → infoDiagram-5YYISTIA-DE6BqzK_.js} +1 -1
- package/dist/assets/{ishikawaDiagram-YF4QCWOH-nXOztZiZ.js → ishikawaDiagram-YF4QCWOH-Dml8NwQI.js} +1 -1
- package/dist/assets/{journeyDiagram-JHISSGLW-Bko3tTdh.js → journeyDiagram-JHISSGLW-CwWeJgjE.js} +1 -1
- package/dist/assets/{kanban-definition-UN3LZRKU-1e-7i8st.js → kanban-definition-UN3LZRKU-DnG956Wh.js} +1 -1
- package/dist/assets/{linear-Dx5ZJB7F.js → linear-CA3N7Rpi.js} +1 -1
- package/dist/assets/{mindmap-definition-RKZ34NQL-CNwNkDqN.js → mindmap-definition-RKZ34NQL-CxfIOjLX.js} +1 -1
- package/dist/assets/{pieDiagram-4H26LBE5-C5fvCej-.js → pieDiagram-4H26LBE5-O7aIwy1x.js} +1 -1
- package/dist/assets/{quadrantDiagram-W4KKPZXB-4NoQsF61.js → quadrantDiagram-W4KKPZXB-CPQ2qq7c.js} +1 -1
- package/dist/assets/{requirementDiagram-4Y6WPE33-q5WxB9LO.js → requirementDiagram-4Y6WPE33-C23horL4.js} +1 -1
- package/dist/assets/{sankeyDiagram-5OEKKPKP-DlQNB367.js → sankeyDiagram-5OEKKPKP-DPY04kOW.js} +1 -1
- package/dist/assets/{sequenceDiagram-3UESZ5HK-BzHclOKt.js → sequenceDiagram-3UESZ5HK-BKaTfIvo.js} +1 -1
- package/dist/assets/{stateDiagram-AJRCARHV-BvWRI9zK.js → stateDiagram-AJRCARHV-B9na_6mY.js} +1 -1
- package/dist/assets/stateDiagram-v2-BHNVJYJU-Cf84VDiH.js +1 -0
- package/dist/assets/{timeline-definition-PNZ67QCA-j2wKjAti.js → timeline-definition-PNZ67QCA-BBWPqd7X.js} +1 -1
- package/dist/assets/{vennDiagram-CIIHVFJN-B77g7htC.js → vennDiagram-CIIHVFJN-tWqiHsOZ.js} +1 -1
- package/dist/assets/{wardley-L42UT6IY-83Im2mo2.js → wardley-L42UT6IY-DorxG6os.js} +1 -1
- package/dist/assets/{wardleyDiagram-YWT4CUSO-CK-XB-bO.js → wardleyDiagram-YWT4CUSO-B49f8GzW.js} +1 -1
- package/dist/assets/{xychartDiagram-2RQKCTM6-D42FcVOY.js → xychartDiagram-2RQKCTM6-BgKSj8Qb.js} +1 -1
- package/dist/index.html +1 -1
- package/lib/budget-ledger.js +84 -0
- package/lib/build-stream-schema.js +5 -3
- package/lib/build.js +91 -2
- package/lib/feature-validator.js +40 -8
- package/lib/gsd-budget.js +205 -0
- package/lib/gsd-stuck.js +275 -0
- package/lib/gsd.js +499 -8
- package/lib/result-normalizer.js +5 -1
- package/package.json +2 -2
- package/server/agent-spawn.js +7 -1
- package/server/compose-mcp-tools.js +103 -1
- package/server/compose-mcp.js +34 -4
- package/server/design-routes.js +4 -1
- package/server/mcp-tool-policy.js +112 -0
- package/dist/assets/channel-BD-5_hPW.js +0 -1
- package/dist/assets/classDiagram-4FO5ZUOK-mSW5R7DY.js +0 -1
- package/dist/assets/classDiagram-v2-Q7XG4LA2-mSW5R7DY.js +0 -1
- package/dist/assets/graph-CJVNlri5.js +0 -331
- package/dist/assets/stateDiagram-v2-BHNVJYJU-CDlF0VA8.js +0 -1
package/lib/gsd-stuck.js
ADDED
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* gsd-stuck.js — GsdStuckDetector for COMP-GSD-5.
|
|
3
|
+
*
|
|
4
|
+
* Detects, in real time during per-task `compose gsd` dispatch, that an agent
|
|
5
|
+
* is spinning, and emits a structured verdict so the run loop can halt cleanly.
|
|
6
|
+
*
|
|
7
|
+
* Four signals (thresholds tunable via constructor opts; defaults 3/3/8/600000):
|
|
8
|
+
* - same_file: one file_path edited >= sameFileEdits times.
|
|
9
|
+
* - error_recurrence: a normalized error hash recurs >= errorRepeats.
|
|
10
|
+
* - no_progress: >= noProgressCalls consecutive non-file-changing tool calls.
|
|
11
|
+
* - wall_clock: nowMs - startedAt(taskId) >= wallClockMs.
|
|
12
|
+
*
|
|
13
|
+
* The same-file signal REUSES FixChainDetector (lib/debug-discipline.js) for its
|
|
14
|
+
* per-key file-hit counting — keyed here by taskId. Error-recurrence and
|
|
15
|
+
* no-progress are the only new bookkeeping.
|
|
16
|
+
*
|
|
17
|
+
* Consumes BuildStreamEvents from stratum.onEvent inside
|
|
18
|
+
* executeParallelDispatchServer, keyed by event.task_id. gsd runs the execute
|
|
19
|
+
* step max_concurrent:1, so per-task state is unambiguous. Telemetry contract
|
|
20
|
+
* (schema 0.2.7, STRAT-PAR-STREAM-TOOLDETAIL):
|
|
21
|
+
* tool_use_summary.metadata = { tool, summary, ok, duration_ms, input, tool_use_id }
|
|
22
|
+
* input.file_path present for Edit/Write/MultiEdit/Read
|
|
23
|
+
* tool_result.metadata = { tool_use_id, ok, output }
|
|
24
|
+
*
|
|
25
|
+
* See: docs/features/COMP-GSD-5/{design,blueprint,plan}.md
|
|
26
|
+
* contracts/gsd-stuck.json (`stuck` diagnostic shape)
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
import { createHash } from 'node:crypto';
|
|
30
|
+
import { FixChainDetector } from './debug-discipline.js';
|
|
31
|
+
|
|
32
|
+
// Default thresholds (Decision 4 in design.md).
|
|
33
|
+
export const DEFAULT_THRESHOLDS = Object.freeze({
|
|
34
|
+
sameFileEdits: 3,
|
|
35
|
+
errorRepeats: 3,
|
|
36
|
+
noProgressCalls: 8,
|
|
37
|
+
wallClockMs: 600000,
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// Tools that change files on disk — they reset no-progress and feed same-file.
|
|
41
|
+
// Read is deliberately excluded: it touches a file_path but makes no change.
|
|
42
|
+
const FILE_CHANGING_TOOLS = new Set(['Edit', 'Write', 'MultiEdit']);
|
|
43
|
+
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
// Error normalization — collapse cosmetic diffs (volatile paths, line:col
|
|
46
|
+
// numbers, whitespace, hex/temp ids) so the SAME logical failure hashes the
|
|
47
|
+
// same across repeats.
|
|
48
|
+
// ---------------------------------------------------------------------------
|
|
49
|
+
|
|
50
|
+
export function normalizeError(output) {
|
|
51
|
+
if (output == null) return '';
|
|
52
|
+
let s = String(output);
|
|
53
|
+
// Absolute/relative file paths -> a stable token. Catches /Users/..,
|
|
54
|
+
// /tmp/.., /var/.., ./rel/path, C:\... etc. up to a :line:col or space.
|
|
55
|
+
s = s.replace(/(?:[A-Za-z]:)?(?:\/|\\)[^\s:]+(?:[/\\][^\s:]+)*/g, '<path>');
|
|
56
|
+
// Bare relative module-ish paths (a/b/c.js) that didn't start with a slash.
|
|
57
|
+
s = s.replace(/\b[\w.-]+(?:\/[\w.-]+)+\.\w+\b/g, '<path>');
|
|
58
|
+
// line:col suffixes (e.g. :12:5 or :12).
|
|
59
|
+
s = s.replace(/:\d+(?::\d+)?\b/g, ':<n>');
|
|
60
|
+
// Standalone long digit runs (ids, ports, offsets) and hex blobs.
|
|
61
|
+
s = s.replace(/0x[0-9a-fA-F]+/g, '<hex>');
|
|
62
|
+
s = s.replace(/\b\d{2,}\b/g, '<n>');
|
|
63
|
+
// Collapse all whitespace (incl. the em-dash-adjacent spacing) to single spaces.
|
|
64
|
+
s = s.replace(/\s+/g, ' ').trim().toLowerCase();
|
|
65
|
+
return s;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function hashError(output) {
|
|
69
|
+
return createHash('sha1').update(normalizeError(output)).digest('hex').slice(0, 16);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// ---------------------------------------------------------------------------
|
|
73
|
+
// GsdStuckDetector
|
|
74
|
+
// ---------------------------------------------------------------------------
|
|
75
|
+
|
|
76
|
+
export class GsdStuckDetector {
|
|
77
|
+
constructor(opts = {}) {
|
|
78
|
+
this.sameFileEdits = opts.sameFileEdits ?? DEFAULT_THRESHOLDS.sameFileEdits;
|
|
79
|
+
this.errorRepeats = opts.errorRepeats ?? DEFAULT_THRESHOLDS.errorRepeats;
|
|
80
|
+
this.noProgressCalls = opts.noProgressCalls ?? DEFAULT_THRESHOLDS.noProgressCalls;
|
|
81
|
+
this.wallClockMs = opts.wallClockMs ?? DEFAULT_THRESHOLDS.wallClockMs;
|
|
82
|
+
|
|
83
|
+
// same-file: reuse FixChainDetector's per-key file-hit counter, keyed by taskId.
|
|
84
|
+
this._fixChain = new FixChainDetector();
|
|
85
|
+
// error-recurrence: per-task Map<normalizedHash, count>.
|
|
86
|
+
/** @type {Map<string, Map<string, number>>} */
|
|
87
|
+
this._errorHits = new Map();
|
|
88
|
+
// no-progress: per-task consecutive non-file-changing call count.
|
|
89
|
+
/** @type {Map<string, number>} */
|
|
90
|
+
this._noProgress = new Map();
|
|
91
|
+
// wall-clock baseline per task.
|
|
92
|
+
/** @type {Map<string, number>} */
|
|
93
|
+
this._startedAt = new Map();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** Mark a task's dispatch start — establishes the wall-clock baseline. */
|
|
97
|
+
startTask(taskId, nowMs) {
|
|
98
|
+
if (!taskId) return;
|
|
99
|
+
if (!this._startedAt.has(taskId)) {
|
|
100
|
+
this._startedAt.set(taskId, nowMs);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Route a BuildStreamEvent into per-task state. Only tool_use_summary and
|
|
106
|
+
* tool_result are meaningful; everything else (and any untagged event) is
|
|
107
|
+
* ignored. Keyed by event.task_id.
|
|
108
|
+
*/
|
|
109
|
+
record(event) {
|
|
110
|
+
if (!event || typeof event !== 'object') return;
|
|
111
|
+
const taskId = event.task_id;
|
|
112
|
+
if (!taskId) return; // gsd is max_concurrent:1 but be defensive about attribution.
|
|
113
|
+
const md = event.metadata ?? {};
|
|
114
|
+
|
|
115
|
+
if (event.kind === 'tool_use_summary') {
|
|
116
|
+
const tool = md.tool;
|
|
117
|
+
const filePath = md.input?.file_path;
|
|
118
|
+
if (FILE_CHANGING_TOOLS.has(tool)) {
|
|
119
|
+
// same-file: count the file hit (reuse FixChainDetector per-key counter).
|
|
120
|
+
if (filePath) {
|
|
121
|
+
this._fixChain.recordIterationForBug(taskId, [filePath]);
|
|
122
|
+
}
|
|
123
|
+
// no-progress: a file-changing tool resets the consecutive run.
|
|
124
|
+
this._noProgress.set(taskId, 0);
|
|
125
|
+
} else if ((this._fixChain.byBug.get(taskId)?.fileHits?.size ?? 0) > 0) {
|
|
126
|
+
// "No progress" = non-file-changing calls (Bash, Grep, Read, Glob, ...)
|
|
127
|
+
// AFTER the task has started editing. A task's initial read/grep/test
|
|
128
|
+
// exploration is legitimate work, not a stall — counting it would
|
|
129
|
+
// false-positive and abort productive TDD loops (COMP-GSD-5 Codex
|
|
130
|
+
// review). A task that NEVER edits is caught by the wall_clock backstop.
|
|
131
|
+
this._noProgress.set(taskId, (this._noProgress.get(taskId) ?? 0) + 1);
|
|
132
|
+
}
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (event.kind === 'tool_result') {
|
|
137
|
+
if (md.ok === false) {
|
|
138
|
+
const hash = hashError(md.output);
|
|
139
|
+
let m = this._errorHits.get(taskId);
|
|
140
|
+
if (!m) { m = new Map(); this._errorHits.set(taskId, m); }
|
|
141
|
+
m.set(hash, (m.get(hash) ?? 0) + 1);
|
|
142
|
+
}
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Evaluate the stuck signals for a task. Returns the FIRST signal that has
|
|
149
|
+
* tripped (precedence: same_file, error_recurrence, no_progress, wall_clock).
|
|
150
|
+
* @returns {{stuck:true, signal:string, detail:string} | {stuck:false}}
|
|
151
|
+
*/
|
|
152
|
+
check(taskId, nowMs) {
|
|
153
|
+
// --- same_file ---
|
|
154
|
+
const fileHits = this._fixChain.byBug.get(taskId)?.fileHits;
|
|
155
|
+
if (fileHits) {
|
|
156
|
+
for (const [file, count] of fileHits.entries()) {
|
|
157
|
+
if (count >= this.sameFileEdits) {
|
|
158
|
+
return {
|
|
159
|
+
stuck: true,
|
|
160
|
+
signal: 'same_file',
|
|
161
|
+
detail: `file ${file} edited ${count} times (>= ${this.sameFileEdits}) without converging`,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// --- error_recurrence ---
|
|
168
|
+
const errs = this._errorHits.get(taskId);
|
|
169
|
+
if (errs) {
|
|
170
|
+
for (const [hash, count] of errs.entries()) {
|
|
171
|
+
if (count >= this.errorRepeats) {
|
|
172
|
+
return {
|
|
173
|
+
stuck: true,
|
|
174
|
+
signal: 'error_recurrence',
|
|
175
|
+
detail: `the same error recurred ${count} times (>= ${this.errorRepeats}); normalized hash ${hash}`,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// --- no_progress ---
|
|
182
|
+
const np = this._noProgress.get(taskId) ?? 0;
|
|
183
|
+
if (np >= this.noProgressCalls) {
|
|
184
|
+
return {
|
|
185
|
+
stuck: true,
|
|
186
|
+
signal: 'no_progress',
|
|
187
|
+
detail: `${np} consecutive tool calls (>= ${this.noProgressCalls}) with no file-changing edit`,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// --- wall_clock ---
|
|
192
|
+
const startedAt = this._startedAt.get(taskId);
|
|
193
|
+
if (startedAt != null && nowMs - startedAt >= this.wallClockMs) {
|
|
194
|
+
return {
|
|
195
|
+
stuck: true,
|
|
196
|
+
signal: 'wall_clock',
|
|
197
|
+
detail: `task ran ${nowMs - startedAt}ms (>= ${this.wallClockMs}ms) without finishing`,
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return { stuck: false };
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Build the `attemptCounts` snapshot for the stuck.json diagnostic
|
|
206
|
+
* (contracts/gsd-stuck.json#/definitions/stuck/attemptCounts).
|
|
207
|
+
*/
|
|
208
|
+
attemptCounts(taskId) {
|
|
209
|
+
const fileHits = this._fixChain.byBug.get(taskId)?.fileHits;
|
|
210
|
+
const maxFileEdits = fileHits ? Math.max(0, ...fileHits.values()) : 0;
|
|
211
|
+
const errs = this._errorHits.get(taskId);
|
|
212
|
+
const maxErrorRepeats = errs ? Math.max(0, ...errs.values()) : 0;
|
|
213
|
+
return {
|
|
214
|
+
sameFileEdits: maxFileEdits,
|
|
215
|
+
errorRepeats: maxErrorRepeats,
|
|
216
|
+
noProgressCalls: this._noProgress.get(taskId) ?? 0,
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/** Clear all state for one task without touching others. */
|
|
221
|
+
reset(taskId) {
|
|
222
|
+
this._fixChain.resetForBug(taskId);
|
|
223
|
+
this._errorHits.delete(taskId);
|
|
224
|
+
this._noProgress.delete(taskId);
|
|
225
|
+
this._startedAt.delete(taskId);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// --- Serialization (resume) ----------------------------------------------
|
|
229
|
+
|
|
230
|
+
toJSON() {
|
|
231
|
+
return {
|
|
232
|
+
thresholds: {
|
|
233
|
+
sameFileEdits: this.sameFileEdits,
|
|
234
|
+
errorRepeats: this.errorRepeats,
|
|
235
|
+
noProgressCalls: this.noProgressCalls,
|
|
236
|
+
wallClockMs: this.wallClockMs,
|
|
237
|
+
},
|
|
238
|
+
fixChain: this._fixChain.toJSON(),
|
|
239
|
+
errorHits: Object.fromEntries(
|
|
240
|
+
[...this._errorHits.entries()].map(([t, m]) => [t, Object.fromEntries(m)]),
|
|
241
|
+
),
|
|
242
|
+
noProgress: Object.fromEntries(this._noProgress),
|
|
243
|
+
startedAt: Object.fromEntries(this._startedAt),
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
static fromJSON(json) {
|
|
248
|
+
const t = (json && typeof json === 'object' && json.thresholds) || {};
|
|
249
|
+
const d = new GsdStuckDetector({
|
|
250
|
+
sameFileEdits: t.sameFileEdits,
|
|
251
|
+
errorRepeats: t.errorRepeats,
|
|
252
|
+
noProgressCalls: t.noProgressCalls,
|
|
253
|
+
wallClockMs: t.wallClockMs,
|
|
254
|
+
});
|
|
255
|
+
if (!json || typeof json !== 'object') return d;
|
|
256
|
+
|
|
257
|
+
d._fixChain = FixChainDetector.fromJSON(json.fixChain ?? {});
|
|
258
|
+
if (json.errorHits && typeof json.errorHits === 'object') {
|
|
259
|
+
for (const [taskId, hashes] of Object.entries(json.errorHits)) {
|
|
260
|
+
d._errorHits.set(taskId, new Map(Object.entries(hashes ?? {})));
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
if (json.noProgress && typeof json.noProgress === 'object') {
|
|
264
|
+
for (const [taskId, n] of Object.entries(json.noProgress)) {
|
|
265
|
+
d._noProgress.set(taskId, Number(n) || 0);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
if (json.startedAt && typeof json.startedAt === 'object') {
|
|
269
|
+
for (const [taskId, ms] of Object.entries(json.startedAt)) {
|
|
270
|
+
d._startedAt.set(taskId, Number(ms) || 0);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
return d;
|
|
274
|
+
}
|
|
275
|
+
}
|