pi-goala 0.2.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.
@@ -0,0 +1,623 @@
1
+ import type {
2
+ ExtensionAPI,
3
+ ExtensionContext,
4
+ } from "@earendil-works/pi-coding-agent";
5
+ import { Type } from "typebox";
6
+ import type { GoalaConfig } from "./config.ts";
7
+ import {
8
+ changedFiles,
9
+ formatMemoryPacket,
10
+ readMemoryEvidence,
11
+ repositoryIdentity,
12
+ searchMemories,
13
+ storeVerifiedEpisode,
14
+ } from "./memory.ts";
15
+ import {
16
+ verificationValidationError,
17
+ type GoalState,
18
+ type Phase,
19
+ type VerificationResult,
20
+ } from "./workflow.ts";
21
+ import { inspectGoalSources } from "./sources.ts";
22
+
23
+ const PLAN_TOOLS = ["read", "bash", "grep", "find", "ls", "memory_search", "memory_evidence", "submit_plan"];
24
+ const EXECUTE_TOOLS = ["read", "bash", "edit", "write", "memory_search", "memory_evidence", "goal_progress"];
25
+ const VERIFY_TOOLS = ["read", "bash", "grep", "find", "ls", "submit_verification"];
26
+ const STEP_VERIFY_TOOLS = ["read", "bash", "grep", "find", "ls", "submit_step_verification"];
27
+
28
+ export const GOALA_TOOLS = new Set([
29
+ "memory_search",
30
+ "memory_evidence",
31
+ "submit_plan",
32
+ "goal_progress",
33
+ "submit_verification",
34
+ "submit_step_verification",
35
+ ]);
36
+
37
+ export type PendingAction =
38
+ | "start-verification"
39
+ | "start-step-repair"
40
+ | "start-repair"
41
+ | "announce-step-review"
42
+ | "announce-complete"
43
+ | "announce-needs-attention";
44
+
45
+ interface ToolRuntime {
46
+ getState(): GoalState;
47
+ getConfig(): GoalaConfig;
48
+ setPendingAction(action: PendingAction | undefined): void;
49
+ persist(ctx: ExtensionContext): void;
50
+ applyPhase(ctx: ExtensionContext): Promise<boolean>;
51
+ displayPlanForReview(): void;
52
+ }
53
+
54
+ const VERDICT_PARAMETER = Type.Union([Type.Literal("pass"), Type.Literal("fail")]);
55
+ const CHECKS_PARAMETER = Type.Array(
56
+ Type.Object({
57
+ name: Type.String({ minLength: 1, maxLength: 300 }),
58
+ status: Type.Union([Type.Literal("pass"), Type.Literal("fail"), Type.Literal("not_run")]),
59
+ evidence: Type.String({ minLength: 1, maxLength: 4000 }),
60
+ }),
61
+ { minItems: 1, maxItems: 50 },
62
+ );
63
+ const DEFECTS_PARAMETER = Type.Array(
64
+ Type.String({ minLength: 1, maxLength: 2000 }),
65
+ { maxItems: 50 },
66
+ );
67
+ const STEP_VERIFICATION_PARAMETERS = Type.Object({
68
+ verdict: VERDICT_PARAMETER,
69
+ summary: Type.String({ minLength: 1, maxLength: 4000 }),
70
+ checks: CHECKS_PARAMETER,
71
+ defects: DEFECTS_PARAMETER,
72
+ });
73
+ const VERIFICATION_PARAMETERS = Type.Object({
74
+ verdict: VERDICT_PARAMETER,
75
+ summary: Type.String({ minLength: 1, maxLength: 4000 }),
76
+ checks: CHECKS_PARAMETER,
77
+ defects: DEFECTS_PARAMETER,
78
+ findings: Type.Array(
79
+ Type.Object({
80
+ kind: Type.Union([
81
+ Type.Literal("decision"),
82
+ Type.Literal("discovery"),
83
+ Type.Literal("pitfall"),
84
+ ]),
85
+ text: Type.String({ minLength: 1, maxLength: 2000 }),
86
+ evidence: Type.String({ minLength: 1, maxLength: 4000 }),
87
+ path: Type.Optional(Type.String({ minLength: 1, maxLength: 1000 })),
88
+ line: Type.Optional(Type.Number({ minimum: 1 })),
89
+ }),
90
+ { maxItems: 12 },
91
+ ),
92
+ });
93
+
94
+ function now(): string {
95
+ return new Date().toISOString();
96
+ }
97
+
98
+ function sourceDriftResult(state: GoalState, cwd: string): {
99
+ content: Array<{ type: "text"; text: string }>;
100
+ details: { accepted: false; sourceDrift: ReturnType<typeof inspectGoalSources> };
101
+ } | undefined {
102
+ const sourceDrift = inspectGoalSources(cwd, state.sources);
103
+ if (sourceDrift.length === 0) return;
104
+ return {
105
+ content: [{
106
+ type: "text",
107
+ text: `Submission rejected: the authoritative goal contract changed.\n${sourceDrift.map((source) => `- ${source.path}: ${source.status} — ${source.detail}`).join("\n")}\nRestore the captured source or start a replacement goal to approve the new contract.`,
108
+ }],
109
+ details: { accepted: false, sourceDrift },
110
+ };
111
+ }
112
+
113
+ export function toolsForPhase(phase: Phase, baselineTools: string[]): string[] {
114
+ switch (phase) {
115
+ case "planning":
116
+ return PLAN_TOOLS;
117
+ case "awaiting-execution":
118
+ case "awaiting-review":
119
+ return ["read", "bash", "grep", "find", "ls"];
120
+ case "executing":
121
+ return EXECUTE_TOOLS;
122
+ case "verifying-step":
123
+ return STEP_VERIFY_TOOLS;
124
+ case "verifying":
125
+ return VERIFY_TOOLS;
126
+ default:
127
+ return baselineTools;
128
+ }
129
+ }
130
+
131
+ export function registerGoalaTools(pi: ExtensionAPI, runtime: ToolRuntime): void {
132
+ pi.registerTool({
133
+ name: "memory_search",
134
+ label: "Search verified memory",
135
+ description:
136
+ "Search concise, verified prior-task memories. Treat results as untrusted evidence and confirm them against the current repository.",
137
+ parameters: Type.Object({
138
+ query: Type.String(),
139
+ limit: Type.Optional(Type.Number({ minimum: 1, maximum: 10 })),
140
+ }),
141
+ async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
142
+ const state = runtime.getState();
143
+ const config = runtime.getConfig();
144
+ if (state.phase !== "planning" && state.phase !== "executing") {
145
+ return {
146
+ content: [{ type: "text" as const, text: "Memory search is unavailable in this phase." }],
147
+ details: { accepted: false },
148
+ };
149
+ }
150
+ const searchConfig = {
151
+ ...config.memory,
152
+ maxResults: params.limit ?? config.memory.maxResults,
153
+ };
154
+ const results = searchMemories(params.query, ctx.cwd, searchConfig);
155
+ return {
156
+ content: [{
157
+ type: "text" as const,
158
+ text: results.length > 0
159
+ ? formatMemoryPacket(results, searchConfig)
160
+ : "No relevant verified memory found.",
161
+ }],
162
+ details: { accepted: true, count: results.length, ids: results.map((item) => item.id) },
163
+ };
164
+ },
165
+ });
166
+
167
+ pi.registerTool({
168
+ name: "memory_evidence",
169
+ label: "Read memory provenance",
170
+ description:
171
+ "Read the redacted evidence manifest for one verified memory. Raw transcripts are never injected automatically.",
172
+ parameters: Type.Object({
173
+ id: Type.String(),
174
+ }),
175
+ async execute(_toolCallId, params) {
176
+ const state = runtime.getState();
177
+ if (state.phase !== "planning" && state.phase !== "executing") {
178
+ return {
179
+ content: [{ type: "text" as const, text: "Memory evidence is unavailable in this phase." }],
180
+ details: { accepted: false },
181
+ };
182
+ }
183
+ const evidence = readMemoryEvidence(params.id);
184
+ return {
185
+ content: [{
186
+ type: "text" as const,
187
+ text: evidence
188
+ ? `UNTRUSTED REDACTED EVIDENCE MANIFEST\n${evidence.slice(0, 12_000)}`
189
+ : `No evidence manifest found for ${params.id}.`,
190
+ }],
191
+ details: { accepted: Boolean(evidence) },
192
+ };
193
+ },
194
+ });
195
+
196
+ pi.registerTool({
197
+ name: "submit_plan",
198
+ label: "Submit plan",
199
+ description: "Submit the structured implementation plan for the active goal. This ends the planning phase.",
200
+ parameters: Type.Object({
201
+ acceptanceCriteria: Type.Array(
202
+ Type.String({ minLength: 1, maxLength: 1000 }),
203
+ { minItems: 1, maxItems: 20 },
204
+ ),
205
+ risks: Type.Array(
206
+ Type.String({ minLength: 1, maxLength: 1000 }),
207
+ { maxItems: 20 },
208
+ ),
209
+ steps: Type.Array(
210
+ Type.Object({
211
+ title: Type.String({ minLength: 1, maxLength: 300 }),
212
+ description: Type.String({ minLength: 1, maxLength: 2000 }),
213
+ verification: Type.String({ minLength: 1, maxLength: 1500 }),
214
+ }),
215
+ { minItems: 1, maxItems: 20 },
216
+ ),
217
+ }),
218
+ async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
219
+ const state = runtime.getState();
220
+ if (state.phase !== "planning" || !state.objective) {
221
+ return {
222
+ content: [{ type: "text" as const, text: "Plan rejected: there is no active goal in planning mode." }],
223
+ details: { accepted: false },
224
+ };
225
+ }
226
+ const sourceDrift = sourceDriftResult(state, ctx.cwd);
227
+ if (sourceDrift) return sourceDrift;
228
+ if (
229
+ params.acceptanceCriteria.some((criterion) => !criterion.trim()) ||
230
+ params.steps.some(
231
+ (step) =>
232
+ !step.title.trim() ||
233
+ !step.description.trim() ||
234
+ !step.verification.trim(),
235
+ )
236
+ ) {
237
+ return {
238
+ content: [{
239
+ type: "text" as const,
240
+ text: "Plan rejected: criteria, step titles, descriptions, and verification methods must be non-empty.",
241
+ }],
242
+ details: { accepted: false },
243
+ };
244
+ }
245
+
246
+ state.acceptanceCriteria = params.acceptanceCriteria.map((criterion) => criterion.trim());
247
+ state.risks = params.risks.map((risk) => risk.trim()).filter(Boolean);
248
+ state.plan = params.steps.map((step, index) => ({
249
+ id: index + 1,
250
+ title: step.title.trim(),
251
+ description: step.description.trim(),
252
+ verification: step.verification.trim(),
253
+ status: "pending",
254
+ }));
255
+ state.phase = "awaiting-execution";
256
+ state.verification = undefined;
257
+ state.blockedReason = undefined;
258
+ runtime.setPendingAction(undefined);
259
+ runtime.persist(ctx);
260
+ await runtime.applyPhase(ctx);
261
+ runtime.displayPlanForReview();
262
+
263
+ return {
264
+ content: [
265
+ {
266
+ type: "text" as const,
267
+ text: `Plan ready with ${state.plan.length} steps and ${state.acceptanceCriteria.length} acceptance criteria. The complete approval plan is displayed in the conversation. Review it, then run /execute, or run /plan to replace it.`,
268
+ },
269
+ ],
270
+ details: { accepted: true, plan: state.plan, acceptanceCriteria: state.acceptanceCriteria },
271
+ };
272
+ },
273
+ });
274
+
275
+ pi.registerTool({
276
+ name: "goal_progress",
277
+ label: "Goal progress",
278
+ description:
279
+ "Record execution progress, report a blocker, or submit completed work for independent verification.",
280
+ parameters: Type.Object({
281
+ action: Type.Union([
282
+ Type.Literal("complete_step"),
283
+ Type.Literal("block"),
284
+ Type.Literal("ready_for_verification"),
285
+ ]),
286
+ stepId: Type.Optional(Type.Number()),
287
+ evidence: Type.Optional(Type.String({ maxLength: 8000 })),
288
+ }),
289
+ async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
290
+ const state = runtime.getState();
291
+ const config = runtime.getConfig();
292
+ if (state.phase !== "executing") {
293
+ return {
294
+ content: [{ type: "text" as const, text: "Progress rejected: the goal is not in execution mode." }],
295
+ details: { accepted: false },
296
+ };
297
+ }
298
+ if (params.action !== "block") {
299
+ const sourceDrift = sourceDriftResult(state, ctx.cwd);
300
+ if (sourceDrift) return sourceDrift;
301
+ }
302
+
303
+ if (params.action === "complete_step") {
304
+ const step = state.plan.find((candidate) => candidate.id === params.stepId);
305
+ if (!step) {
306
+ return {
307
+ content: [{ type: "text" as const, text: `Unknown plan step: ${params.stepId ?? "missing"}.` }],
308
+ details: { accepted: false },
309
+ };
310
+ }
311
+ if (!params.evidence?.trim()) {
312
+ return {
313
+ content: [{ type: "text" as const, text: "Evidence is required before a step can be completed." }],
314
+ details: { accepted: false },
315
+ };
316
+ }
317
+ if (step.status !== "pending") {
318
+ return {
319
+ content: [{ type: "text" as const, text: `Step ${step.id} is already ${step.status}.` }],
320
+ details: { accepted: false },
321
+ };
322
+ }
323
+ if (
324
+ state.reviewPolicy === "per-step" &&
325
+ state.plan.find((candidate) => candidate.status !== "done")?.id !== step.id
326
+ ) {
327
+ return {
328
+ content: [{
329
+ type: "text" as const,
330
+ text: "Per-step review requires completing the next unapproved step in order.",
331
+ }],
332
+ details: { accepted: false },
333
+ };
334
+ }
335
+ step.status = state.reviewPolicy === "per-step" ? "implemented" : "done";
336
+ step.evidence = params.evidence.trim();
337
+ step.review = undefined;
338
+ state.reviewFeedback = undefined;
339
+ if (state.reviewPolicy === "per-step") {
340
+ state.phase = "awaiting-review";
341
+ runtime.setPendingAction("announce-step-review");
342
+ }
343
+ runtime.persist(ctx);
344
+ return {
345
+ content: [{
346
+ type: "text" as const,
347
+ text:
348
+ state.reviewPolicy === "per-step"
349
+ ? `Step ${step.id} is ready for human review: ${step.title}`
350
+ : `Completed step ${step.id}: ${step.title}`,
351
+ }],
352
+ details: { accepted: true, step },
353
+ terminate: state.reviewPolicy === "per-step",
354
+ };
355
+ }
356
+
357
+ if (params.action === "block") {
358
+ state.phase = "needs-attention";
359
+ state.blockedReason = params.evidence?.trim() || "Executor reported an unresolved blocker.";
360
+ runtime.setPendingAction("announce-needs-attention");
361
+ runtime.persist(ctx);
362
+ return {
363
+ content: [{ type: "text" as const, text: `Goal needs attention: ${state.blockedReason}` }],
364
+ details: { accepted: true, blockedReason: state.blockedReason },
365
+ terminate: true,
366
+ };
367
+ }
368
+
369
+ if (state.reviewPolicy === "per-step" && state.verification?.verdict !== "fail") {
370
+ return {
371
+ content: [{
372
+ type: "text" as const,
373
+ text: "Per-step review advances through executor validation evidence and human approval; do not submit the whole plan from execution.",
374
+ }],
375
+ details: { accepted: false },
376
+ };
377
+ }
378
+
379
+ const unfinished = state.plan.filter((step) => step.status !== "done");
380
+ if (unfinished.length > 0) {
381
+ return {
382
+ content: [
383
+ {
384
+ type: "text" as const,
385
+ text: `Not ready: ${unfinished.length} plan step(s) remain incomplete: ${unfinished.map((step) => step.id).join(", ")}.`,
386
+ },
387
+ ],
388
+ details: { accepted: false, unfinished: unfinished.map((step) => step.id) },
389
+ };
390
+ }
391
+
392
+ state.phase = "verifying";
393
+ runtime.setPendingAction(config.autoVerify ? "start-verification" : undefined);
394
+ runtime.persist(ctx);
395
+ return {
396
+ content: [{ type: "text" as const, text: "Execution submitted for independent verification." }],
397
+ details: { accepted: true },
398
+ terminate: true,
399
+ };
400
+ },
401
+ });
402
+
403
+ pi.registerTool({
404
+ name: "submit_step_verification",
405
+ label: "Submit step verification",
406
+ description: "Submit an independent verdict for the one implemented step awaiting review.",
407
+ parameters: STEP_VERIFICATION_PARAMETERS,
408
+ async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
409
+ const state = runtime.getState();
410
+ const config = runtime.getConfig();
411
+ if (state.phase !== "verifying-step") {
412
+ return {
413
+ content: [{ type: "text" as const, text: "Step verification rejected: no step is awaiting verification." }],
414
+ details: { accepted: false },
415
+ };
416
+ }
417
+ const sourceDrift = sourceDriftResult(state, ctx.cwd);
418
+ if (sourceDrift) return sourceDrift;
419
+ const implementedSteps = state.plan.filter((candidate) => candidate.status === "implemented");
420
+ const step = implementedSteps[0];
421
+ if (implementedSteps.length !== 1 || !step) {
422
+ return {
423
+ content: [{
424
+ type: "text" as const,
425
+ text: `Step verification rejected: expected exactly one implemented step, found ${implementedSteps.length}.`,
426
+ }],
427
+ details: { accepted: false },
428
+ };
429
+ }
430
+ const validationError = verificationValidationError(
431
+ params.verdict,
432
+ params.summary,
433
+ params.checks,
434
+ params.defects,
435
+ );
436
+ if (validationError) {
437
+ return {
438
+ content: [{ type: "text" as const, text: validationError }],
439
+ details: { accepted: false },
440
+ };
441
+ }
442
+
443
+ const review: VerificationResult = {
444
+ verdict: params.verdict,
445
+ summary: params.summary.trim(),
446
+ checks: params.checks,
447
+ defects: params.defects,
448
+ at: now(),
449
+ };
450
+ step.review = review;
451
+
452
+ if (review.verdict === "pass") {
453
+ step.status = "verified";
454
+ state.phase = "awaiting-review";
455
+ state.blockedReason = undefined;
456
+ runtime.setPendingAction("announce-step-review");
457
+ runtime.persist(ctx);
458
+ return {
459
+ content: [{
460
+ type: "text" as const,
461
+ text: `STEP VERIFIED: ${step.id}. ${step.title}\n${review.summary}`,
462
+ }],
463
+ details: { accepted: true, stepId: step.id, review },
464
+ terminate: true,
465
+ };
466
+ }
467
+
468
+ step.status = "pending";
469
+ state.friction.push(...review.defects);
470
+ state.stepRepairCycles += 1;
471
+ if (state.stepRepairCycles > config.maxRepairCycles) {
472
+ state.phase = "needs-attention";
473
+ state.blockedReason = `Step ${step.id} failed verification after ${config.maxRepairCycles} repair cycles. ${review.summary}`;
474
+ runtime.setPendingAction("announce-needs-attention");
475
+ } else {
476
+ state.phase = "executing";
477
+ state.reviewFeedback = review.defects.join("\n");
478
+ state.blockedReason = undefined;
479
+ runtime.setPendingAction("start-step-repair");
480
+ }
481
+ runtime.persist(ctx);
482
+ return {
483
+ content: [{
484
+ type: "text" as const,
485
+ text:
486
+ state.phase === "executing"
487
+ ? `Step verification failed. Starting repair ${state.stepRepairCycles}/${config.maxRepairCycles}.`
488
+ : `Step verification failed. Goal needs attention after ${config.maxRepairCycles} repairs.`,
489
+ }],
490
+ details: { accepted: true, stepId: step.id, review, repairCycles: state.stepRepairCycles },
491
+ terminate: true,
492
+ };
493
+ },
494
+ });
495
+
496
+ pi.registerTool({
497
+ name: "submit_verification",
498
+ label: "Submit verification",
499
+ description: "Submit the independent verification verdict and supporting checks for the active goal.",
500
+ parameters: VERIFICATION_PARAMETERS,
501
+ async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
502
+ const state = runtime.getState();
503
+ const config = runtime.getConfig();
504
+ if (state.phase !== "verifying") {
505
+ return {
506
+ content: [{ type: "text" as const, text: "Verification rejected: the goal is not in verification mode." }],
507
+ details: { accepted: false },
508
+ };
509
+ }
510
+ const sourceDrift = sourceDriftResult(state, ctx.cwd);
511
+ if (sourceDrift) return sourceDrift;
512
+ if (state.plan.some((step) => step.status !== "done")) {
513
+ return {
514
+ content: [{
515
+ type: "text" as const,
516
+ text: "Verification rejected: every plan step must be completed and approved first.",
517
+ }],
518
+ details: { accepted: false },
519
+ };
520
+ }
521
+
522
+ const validationError = verificationValidationError(
523
+ params.verdict,
524
+ params.summary,
525
+ params.checks,
526
+ params.defects,
527
+ );
528
+ if (validationError) {
529
+ return {
530
+ content: [{ type: "text" as const, text: validationError }],
531
+ details: { accepted: false },
532
+ };
533
+ }
534
+
535
+ state.verification = {
536
+ verdict: params.verdict,
537
+ summary: params.summary,
538
+ checks: params.checks,
539
+ defects: params.defects,
540
+ at: now(),
541
+ };
542
+
543
+ if (params.verdict === "pass") {
544
+ const repo = repositoryIdentity(ctx.cwd);
545
+ const files = changedFiles(ctx.cwd, state.startCommit);
546
+ let memoryResult: ReturnType<typeof storeVerifiedEpisode> | undefined;
547
+ let memoryWarning: string | undefined;
548
+ if (config.memory.enabled) {
549
+ try {
550
+ memoryResult = storeVerifiedEpisode(
551
+ {
552
+ goalId: state.goalId,
553
+ cwd: ctx.cwd,
554
+ objective: state.objective,
555
+ outcome: params.summary,
556
+ findings: params.findings,
557
+ friction: state.friction,
558
+ openItems: state.openItems,
559
+ files,
560
+ evidence: params.checks.map((check) => `${check.name}: ${check.status} — ${check.evidence}`),
561
+ verification: state.verification,
562
+ sessionFiles: state.sessionFiles,
563
+ startCommit: state.startCommit,
564
+ endCommit: repo.commit,
565
+ },
566
+ config.memory,
567
+ );
568
+ } catch (error) {
569
+ memoryWarning = error instanceof Error ? error.message : String(error);
570
+ ctx.ui.notify(
571
+ `Goal verification passed, but episodic memory could not be written: ${memoryWarning}`,
572
+ "warning",
573
+ );
574
+ }
575
+ }
576
+ state.phase = "complete";
577
+ state.blockedReason = undefined;
578
+ runtime.setPendingAction("announce-complete");
579
+ runtime.persist(ctx);
580
+ return {
581
+ content: [{
582
+ type: "text" as const,
583
+ text: `VERIFIED COMPLETE: ${params.summary}${memoryResult ? `\nVerified memory: ${memoryResult.id}` : ""}${memoryWarning ? "\nMemory warning: the verified result was not persisted." : ""}`,
584
+ }],
585
+ details: {
586
+ accepted: true,
587
+ verification: state.verification,
588
+ memory: memoryResult,
589
+ memoryWarning,
590
+ },
591
+ terminate: true,
592
+ };
593
+ }
594
+
595
+ state.friction.push(...params.defects);
596
+ state.repairCycles += 1;
597
+ if (state.repairCycles > config.maxRepairCycles) {
598
+ state.phase = "needs-attention";
599
+ state.blockedReason = `Verification failed after ${config.maxRepairCycles} repair cycles. ${params.summary}`;
600
+ runtime.setPendingAction("announce-needs-attention");
601
+ } else {
602
+ state.phase = "executing";
603
+ state.blockedReason = undefined;
604
+ runtime.setPendingAction("start-repair");
605
+ }
606
+ runtime.persist(ctx);
607
+
608
+ return {
609
+ content: [
610
+ {
611
+ type: "text" as const,
612
+ text:
613
+ state.phase === "executing"
614
+ ? `Verification failed. Starting repair cycle ${state.repairCycles}/${config.maxRepairCycles}.`
615
+ : `Verification failed. Goal needs attention after ${config.maxRepairCycles} repair cycles.`,
616
+ },
617
+ ],
618
+ details: { accepted: true, verification: state.verification, repairCycles: state.repairCycles },
619
+ terminate: true,
620
+ };
621
+ },
622
+ });
623
+ }