iterate-plugin 2.9.3 → 2.9.4
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/lib/client.js +76 -54
- package/lib/parse.js +90 -57
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -43,6 +43,20 @@ var SEVERITY_COLOR = {
|
|
|
43
43
|
medium: "#eab308",
|
|
44
44
|
low: "#6b7280"
|
|
45
45
|
};
|
|
46
|
+
function safeGet(o, key) {
|
|
47
|
+
try {
|
|
48
|
+
return o[key];
|
|
49
|
+
} catch {
|
|
50
|
+
return void 0;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function safeKeys(o) {
|
|
54
|
+
try {
|
|
55
|
+
return Object.keys(o);
|
|
56
|
+
} catch {
|
|
57
|
+
return [];
|
|
58
|
+
}
|
|
59
|
+
}
|
|
46
60
|
function scanSessionForResume(obj, seen, maxDepth = 20) {
|
|
47
61
|
if (maxDepth <= 0) return 0;
|
|
48
62
|
if (!obj || typeof obj !== "object") return 0;
|
|
@@ -54,27 +68,28 @@ function scanSessionForResume(obj, seen, maxDepth = 20) {
|
|
|
54
68
|
/** @type {Record<string, unknown>} */
|
|
55
69
|
obj
|
|
56
70
|
);
|
|
57
|
-
if (direct
|
|
71
|
+
if (safeGet(direct, "type") === "resume") {
|
|
58
72
|
const data = (
|
|
59
73
|
/** @type {Record<string, unknown>} */
|
|
60
|
-
direct
|
|
74
|
+
safeGet(direct, "data") || {}
|
|
61
75
|
);
|
|
62
|
-
if (typeof data
|
|
63
|
-
best = data
|
|
76
|
+
if (typeof safeGet(data, "resumeCount") === "number" && safeGet(data, "resumeCount") > best) {
|
|
77
|
+
best = safeGet(data, "resumeCount");
|
|
64
78
|
}
|
|
65
79
|
}
|
|
66
|
-
|
|
80
|
+
const directEntry = safeGet(direct, "entry");
|
|
81
|
+
if (directEntry && typeof directEntry === "object") {
|
|
67
82
|
const entry = (
|
|
68
83
|
/** @type {Record<string, unknown>} */
|
|
69
|
-
|
|
84
|
+
directEntry
|
|
70
85
|
);
|
|
71
|
-
if (entry
|
|
86
|
+
if (safeGet(entry, "type") === "resume") {
|
|
72
87
|
const data = (
|
|
73
88
|
/** @type {Record<string, unknown>} */
|
|
74
|
-
entry
|
|
89
|
+
safeGet(entry, "data") || {}
|
|
75
90
|
);
|
|
76
|
-
if (typeof data
|
|
77
|
-
best = data
|
|
91
|
+
if (typeof safeGet(data, "resumeCount") === "number" && safeGet(data, "resumeCount") > best) {
|
|
92
|
+
best = safeGet(data, "resumeCount");
|
|
78
93
|
}
|
|
79
94
|
}
|
|
80
95
|
}
|
|
@@ -85,8 +100,8 @@ function scanSessionForResume(obj, seen, maxDepth = 20) {
|
|
|
85
100
|
}
|
|
86
101
|
return best;
|
|
87
102
|
}
|
|
88
|
-
for (const key of
|
|
89
|
-
const val = direct
|
|
103
|
+
for (const key of safeKeys(direct)) {
|
|
104
|
+
const val = safeGet(direct, key);
|
|
90
105
|
if (val && typeof val === "object") {
|
|
91
106
|
const found = scanSessionForResume(val, s, maxDepth - 1);
|
|
92
107
|
if (found > best) best = found;
|
|
@@ -107,15 +122,15 @@ function countSessionImages(session) {
|
|
|
107
122
|
obj
|
|
108
123
|
);
|
|
109
124
|
let ref = null;
|
|
110
|
-
if (o
|
|
125
|
+
if (safeGet(o, "type") === "image" && safeGet(o, "attachment") && typeof safeGet(o, "attachment") === "object") {
|
|
111
126
|
ref = /** @type {Record<string, unknown>} */
|
|
112
|
-
o
|
|
127
|
+
safeGet(o, "attachment");
|
|
113
128
|
}
|
|
114
|
-
if (!ref && typeof o
|
|
129
|
+
if (!ref && typeof safeGet(o, "mediaType") === "string" && String(safeGet(o, "mediaType")).startsWith("image/")) {
|
|
115
130
|
ref = o;
|
|
116
131
|
}
|
|
117
132
|
if (ref) {
|
|
118
|
-
const id = typeof ref
|
|
133
|
+
const id = typeof safeGet(ref, "attachmentId") === "string" ? safeGet(ref, "attachmentId") : null;
|
|
119
134
|
if (id) {
|
|
120
135
|
if (!ids.has(id)) {
|
|
121
136
|
ids.add(id);
|
|
@@ -129,8 +144,8 @@ function countSessionImages(session) {
|
|
|
129
144
|
for (const item of obj) walk(item, depth - 1);
|
|
130
145
|
return;
|
|
131
146
|
}
|
|
132
|
-
for (const key of
|
|
133
|
-
const val = o
|
|
147
|
+
for (const key of safeKeys(o)) {
|
|
148
|
+
const val = safeGet(o, key);
|
|
134
149
|
if (val && typeof val === "object") walk(val, depth - 1);
|
|
135
150
|
}
|
|
136
151
|
};
|
|
@@ -144,7 +159,8 @@ function isReviewReport(obj) {
|
|
|
144
159
|
/** @type {Record<string, unknown>} */
|
|
145
160
|
obj
|
|
146
161
|
);
|
|
147
|
-
|
|
162
|
+
const convergence = safeGet(o, "convergence");
|
|
163
|
+
return typeof convergence === "object" && convergence !== null && Array.isArray(safeGet(o, "findings")) && Array.isArray(safeGet(o, "rounds"));
|
|
148
164
|
}
|
|
149
165
|
function findReportInObject(obj, seen, maxDepth = 20) {
|
|
150
166
|
if (maxDepth <= 0) return null;
|
|
@@ -167,8 +183,8 @@ function findReportInObject(obj, seen, maxDepth = 20) {
|
|
|
167
183
|
/** @type {Record<string, unknown>} */
|
|
168
184
|
obj
|
|
169
185
|
);
|
|
170
|
-
for (const key of
|
|
171
|
-
const val = o
|
|
186
|
+
for (const key of safeKeys(o)) {
|
|
187
|
+
const val = safeGet(o, key);
|
|
172
188
|
if (val && typeof val === "object") {
|
|
173
189
|
const found = findReportInObject(val, s, maxDepth - 1);
|
|
174
190
|
if (found) return found;
|
|
@@ -184,54 +200,58 @@ function scanSessionForReport(session) {
|
|
|
184
200
|
/** @type {Record<string, unknown>} */
|
|
185
201
|
session
|
|
186
202
|
);
|
|
187
|
-
|
|
203
|
+
const toolCalls = safeGet(s, "toolCalls");
|
|
204
|
+
if (Array.isArray(toolCalls)) {
|
|
188
205
|
const calls = (
|
|
189
206
|
/** @type {Array<Record<string, unknown>>} */
|
|
190
|
-
|
|
207
|
+
toolCalls
|
|
191
208
|
);
|
|
192
209
|
for (let i = calls.length - 1; i >= 0; i--) {
|
|
193
210
|
const call = calls[i];
|
|
194
211
|
if (!call) continue;
|
|
195
|
-
if (call
|
|
196
|
-
const result = call
|
|
212
|
+
if (safeGet(call, "tool") === "iterate_review" || String(safeGet(call, "tool") ?? "").endsWith("iterate_review")) {
|
|
213
|
+
const result = safeGet(call, "result");
|
|
197
214
|
if (result && typeof result === "object") {
|
|
198
215
|
const r = (
|
|
199
216
|
/** @type {Record<string, unknown>} */
|
|
200
217
|
result
|
|
201
218
|
);
|
|
202
|
-
|
|
219
|
+
const report = safeGet(r, "report");
|
|
220
|
+
if (report && typeof report === "object") {
|
|
203
221
|
return (
|
|
204
222
|
/** @type {Record<string, unknown>} */
|
|
205
|
-
|
|
223
|
+
report
|
|
206
224
|
);
|
|
207
225
|
}
|
|
208
226
|
}
|
|
209
227
|
}
|
|
210
228
|
}
|
|
211
229
|
}
|
|
212
|
-
|
|
230
|
+
const messages = safeGet(s, "messages");
|
|
231
|
+
if (Array.isArray(messages)) {
|
|
213
232
|
const msgs = (
|
|
214
233
|
/** @type {Array<Record<string, unknown>>} */
|
|
215
|
-
|
|
234
|
+
messages
|
|
216
235
|
);
|
|
217
236
|
for (let i = msgs.length - 1; i >= 0; i--) {
|
|
218
237
|
const msg = msgs[i];
|
|
219
|
-
|
|
238
|
+
const msgCalls = msg && Array.isArray(safeGet(msg, "tool_calls")) ? safeGet(msg, "tool_calls") : null;
|
|
239
|
+
if (!msg || !msgCalls) continue;
|
|
220
240
|
const calls = (
|
|
221
241
|
/** @type {Array<Record<string, unknown>>} */
|
|
222
|
-
|
|
242
|
+
msgCalls
|
|
223
243
|
);
|
|
224
244
|
for (const call of calls) {
|
|
225
245
|
if (!call) continue;
|
|
226
|
-
const fn = call
|
|
246
|
+
const fn = safeGet(call, "function");
|
|
227
247
|
if (fn && typeof fn === "object") {
|
|
228
248
|
const f = (
|
|
229
249
|
/** @type {Record<string, unknown>} */
|
|
230
250
|
fn
|
|
231
251
|
);
|
|
232
|
-
if (String(f
|
|
252
|
+
if (String(safeGet(f, "name") ?? "").endsWith("iterate_review")) {
|
|
233
253
|
try {
|
|
234
|
-
const args = JSON.parse(String(f
|
|
254
|
+
const args = JSON.parse(String(safeGet(f, "arguments") ?? "{}"));
|
|
235
255
|
const found = findReportInObject(args);
|
|
236
256
|
if (found) return found;
|
|
237
257
|
} catch {
|
|
@@ -249,8 +269,8 @@ function isRunSummary(obj) {
|
|
|
249
269
|
/** @type {Record<string, unknown>} */
|
|
250
270
|
obj
|
|
251
271
|
);
|
|
252
|
-
const final = o
|
|
253
|
-
return !!final && typeof final === "object" && (final
|
|
272
|
+
const final = safeGet(o, "finalReport");
|
|
273
|
+
return !!final && typeof final === "object" && (safeGet(final, "verdict") === "approved" || safeGet(final, "verdict") === "needs_revision");
|
|
254
274
|
}
|
|
255
275
|
function findRunSummaryInObject(obj, seen, maxDepth = 20) {
|
|
256
276
|
if (maxDepth <= 0) return null;
|
|
@@ -273,8 +293,8 @@ function findRunSummaryInObject(obj, seen, maxDepth = 20) {
|
|
|
273
293
|
/** @type {Record<string, unknown>} */
|
|
274
294
|
obj
|
|
275
295
|
);
|
|
276
|
-
for (const key of
|
|
277
|
-
const val = o
|
|
296
|
+
for (const key of safeKeys(o)) {
|
|
297
|
+
const val = safeGet(o, key);
|
|
278
298
|
if (val && typeof val === "object") {
|
|
279
299
|
const found = findRunSummaryInObject(val, s, maxDepth - 1);
|
|
280
300
|
if (found) return found;
|
|
@@ -290,29 +310,31 @@ function scanSessionForRunSummary(session) {
|
|
|
290
310
|
/** @type {Record<string, unknown>} */
|
|
291
311
|
session
|
|
292
312
|
);
|
|
293
|
-
|
|
313
|
+
const toolCalls = safeGet(s, "toolCalls");
|
|
314
|
+
if (Array.isArray(toolCalls)) {
|
|
294
315
|
const calls = (
|
|
295
316
|
/** @type {Array<Record<string, unknown>>} */
|
|
296
|
-
|
|
317
|
+
toolCalls
|
|
297
318
|
);
|
|
298
319
|
for (let i = calls.length - 1; i >= 0; i--) {
|
|
299
320
|
const call = calls[i];
|
|
300
321
|
if (!call) continue;
|
|
301
|
-
if (call
|
|
302
|
-
const found = findRunSummaryInObject(call
|
|
322
|
+
if (safeGet(call, "tool") === "workflow" || String(safeGet(call, "tool") ?? "").endsWith("workflow")) {
|
|
323
|
+
const found = findRunSummaryInObject(safeGet(call, "result"), void 0, 24);
|
|
303
324
|
if (found) return found;
|
|
304
325
|
}
|
|
305
326
|
}
|
|
306
327
|
}
|
|
307
|
-
|
|
328
|
+
const messages = safeGet(s, "messages");
|
|
329
|
+
if (Array.isArray(messages)) {
|
|
308
330
|
const msgs = (
|
|
309
331
|
/** @type {Array<Record<string, unknown>>} */
|
|
310
|
-
|
|
332
|
+
messages
|
|
311
333
|
);
|
|
312
334
|
for (let i = msgs.length - 1; i >= 0; i--) {
|
|
313
335
|
const msg = msgs[i];
|
|
314
336
|
if (!msg) continue;
|
|
315
|
-
const found = findRunSummaryInObject(msg
|
|
337
|
+
const found = findRunSummaryInObject(safeGet(msg, "content"));
|
|
316
338
|
if (found) return found;
|
|
317
339
|
}
|
|
318
340
|
}
|
|
@@ -326,22 +348,22 @@ function extractVerdict(runSummary) {
|
|
|
326
348
|
);
|
|
327
349
|
const final = (
|
|
328
350
|
/** @type {Record<string, unknown>} */
|
|
329
|
-
o
|
|
351
|
+
safeGet(o, "finalReport")
|
|
330
352
|
);
|
|
331
|
-
const meta = final
|
|
353
|
+
const meta = safeGet(final, "metaReview") && typeof safeGet(final, "metaReview") === "object" ? (
|
|
332
354
|
/** @type {Record<string, unknown>} */
|
|
333
|
-
final
|
|
355
|
+
safeGet(final, "metaReview")
|
|
334
356
|
) : {};
|
|
335
|
-
const issues = Array.isArray(meta
|
|
336
|
-
const roundsVal = o
|
|
357
|
+
const issues = Array.isArray(safeGet(meta, "issues")) ? safeGet(meta, "issues") : [];
|
|
358
|
+
const roundsVal = safeGet(o, "rounds");
|
|
337
359
|
const totalRounds = typeof roundsVal === "number" ? roundsVal : Array.isArray(roundsVal) ? roundsVal.length : 0;
|
|
338
360
|
return {
|
|
339
|
-
verdict: final
|
|
361
|
+
verdict: safeGet(final, "verdict") === "needs_revision" ? "needs_revision" : "approved",
|
|
340
362
|
reportIssues: issues.length,
|
|
341
|
-
checksRun: typeof meta
|
|
342
|
-
converged: o
|
|
363
|
+
checksRun: typeof safeGet(meta, "checksRun") === "number" ? safeGet(meta, "checksRun") : 0,
|
|
364
|
+
converged: safeGet(o, "converged") === true,
|
|
343
365
|
totalRounds,
|
|
344
|
-
totalFindings: typeof o
|
|
366
|
+
totalFindings: typeof safeGet(o, "totalFindings") === "number" ? safeGet(o, "totalFindings") : 0
|
|
345
367
|
};
|
|
346
368
|
}
|
|
347
369
|
function normalizeReport(report) {
|
package/lib/parse.js
CHANGED
|
@@ -28,6 +28,31 @@ export const SEVERITY_COLOR = {
|
|
|
28
28
|
low: '#6b7280',
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
// ─── Safe property access ────────────────────────────────────────────────────
|
|
32
|
+
// Session snapshots handed to the client UI can be cordis service proxies or
|
|
33
|
+
// contain proxy references (owner share objects). Reading an un-injected
|
|
34
|
+
// service name off such a proxy throws `cannot get property "x" without
|
|
35
|
+
// inject`. All deep scans below therefore read through these helpers so a
|
|
36
|
+
// hostile/proxied object degrades to "no match" instead of crashing the slot.
|
|
37
|
+
|
|
38
|
+
/** Read one property that may sit on a cordis service proxy; never throws. */
|
|
39
|
+
function safeGet(o, key) {
|
|
40
|
+
try {
|
|
41
|
+
return o[key]
|
|
42
|
+
} catch {
|
|
43
|
+
return undefined
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Keys of an object that may be a cordis service proxy; never throws. */
|
|
48
|
+
function safeKeys(o) {
|
|
49
|
+
try {
|
|
50
|
+
return Object.keys(o)
|
|
51
|
+
} catch {
|
|
52
|
+
return []
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
31
56
|
// ─── Interruption / resume + image attachment detection ──────────────────────
|
|
32
57
|
|
|
33
58
|
/**
|
|
@@ -55,19 +80,20 @@ export function scanSessionForResume(obj, seen, maxDepth = 20) {
|
|
|
55
80
|
|
|
56
81
|
// Direct marker: { type: "resume", data: { resumeCount } }.
|
|
57
82
|
const direct = /** @type {Record<string, unknown>} */ (obj)
|
|
58
|
-
if (direct
|
|
59
|
-
const data = /** @type {Record<string, unknown>} */ (direct
|
|
60
|
-
if (typeof data
|
|
61
|
-
best = data
|
|
83
|
+
if (safeGet(direct, 'type') === 'resume') {
|
|
84
|
+
const data = /** @type {Record<string, unknown>} */ (safeGet(direct, 'data') || {})
|
|
85
|
+
if (typeof safeGet(data, 'resumeCount') === 'number' && safeGet(data, 'resumeCount') > best) {
|
|
86
|
+
best = safeGet(data, 'resumeCount')
|
|
62
87
|
}
|
|
63
88
|
}
|
|
64
89
|
// Nested entry: { entry: { type: "resume", data: { resumeCount } } }.
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
90
|
+
const directEntry = safeGet(direct, 'entry')
|
|
91
|
+
if (directEntry && typeof directEntry === 'object') {
|
|
92
|
+
const entry = /** @type {Record<string, unknown>} */ (directEntry)
|
|
93
|
+
if (safeGet(entry, 'type') === 'resume') {
|
|
94
|
+
const data = /** @type {Record<string, unknown>} */ (safeGet(entry, 'data') || {})
|
|
95
|
+
if (typeof safeGet(data, 'resumeCount') === 'number' && safeGet(data, 'resumeCount') > best) {
|
|
96
|
+
best = safeGet(data, 'resumeCount')
|
|
71
97
|
}
|
|
72
98
|
}
|
|
73
99
|
}
|
|
@@ -80,8 +106,8 @@ export function scanSessionForResume(obj, seen, maxDepth = 20) {
|
|
|
80
106
|
return best
|
|
81
107
|
}
|
|
82
108
|
|
|
83
|
-
for (const key of
|
|
84
|
-
const val = direct
|
|
109
|
+
for (const key of safeKeys(direct)) {
|
|
110
|
+
const val = safeGet(direct, key)
|
|
85
111
|
if (val && typeof val === 'object') {
|
|
86
112
|
const found = scanSessionForResume(val, s, maxDepth - 1)
|
|
87
113
|
if (found > best) best = found
|
|
@@ -115,15 +141,15 @@ export function countSessionImages(session) {
|
|
|
115
141
|
|
|
116
142
|
// Image block: { type: "image", attachment: { ...ref } }.
|
|
117
143
|
let ref = null
|
|
118
|
-
if (o
|
|
119
|
-
ref = /** @type {Record<string, unknown>} */ (o
|
|
144
|
+
if (safeGet(o, 'type') === 'image' && safeGet(o, 'attachment') && typeof safeGet(o, 'attachment') === 'object') {
|
|
145
|
+
ref = /** @type {Record<string, unknown>} */ (safeGet(o, 'attachment'))
|
|
120
146
|
}
|
|
121
147
|
// Raw attachment reference shape.
|
|
122
|
-
if (!ref && typeof o
|
|
148
|
+
if (!ref && typeof safeGet(o, 'mediaType') === 'string' && String(safeGet(o, 'mediaType')).startsWith('image/')) {
|
|
123
149
|
ref = o
|
|
124
150
|
}
|
|
125
151
|
if (ref) {
|
|
126
|
-
const id = typeof ref
|
|
152
|
+
const id = typeof safeGet(ref, 'attachmentId') === 'string' ? safeGet(ref, 'attachmentId') : null
|
|
127
153
|
if (id) {
|
|
128
154
|
if (!ids.has(id)) { ids.add(id); count += 1 }
|
|
129
155
|
} else {
|
|
@@ -135,8 +161,8 @@ export function countSessionImages(session) {
|
|
|
135
161
|
for (const item of obj) walk(item, depth - 1)
|
|
136
162
|
return
|
|
137
163
|
}
|
|
138
|
-
for (const key of
|
|
139
|
-
const val = o
|
|
164
|
+
for (const key of safeKeys(o)) {
|
|
165
|
+
const val = safeGet(o, key)
|
|
140
166
|
if (val && typeof val === 'object') walk(val, depth - 1)
|
|
141
167
|
}
|
|
142
168
|
}
|
|
@@ -159,11 +185,12 @@ export function countSessionImages(session) {
|
|
|
159
185
|
export function isReviewReport(obj) {
|
|
160
186
|
if (!obj || typeof obj !== 'object') return false
|
|
161
187
|
const o = /** @type {Record<string, unknown>} */ (obj)
|
|
188
|
+
const convergence = safeGet(o, 'convergence')
|
|
162
189
|
return (
|
|
163
|
-
typeof
|
|
164
|
-
|
|
165
|
-
Array.isArray(o
|
|
166
|
-
Array.isArray(o
|
|
190
|
+
typeof convergence === 'object' &&
|
|
191
|
+
convergence !== null &&
|
|
192
|
+
Array.isArray(safeGet(o, 'findings')) &&
|
|
193
|
+
Array.isArray(safeGet(o, 'rounds'))
|
|
167
194
|
)
|
|
168
195
|
}
|
|
169
196
|
|
|
@@ -201,8 +228,8 @@ export function findReportInObject(obj, seen, maxDepth = 20) {
|
|
|
201
228
|
|
|
202
229
|
// Check object values
|
|
203
230
|
const o = /** @type {Record<string, unknown>} */ (obj)
|
|
204
|
-
for (const key of
|
|
205
|
-
const val = o
|
|
231
|
+
for (const key of safeKeys(o)) {
|
|
232
|
+
const val = safeGet(o, key)
|
|
206
233
|
if (val && typeof val === 'object') {
|
|
207
234
|
// Check leaf values that are arrays or objects
|
|
208
235
|
const found = findReportInObject(val, s, maxDepth - 1)
|
|
@@ -231,17 +258,19 @@ export function scanSessionForReport(session) {
|
|
|
231
258
|
const s = /** @type {Record<string, unknown>} */ (session)
|
|
232
259
|
|
|
233
260
|
// Common pattern: session.toolCalls[].result.report
|
|
234
|
-
|
|
235
|
-
|
|
261
|
+
const toolCalls = safeGet(s, 'toolCalls')
|
|
262
|
+
if (Array.isArray(toolCalls)) {
|
|
263
|
+
const calls = /** @type {Array<Record<string, unknown>>} */ (toolCalls)
|
|
236
264
|
for (let i = calls.length - 1; i >= 0; i--) {
|
|
237
265
|
const call = calls[i]
|
|
238
266
|
if (!call) continue
|
|
239
|
-
if (call
|
|
240
|
-
const result = call
|
|
267
|
+
if (safeGet(call, 'tool') === 'iterate_review' || String(safeGet(call, 'tool') ?? '').endsWith('iterate_review')) {
|
|
268
|
+
const result = safeGet(call, 'result')
|
|
241
269
|
if (result && typeof result === 'object') {
|
|
242
270
|
const r = /** @type {Record<string, unknown>} */ (result)
|
|
243
|
-
|
|
244
|
-
|
|
271
|
+
const report = safeGet(r, 'report')
|
|
272
|
+
if (report && typeof report === 'object') {
|
|
273
|
+
return /** @type {Record<string, unknown>} */ (report)
|
|
245
274
|
}
|
|
246
275
|
}
|
|
247
276
|
}
|
|
@@ -249,21 +278,23 @@ export function scanSessionForReport(session) {
|
|
|
249
278
|
}
|
|
250
279
|
|
|
251
280
|
// Common pattern: session.messages[].tool_calls[].function.arguments
|
|
252
|
-
|
|
253
|
-
|
|
281
|
+
const messages = safeGet(s, 'messages')
|
|
282
|
+
if (Array.isArray(messages)) {
|
|
283
|
+
const msgs = /** @type {Array<Record<string, unknown>>} */ (messages)
|
|
254
284
|
for (let i = msgs.length - 1; i >= 0; i--) {
|
|
255
285
|
const msg = msgs[i]
|
|
256
|
-
|
|
257
|
-
|
|
286
|
+
const msgCalls = msg && Array.isArray(safeGet(msg, 'tool_calls')) ? safeGet(msg, 'tool_calls') : null
|
|
287
|
+
if (!msg || !msgCalls) continue
|
|
288
|
+
const calls = /** @type {Array<Record<string, unknown>>} */ (msgCalls)
|
|
258
289
|
for (const call of calls) {
|
|
259
290
|
if (!call) continue
|
|
260
|
-
const fn = call
|
|
291
|
+
const fn = safeGet(call, 'function')
|
|
261
292
|
if (fn && typeof fn === 'object') {
|
|
262
293
|
const f = /** @type {Record<string, unknown>} */ (fn)
|
|
263
|
-
if (String(f
|
|
294
|
+
if (String(safeGet(f, 'name') ?? '').endsWith('iterate_review')) {
|
|
264
295
|
// Try to parse arguments
|
|
265
296
|
try {
|
|
266
|
-
const args = JSON.parse(String(f
|
|
297
|
+
const args = JSON.parse(String(safeGet(f, 'arguments') ?? '{}'))
|
|
267
298
|
const found = findReportInObject(args)
|
|
268
299
|
if (found) return found
|
|
269
300
|
} catch {
|
|
@@ -296,10 +327,10 @@ export function scanSessionForReport(session) {
|
|
|
296
327
|
export function isRunSummary(obj) {
|
|
297
328
|
if (!obj || typeof obj !== 'object') return false
|
|
298
329
|
const o = /** @type {Record<string, unknown>} */ (obj)
|
|
299
|
-
const final = o
|
|
330
|
+
const final = safeGet(o, 'finalReport')
|
|
300
331
|
return !!final &&
|
|
301
332
|
typeof final === 'object' &&
|
|
302
|
-
(final
|
|
333
|
+
(safeGet(final, 'verdict') === 'approved' || safeGet(final, 'verdict') === 'needs_revision')
|
|
303
334
|
}
|
|
304
335
|
|
|
305
336
|
/**
|
|
@@ -330,8 +361,8 @@ export function findRunSummaryInObject(obj, seen, maxDepth = 20) {
|
|
|
330
361
|
}
|
|
331
362
|
|
|
332
363
|
const o = /** @type {Record<string, unknown>} */ (obj)
|
|
333
|
-
for (const key of
|
|
334
|
-
const val = o
|
|
364
|
+
for (const key of safeKeys(o)) {
|
|
365
|
+
const val = safeGet(o, key)
|
|
335
366
|
if (val && typeof val === 'object') {
|
|
336
367
|
const found = findRunSummaryInObject(val, s, maxDepth - 1)
|
|
337
368
|
if (found) return found
|
|
@@ -357,25 +388,27 @@ export function scanSessionForRunSummary(session) {
|
|
|
357
388
|
const s = /** @type {Record<string, unknown>} */ (session)
|
|
358
389
|
|
|
359
390
|
// Common pattern: session.toolCalls[].result contains a run summary.
|
|
360
|
-
|
|
361
|
-
|
|
391
|
+
const toolCalls = safeGet(s, 'toolCalls')
|
|
392
|
+
if (Array.isArray(toolCalls)) {
|
|
393
|
+
const calls = /** @type {Array<Record<string, unknown>>} */ (toolCalls)
|
|
362
394
|
for (let i = calls.length - 1; i >= 0; i--) {
|
|
363
395
|
const call = calls[i]
|
|
364
396
|
if (!call) continue
|
|
365
|
-
if (call
|
|
366
|
-
const found = findRunSummaryInObject(call
|
|
397
|
+
if (safeGet(call, 'tool') === 'workflow' || String(safeGet(call, 'tool') ?? '').endsWith('workflow')) {
|
|
398
|
+
const found = findRunSummaryInObject(safeGet(call, 'result'), undefined, 24)
|
|
367
399
|
if (found) return found
|
|
368
400
|
}
|
|
369
401
|
}
|
|
370
402
|
}
|
|
371
403
|
|
|
372
404
|
// Common pattern: assistant message content holding the workflow return.
|
|
373
|
-
|
|
374
|
-
|
|
405
|
+
const messages = safeGet(s, 'messages')
|
|
406
|
+
if (Array.isArray(messages)) {
|
|
407
|
+
const msgs = /** @type {Array<Record<string, unknown>>} */ (messages)
|
|
375
408
|
for (let i = msgs.length - 1; i >= 0; i--) {
|
|
376
409
|
const msg = msgs[i]
|
|
377
410
|
if (!msg) continue
|
|
378
|
-
const found = findRunSummaryInObject(msg
|
|
411
|
+
const found = findRunSummaryInObject(safeGet(msg, 'content'))
|
|
379
412
|
if (found) return found
|
|
380
413
|
}
|
|
381
414
|
}
|
|
@@ -393,21 +426,21 @@ export function scanSessionForRunSummary(session) {
|
|
|
393
426
|
export function extractVerdict(runSummary) {
|
|
394
427
|
if (!isRunSummary(runSummary)) return null
|
|
395
428
|
const o = /** @type {Record<string, unknown>} */ (runSummary)
|
|
396
|
-
const final = /** @type {Record<string, unknown>} */ (o
|
|
397
|
-
const meta = final
|
|
398
|
-
? /** @type {Record<string, unknown>} */ (final
|
|
429
|
+
const final = /** @type {Record<string, unknown>} */ (safeGet(o, 'finalReport'))
|
|
430
|
+
const meta = safeGet(final, 'metaReview') && typeof safeGet(final, 'metaReview') === 'object'
|
|
431
|
+
? /** @type {Record<string, unknown>} */ (safeGet(final, 'metaReview'))
|
|
399
432
|
: {}
|
|
400
|
-
const issues = Array.isArray(meta
|
|
433
|
+
const issues = Array.isArray(safeGet(meta, 'issues')) ? safeGet(meta, 'issues') : []
|
|
401
434
|
// `totalRounds` may be a bare number (dry-run returns `rounds`) or a count.
|
|
402
|
-
const roundsVal = o
|
|
435
|
+
const roundsVal = safeGet(o, 'rounds')
|
|
403
436
|
const totalRounds = typeof roundsVal === 'number' ? roundsVal : (Array.isArray(roundsVal) ? roundsVal.length : 0)
|
|
404
437
|
return {
|
|
405
|
-
verdict: final
|
|
438
|
+
verdict: safeGet(final, 'verdict') === 'needs_revision' ? 'needs_revision' : 'approved',
|
|
406
439
|
reportIssues: issues.length,
|
|
407
|
-
checksRun: typeof meta
|
|
408
|
-
converged: o
|
|
440
|
+
checksRun: typeof safeGet(meta, 'checksRun') === 'number' ? safeGet(meta, 'checksRun') : 0,
|
|
441
|
+
converged: safeGet(o, 'converged') === true,
|
|
409
442
|
totalRounds,
|
|
410
|
-
totalFindings: typeof o
|
|
443
|
+
totalFindings: typeof safeGet(o, 'totalFindings') === 'number' ? safeGet(o, 'totalFindings') : 0,
|
|
411
444
|
}
|
|
412
445
|
}
|
|
413
446
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "iterate-plugin",
|
|
3
|
-
"version": "2.9.
|
|
3
|
+
"version": "2.9.4",
|
|
4
4
|
"description": "dsh plugin that turns the iterate skill into an autonomous closed-loop harness: plan -> parallel review xN -> atomic fixes -> validate -> loop -> auto-stop, plus a dry-run pure-review mode with multi-round convergence and a meta-review that audits the report and emits a final review report.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|