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.
Files changed (3) hide show
  1. package/lib/client.js +76 -54
  2. package/lib/parse.js +90 -57
  3. 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.type === "resume") {
71
+ if (safeGet(direct, "type") === "resume") {
58
72
  const data = (
59
73
  /** @type {Record<string, unknown>} */
60
- direct.data || {}
74
+ safeGet(direct, "data") || {}
61
75
  );
62
- if (typeof data.resumeCount === "number" && data.resumeCount > best) {
63
- best = data.resumeCount;
76
+ if (typeof safeGet(data, "resumeCount") === "number" && safeGet(data, "resumeCount") > best) {
77
+ best = safeGet(data, "resumeCount");
64
78
  }
65
79
  }
66
- if (direct.entry && typeof direct.entry === "object") {
80
+ const directEntry = safeGet(direct, "entry");
81
+ if (directEntry && typeof directEntry === "object") {
67
82
  const entry = (
68
83
  /** @type {Record<string, unknown>} */
69
- direct.entry
84
+ directEntry
70
85
  );
71
- if (entry.type === "resume") {
86
+ if (safeGet(entry, "type") === "resume") {
72
87
  const data = (
73
88
  /** @type {Record<string, unknown>} */
74
- entry.data || {}
89
+ safeGet(entry, "data") || {}
75
90
  );
76
- if (typeof data.resumeCount === "number" && data.resumeCount > best) {
77
- best = data.resumeCount;
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 Object.keys(direct)) {
89
- const val = direct[key];
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.type === "image" && o.attachment && typeof o.attachment === "object") {
125
+ if (safeGet(o, "type") === "image" && safeGet(o, "attachment") && typeof safeGet(o, "attachment") === "object") {
111
126
  ref = /** @type {Record<string, unknown>} */
112
- o.attachment;
127
+ safeGet(o, "attachment");
113
128
  }
114
- if (!ref && typeof o.mediaType === "string" && String(o.mediaType).startsWith("image/")) {
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.attachmentId === "string" ? ref.attachmentId : null;
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 Object.keys(o)) {
133
- const val = o[key];
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
- return typeof o.convergence === "object" && o.convergence !== null && Array.isArray(o.findings) && Array.isArray(o.rounds);
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 Object.keys(o)) {
171
- const val = o[key];
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
- if (Array.isArray(s.toolCalls)) {
203
+ const toolCalls = safeGet(s, "toolCalls");
204
+ if (Array.isArray(toolCalls)) {
188
205
  const calls = (
189
206
  /** @type {Array<Record<string, unknown>>} */
190
- s.toolCalls
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.tool === "iterate_review" || String(call.tool ?? "").endsWith("iterate_review")) {
196
- const result = call.result;
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
- if (r.report && typeof r.report === "object") {
219
+ const report = safeGet(r, "report");
220
+ if (report && typeof report === "object") {
203
221
  return (
204
222
  /** @type {Record<string, unknown>} */
205
- r.report
223
+ report
206
224
  );
207
225
  }
208
226
  }
209
227
  }
210
228
  }
211
229
  }
212
- if (Array.isArray(s.messages)) {
230
+ const messages = safeGet(s, "messages");
231
+ if (Array.isArray(messages)) {
213
232
  const msgs = (
214
233
  /** @type {Array<Record<string, unknown>>} */
215
- s.messages
234
+ messages
216
235
  );
217
236
  for (let i = msgs.length - 1; i >= 0; i--) {
218
237
  const msg = msgs[i];
219
- if (!msg || !Array.isArray(msg.tool_calls)) continue;
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
- msg.tool_calls
242
+ msgCalls
223
243
  );
224
244
  for (const call of calls) {
225
245
  if (!call) continue;
226
- const fn = call.function;
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.name ?? "").endsWith("iterate_review")) {
252
+ if (String(safeGet(f, "name") ?? "").endsWith("iterate_review")) {
233
253
  try {
234
- const args = JSON.parse(String(f.arguments ?? "{}"));
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.finalReport;
253
- return !!final && typeof final === "object" && (final.verdict === "approved" || final.verdict === "needs_revision");
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 Object.keys(o)) {
277
- const val = o[key];
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
- if (Array.isArray(s.toolCalls)) {
313
+ const toolCalls = safeGet(s, "toolCalls");
314
+ if (Array.isArray(toolCalls)) {
294
315
  const calls = (
295
316
  /** @type {Array<Record<string, unknown>>} */
296
- s.toolCalls
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.tool === "workflow" || String(call.tool ?? "").endsWith("workflow")) {
302
- const found = findRunSummaryInObject(call.result, void 0, 24);
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
- if (Array.isArray(s.messages)) {
328
+ const messages = safeGet(s, "messages");
329
+ if (Array.isArray(messages)) {
308
330
  const msgs = (
309
331
  /** @type {Array<Record<string, unknown>>} */
310
- s.messages
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.content);
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.finalReport
351
+ safeGet(o, "finalReport")
330
352
  );
331
- const meta = final.metaReview && typeof final.metaReview === "object" ? (
353
+ const meta = safeGet(final, "metaReview") && typeof safeGet(final, "metaReview") === "object" ? (
332
354
  /** @type {Record<string, unknown>} */
333
- final.metaReview
355
+ safeGet(final, "metaReview")
334
356
  ) : {};
335
- const issues = Array.isArray(meta.issues) ? meta.issues : [];
336
- const roundsVal = o.rounds;
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.verdict === "needs_revision" ? "needs_revision" : "approved",
361
+ verdict: safeGet(final, "verdict") === "needs_revision" ? "needs_revision" : "approved",
340
362
  reportIssues: issues.length,
341
- checksRun: typeof meta.checksRun === "number" ? meta.checksRun : 0,
342
- converged: o.converged === true,
363
+ checksRun: typeof safeGet(meta, "checksRun") === "number" ? safeGet(meta, "checksRun") : 0,
364
+ converged: safeGet(o, "converged") === true,
343
365
  totalRounds,
344
- totalFindings: typeof o.totalFindings === "number" ? o.totalFindings : 0
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.type === 'resume') {
59
- const data = /** @type {Record<string, unknown>} */ (direct.data || {})
60
- if (typeof data.resumeCount === 'number' && data.resumeCount > best) {
61
- best = data.resumeCount
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
- if (direct.entry && typeof direct.entry === 'object') {
66
- const entry = /** @type {Record<string, unknown>} */ (direct.entry)
67
- if (entry.type === 'resume') {
68
- const data = /** @type {Record<string, unknown>} */ (entry.data || {})
69
- if (typeof data.resumeCount === 'number' && data.resumeCount > best) {
70
- best = data.resumeCount
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 Object.keys(direct)) {
84
- const val = direct[key]
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.type === 'image' && o.attachment && typeof o.attachment === 'object') {
119
- ref = /** @type {Record<string, unknown>} */ (o.attachment)
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.mediaType === 'string' && String(o.mediaType).startsWith('image/')) {
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.attachmentId === 'string' ? ref.attachmentId : null
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 Object.keys(o)) {
139
- const val = o[key]
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 o.convergence === 'object' &&
164
- o.convergence !== null &&
165
- Array.isArray(o.findings) &&
166
- Array.isArray(o.rounds)
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 Object.keys(o)) {
205
- const val = o[key]
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
- if (Array.isArray(s.toolCalls)) {
235
- const calls = /** @type {Array<Record<string, unknown>>} */ (s.toolCalls)
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.tool === 'iterate_review' || String(call.tool ?? '').endsWith('iterate_review')) {
240
- const result = call.result
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
- if (r.report && typeof r.report === 'object') {
244
- return /** @type {Record<string, unknown>} */ (r.report)
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
- if (Array.isArray(s.messages)) {
253
- const msgs = /** @type {Array<Record<string, unknown>>} */ (s.messages)
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
- if (!msg || !Array.isArray(msg.tool_calls)) continue
257
- const calls = /** @type {Array<Record<string, unknown>>} */ (msg.tool_calls)
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.function
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.name ?? '').endsWith('iterate_review')) {
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.arguments ?? '{}'))
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.finalReport
330
+ const final = safeGet(o, 'finalReport')
300
331
  return !!final &&
301
332
  typeof final === 'object' &&
302
- (final.verdict === 'approved' || final.verdict === 'needs_revision')
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 Object.keys(o)) {
334
- const val = o[key]
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
- if (Array.isArray(s.toolCalls)) {
361
- const calls = /** @type {Array<Record<string, unknown>>} */ (s.toolCalls)
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.tool === 'workflow' || String(call.tool ?? '').endsWith('workflow')) {
366
- const found = findRunSummaryInObject(call.result, undefined, 24)
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
- if (Array.isArray(s.messages)) {
374
- const msgs = /** @type {Array<Record<string, unknown>>} */ (s.messages)
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.content)
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.finalReport)
397
- const meta = final.metaReview && typeof final.metaReview === 'object'
398
- ? /** @type {Record<string, unknown>} */ (final.metaReview)
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.issues) ? meta.issues : []
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.rounds
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.verdict === 'needs_revision' ? 'needs_revision' : 'approved',
438
+ verdict: safeGet(final, 'verdict') === 'needs_revision' ? 'needs_revision' : 'approved',
406
439
  reportIssues: issues.length,
407
- checksRun: typeof meta.checksRun === 'number' ? meta.checksRun : 0,
408
- converged: o.converged === true,
440
+ checksRun: typeof safeGet(meta, 'checksRun') === 'number' ? safeGet(meta, 'checksRun') : 0,
441
+ converged: safeGet(o, 'converged') === true,
409
442
  totalRounds,
410
- totalFindings: typeof o.totalFindings === 'number' ? o.totalFindings : 0,
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",
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",