taskplane 0.23.6 → 0.23.7
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.
|
@@ -14,5 +14,8 @@ import { fileURLToPath } from "node:url";
|
|
|
14
14
|
import { dirname, join } from "node:path";
|
|
15
15
|
|
|
16
16
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
17
|
-
|
|
17
|
+
// TP-115: Disable jiti filesystem cache to prevent stale compiled code
|
|
18
|
+
// after npm update. Without this, jiti serves old cached .mjs even when
|
|
19
|
+
// the .ts source files have been updated by a new package version.
|
|
20
|
+
const jiti = createJiti(import.meta.url, { fsCache: false });
|
|
18
21
|
await jiti.import(join(__dirname, "engine-worker.ts"));
|
|
@@ -1,575 +1,581 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* -
|
|
9
|
-
* -
|
|
10
|
-
* -
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
import {
|
|
21
|
-
|
|
22
|
-
import {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
import {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
type
|
|
54
|
-
type
|
|
55
|
-
type
|
|
56
|
-
type
|
|
57
|
-
type
|
|
58
|
-
type
|
|
59
|
-
type
|
|
60
|
-
type
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
*
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
|
|
89
|
-
/** Worker
|
|
90
|
-
|
|
91
|
-
/** Worker
|
|
92
|
-
|
|
93
|
-
/** Worker
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
|
|
103
|
-
/** Context pressure
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
*
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
|
|
121
|
-
/**
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
*
|
|
131
|
-
*
|
|
132
|
-
*
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
*
|
|
136
|
-
*
|
|
137
|
-
*
|
|
138
|
-
*
|
|
139
|
-
*
|
|
140
|
-
*
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
*
|
|
144
|
-
*
|
|
145
|
-
*
|
|
146
|
-
*
|
|
147
|
-
*
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
const
|
|
157
|
-
const
|
|
158
|
-
const
|
|
159
|
-
const
|
|
160
|
-
const
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
let
|
|
177
|
-
let
|
|
178
|
-
|
|
179
|
-
let
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
const
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
`
|
|
222
|
-
|
|
223
|
-
`
|
|
224
|
-
`
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
`
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
const
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
const
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
if (pct >= config.
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
//
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
noProgressCount
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
status
|
|
478
|
-
)
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
1
|
+
// DEBUG: module load marker
|
|
2
|
+
try { require('fs').writeFileSync('/c/dev/taskplane/.pi/tp-lane-runner-loaded.txt', 'loaded at ' + new Date().toISOString() + ' from ' + __filename); } catch(e) { try { require('fs').writeFileSync('/c/dev/taskplane/.pi/tp-lane-runner-loaded.txt', 'loaded at ' + new Date().toISOString() + ' err: ' + e); } catch {} }
|
|
3
|
+
/**
|
|
4
|
+
* Lane Runner — Headless per-lane execution for Runtime V2
|
|
5
|
+
*
|
|
6
|
+
* Replaces the legacy TMUX-backed lane execution path with a
|
|
7
|
+
* deterministic Node process that owns:
|
|
8
|
+
* - worker iteration loops
|
|
9
|
+
* - STATUS.md progression
|
|
10
|
+
* - .DONE creation detection
|
|
11
|
+
* - reviewer orchestration (future)
|
|
12
|
+
* - lane snapshot emission
|
|
13
|
+
*
|
|
14
|
+
* No Pi extension dependency. No TMUX. No TASK_AUTOSTART.
|
|
15
|
+
*
|
|
16
|
+
* @module taskplane/lane-runner
|
|
17
|
+
* @since TP-105
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync, unlinkSync } from "fs";
|
|
21
|
+
import { join, dirname, resolve } from "path";
|
|
22
|
+
import { fileURLToPath } from "url";
|
|
23
|
+
|
|
24
|
+
import {
|
|
25
|
+
parsePromptMd,
|
|
26
|
+
parseStatusMd,
|
|
27
|
+
generateStatusMd,
|
|
28
|
+
updateStatusField,
|
|
29
|
+
updateStepStatus,
|
|
30
|
+
logExecution,
|
|
31
|
+
isStepComplete,
|
|
32
|
+
type StepInfo,
|
|
33
|
+
type CoreParsedTask,
|
|
34
|
+
} from "./task-executor-core.ts";
|
|
35
|
+
|
|
36
|
+
import { spawnAgent, type AgentHostOptions, type AgentHostResult } from "./agent-host.ts";
|
|
37
|
+
|
|
38
|
+
import {
|
|
39
|
+
appendAgentEvent,
|
|
40
|
+
writeLaneSnapshot,
|
|
41
|
+
} from "./process-registry.ts";
|
|
42
|
+
|
|
43
|
+
import {
|
|
44
|
+
readOutbox,
|
|
45
|
+
ackOutboxMessage,
|
|
46
|
+
appendMailboxAuditEvent,
|
|
47
|
+
} from "./mailbox.ts";
|
|
48
|
+
|
|
49
|
+
import {
|
|
50
|
+
resolvePacketPaths,
|
|
51
|
+
buildRuntimeAgentId,
|
|
52
|
+
runtimeAgentEventsPath,
|
|
53
|
+
type ExecutionUnit,
|
|
54
|
+
type RuntimeAgentId,
|
|
55
|
+
type RuntimeLaneSnapshot,
|
|
56
|
+
type RuntimeAgentTelemetrySnapshot,
|
|
57
|
+
type RuntimeTaskProgress,
|
|
58
|
+
type RuntimeAgentStatus,
|
|
59
|
+
type PacketPaths,
|
|
60
|
+
type LaneTaskOutcome,
|
|
61
|
+
type LaneTaskStatus,
|
|
62
|
+
type SupervisorAlertCallback,
|
|
63
|
+
} from "./types.ts";
|
|
64
|
+
|
|
65
|
+
const LANE_RUNNER_DIR = dirname(fileURLToPath(import.meta.url));
|
|
66
|
+
|
|
67
|
+
// ── Types ────────────────────────────────────────────────────────────
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Configuration for a lane-runner execution.
|
|
71
|
+
*
|
|
72
|
+
* @since TP-105
|
|
73
|
+
*/
|
|
74
|
+
export interface LaneRunnerConfig {
|
|
75
|
+
/** Batch ID */
|
|
76
|
+
batchId: string;
|
|
77
|
+
/** Operator prefix for agent IDs (e.g., "orch-henrylach") */
|
|
78
|
+
agentIdPrefix: string;
|
|
79
|
+
/** Lane number (1-indexed) */
|
|
80
|
+
laneNumber: number;
|
|
81
|
+
/** Absolute path to the lane worktree */
|
|
82
|
+
worktreePath: string;
|
|
83
|
+
/** Git branch checked out in the worktree */
|
|
84
|
+
branch: string;
|
|
85
|
+
/** Repo ID */
|
|
86
|
+
repoId: string;
|
|
87
|
+
/** State root for runtime artifacts (workspace root or repo root) */
|
|
88
|
+
stateRoot: string;
|
|
89
|
+
/** Worker model (empty string = inherit from session) */
|
|
90
|
+
workerModel: string;
|
|
91
|
+
/** Worker tools */
|
|
92
|
+
workerTools: string;
|
|
93
|
+
/** Worker thinking mode */
|
|
94
|
+
workerThinking: string;
|
|
95
|
+
/** Worker system prompt */
|
|
96
|
+
workerSystemPrompt: string;
|
|
97
|
+
/** Max worker iterations before giving up */
|
|
98
|
+
maxIterations: number;
|
|
99
|
+
/** No-progress stall limit */
|
|
100
|
+
noProgressLimit: number;
|
|
101
|
+
/** Max worker time in minutes per iteration */
|
|
102
|
+
maxWorkerMinutes: number;
|
|
103
|
+
/** Context pressure warn threshold (0-100) */
|
|
104
|
+
warnPercent: number;
|
|
105
|
+
/** Context pressure kill threshold (0-100) */
|
|
106
|
+
killPercent: number;
|
|
107
|
+
/** Optional callback for surfacing runtime mailbox replies/escalations to supervisor */
|
|
108
|
+
onSupervisorAlert?: SupervisorAlertCallback;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Result of executing one task through the lane-runner.
|
|
113
|
+
*
|
|
114
|
+
* @since TP-105
|
|
115
|
+
*/
|
|
116
|
+
export interface LaneRunnerTaskResult {
|
|
117
|
+
/** Standard lane task outcome compatible with the engine */
|
|
118
|
+
outcome: LaneTaskOutcome;
|
|
119
|
+
/** Total worker iterations consumed */
|
|
120
|
+
iterations: number;
|
|
121
|
+
/** Cumulative worker cost in USD */
|
|
122
|
+
costUsd: number;
|
|
123
|
+
/** Total tokens used */
|
|
124
|
+
totalTokens: number;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// ── Core Execution ───────────────────────────────────────────────────
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Execute a single task in a lane using the Runtime V2 headless backend.
|
|
131
|
+
*
|
|
132
|
+
* This is the core function that replaces the legacy TMUX-backed
|
|
133
|
+
* `executeLane()` → `spawnLaneSession()` → `task-runner TASK_AUTOSTART`
|
|
134
|
+
* path with direct child-process hosting.
|
|
135
|
+
*
|
|
136
|
+
* Execution loop:
|
|
137
|
+
* 1. Parse task and ensure STATUS.md exists
|
|
138
|
+
* 2. For each iteration:
|
|
139
|
+
* a. Determine remaining steps
|
|
140
|
+
* b. Spawn worker agent via agent-host
|
|
141
|
+
* c. Wait for worker to exit
|
|
142
|
+
* d. Check progress (checkboxes)
|
|
143
|
+
* e. If all steps complete → success
|
|
144
|
+
* f. If no progress → increment stall counter
|
|
145
|
+
* g. If stall limit or iteration limit hit → fail
|
|
146
|
+
* 3. If all steps complete, check for .DONE
|
|
147
|
+
* 4. Return LaneTaskOutcome
|
|
148
|
+
*
|
|
149
|
+
* @since TP-105
|
|
150
|
+
*/
|
|
151
|
+
export async function executeTaskV2(
|
|
152
|
+
unit: ExecutionUnit,
|
|
153
|
+
config: LaneRunnerConfig,
|
|
154
|
+
pauseSignal: { paused: boolean },
|
|
155
|
+
): Promise<LaneRunnerTaskResult> {
|
|
156
|
+
const startTime = Date.now();
|
|
157
|
+
const statusPath = unit.packet.statusPath;
|
|
158
|
+
const donePath = unit.packet.donePath;
|
|
159
|
+
const promptPath = unit.packet.promptPath;
|
|
160
|
+
const taskFolder = unit.packet.taskFolder;
|
|
161
|
+
const taskId = unit.taskId;
|
|
162
|
+
const workerAgentId = buildRuntimeAgentId(config.agentIdPrefix, config.laneNumber, "worker");
|
|
163
|
+
|
|
164
|
+
// ── 1. Ensure STATUS.md exists ──────────────────────────────────
|
|
165
|
+
if (!existsSync(statusPath)) {
|
|
166
|
+
const content = readFileSync(promptPath, "utf-8");
|
|
167
|
+
const parsed = parsePromptMd(content, promptPath);
|
|
168
|
+
writeFileSync(statusPath, generateStatusMd(parsed));
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
updateStatusField(statusPath, "Status", "🟡 In Progress");
|
|
172
|
+
updateStatusField(statusPath, "Last Updated", new Date().toISOString().slice(0, 10));
|
|
173
|
+
logExecution(statusPath, "Task started", "Runtime V2 lane-runner execution");
|
|
174
|
+
|
|
175
|
+
// ── 2. Iteration loop ───────────────────────────────────────────
|
|
176
|
+
let noProgressCount = 0;
|
|
177
|
+
let totalIterations = 0;
|
|
178
|
+
let cumulativeCostUsd = 0;
|
|
179
|
+
let cumulativeTokens = 0;
|
|
180
|
+
// TP-115: carry latest worker telemetry across iterations and into post-loop terminal snapshots
|
|
181
|
+
let lastTelemetry: Partial<AgentHostResult> = {};
|
|
182
|
+
|
|
183
|
+
for (let iter = 0; iter < config.maxIterations; iter++) {
|
|
184
|
+
if (pauseSignal.paused) {
|
|
185
|
+
logExecution(statusPath, "Paused", `User paused at iteration ${totalIterations}`);
|
|
186
|
+
return makeResult(taskId, workerAgentId, "skipped", startTime,
|
|
187
|
+
"Paused by user", false, totalIterations, cumulativeCostUsd, cumulativeTokens, config, statusPath);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Determine remaining steps
|
|
191
|
+
const currentStatus = parseStatusMd(readFileSync(statusPath, "utf-8"));
|
|
192
|
+
const parsed = parsePromptMd(readFileSync(promptPath, "utf-8"), promptPath);
|
|
193
|
+
const remainingSteps = parsed.steps.filter(step => {
|
|
194
|
+
const ss = currentStatus.steps.find(s => s.number === step.number);
|
|
195
|
+
return !isStepComplete(ss);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
if (remainingSteps.length === 0) break; // All done
|
|
199
|
+
|
|
200
|
+
totalIterations++;
|
|
201
|
+
updateStatusField(statusPath, "Current Step", `Step ${remainingSteps[0].number}: ${remainingSteps[0].name}`);
|
|
202
|
+
updateStatusField(statusPath, "Iteration", `${totalIterations}`);
|
|
203
|
+
|
|
204
|
+
// Mark first incomplete step as in-progress
|
|
205
|
+
const firstStep = remainingSteps[0];
|
|
206
|
+
const firstStepStatus = currentStatus.steps.find(s => s.number === firstStep.number);
|
|
207
|
+
if (firstStepStatus?.status !== "in-progress") {
|
|
208
|
+
updateStepStatus(statusPath, firstStep.number, "in-progress");
|
|
209
|
+
logExecution(statusPath, `Step ${firstStep.number} started`, firstStep.name);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Count checkboxes before worker runs
|
|
213
|
+
const prevTotalChecked = currentStatus.steps.reduce((sum, s) => sum + s.totalChecked, 0);
|
|
214
|
+
|
|
215
|
+
// ── Build worker prompt ─────────────────────────────────────
|
|
216
|
+
const wrapUpFile = join(taskFolder, ".task-wrap-up");
|
|
217
|
+
if (existsSync(wrapUpFile)) try { unlinkSync(wrapUpFile); } catch { /* ignore */ }
|
|
218
|
+
|
|
219
|
+
const promptLines = [
|
|
220
|
+
`Read your task instructions at: ${promptPath}`,
|
|
221
|
+
`Read your execution state at: ${statusPath}`,
|
|
222
|
+
``,
|
|
223
|
+
`Task: ${taskId}`,
|
|
224
|
+
`Task folder: ${taskFolder}/`,
|
|
225
|
+
`Iteration: ${totalIterations}`,
|
|
226
|
+
`Wrap-up signal file: ${wrapUpFile}`,
|
|
227
|
+
``,
|
|
228
|
+
`⚠️ ORCHESTRATED RUN: Do NOT archive or move the task folder. The orchestrator handles post-merge archival.`,
|
|
229
|
+
];
|
|
230
|
+
|
|
231
|
+
if (totalIterations > 1 && remainingSteps.length > 0) {
|
|
232
|
+
const remainingSet = new Set(remainingSteps.map(s => s.number));
|
|
233
|
+
const completedSteps = parsed.steps.filter(s => !remainingSet.has(s.number));
|
|
234
|
+
promptLines.push(
|
|
235
|
+
``,
|
|
236
|
+
`IMPORTANT: You exited previously without completing all steps.`,
|
|
237
|
+
`Completed (do not redo): ${completedSteps.map(s => `Step ${s.number}: ${s.name}`).join(", ") || "(none)"}`,
|
|
238
|
+
`Remaining (focus here): ${remainingSteps.map(s => `Step ${s.number}: ${s.name}`).join(", ")}`,
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// ── Spawn worker ────────────────────────────────────────────
|
|
243
|
+
const eventsPath = runtimeAgentEventsPath(config.stateRoot, config.batchId, workerAgentId);
|
|
244
|
+
|
|
245
|
+
const mailboxDir = join(config.stateRoot, ".pi", "mailbox", config.batchId, workerAgentId);
|
|
246
|
+
mkdirSync(join(mailboxDir, "inbox"), { recursive: true });
|
|
247
|
+
|
|
248
|
+
const steeringPendingPath = join(taskFolder, ".steering-pending");
|
|
249
|
+
|
|
250
|
+
// TP-106: Bridge extension wiring for agent-side reply/escalate tools
|
|
251
|
+
const outboxDir = join(config.stateRoot, ".pi", "mailbox", config.batchId, workerAgentId, "outbox");
|
|
252
|
+
const bridgeExtensionPath = join(LANE_RUNNER_DIR, "agent-bridge-extension.ts");
|
|
253
|
+
|
|
254
|
+
const hostOpts: AgentHostOptions = {
|
|
255
|
+
agentId: workerAgentId,
|
|
256
|
+
role: "worker",
|
|
257
|
+
batchId: config.batchId,
|
|
258
|
+
laneNumber: config.laneNumber,
|
|
259
|
+
taskId,
|
|
260
|
+
repoId: config.repoId,
|
|
261
|
+
cwd: config.worktreePath,
|
|
262
|
+
prompt: promptLines.join("\n"),
|
|
263
|
+
systemPrompt: config.workerSystemPrompt || undefined,
|
|
264
|
+
model: config.workerModel || undefined,
|
|
265
|
+
tools: config.workerTools || "read,write,edit,bash,grep,find,ls",
|
|
266
|
+
thinking: config.workerThinking || undefined,
|
|
267
|
+
mailboxDir,
|
|
268
|
+
steeringPendingPath,
|
|
269
|
+
eventsPath,
|
|
270
|
+
exitSummaryPath: eventsPath.replace(/\.jsonl$/, "-exit.json"),
|
|
271
|
+
timeoutMs: config.maxWorkerMinutes * 60_000,
|
|
272
|
+
stateRoot: config.stateRoot,
|
|
273
|
+
packet: unit.packet,
|
|
274
|
+
extensions: [bridgeExtensionPath],
|
|
275
|
+
env: {
|
|
276
|
+
TASKPLANE_OUTBOX_DIR: outboxDir,
|
|
277
|
+
TASKPLANE_AGENT_ID: workerAgentId,
|
|
278
|
+
ORCH_BATCH_ID: config.batchId,
|
|
279
|
+
},
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
// Context pressure: write wrap-up signal before kill
|
|
283
|
+
let workerKillReason: "context" | "timer" | null = null;
|
|
284
|
+
|
|
285
|
+
// DEBUG: verify this code path executes
|
|
286
|
+
try { writeFileSync(config.stateRoot + '/.pi/tp-debug-pre-spawn.json', JSON.stringify({ ts: Date.now(), iter: totalIterations, taskId })); } catch { /* */ }
|
|
287
|
+
const spawned = spawnAgent(hostOpts, undefined, (telemetry) => {
|
|
288
|
+
// Context pressure check
|
|
289
|
+
if (telemetry.contextUsage) {
|
|
290
|
+
const pct = telemetry.contextUsage.percent;
|
|
291
|
+
if (pct >= config.warnPercent) {
|
|
292
|
+
const msg = `Wrap up (context ${Math.round(pct)}%)`;
|
|
293
|
+
if (!existsSync(wrapUpFile)) writeFileSync(wrapUpFile, msg);
|
|
294
|
+
}
|
|
295
|
+
if (pct >= config.killPercent) {
|
|
296
|
+
workerKillReason = "context";
|
|
297
|
+
spawned.kill();
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
lastTelemetry = telemetry;
|
|
302
|
+
// Emit lane snapshot
|
|
303
|
+
emitSnapshot(config, taskId, "running", telemetry, statusPath);
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
const workerResult = await spawned.promise;
|
|
307
|
+
|
|
308
|
+
// TP-115: Update lastTelemetry with definitive final values from AgentHostResult
|
|
309
|
+
lastTelemetry = workerResult;
|
|
310
|
+
// DEBUG
|
|
311
|
+
try { writeFileSync(config.stateRoot + '/.pi/tp-debug-workerResult.json', JSON.stringify({ iter: totalIterations, cost: workerResult.costUsd, tools: workerResult.toolCalls, input: workerResult.inputTokens, keys: Object.keys(workerResult) }, null, 2)); } catch { /* */ }
|
|
312
|
+
|
|
313
|
+
// Clean up wrap-up signal
|
|
314
|
+
if (existsSync(wrapUpFile)) try { unlinkSync(wrapUpFile); } catch { /* ignore */ }
|
|
315
|
+
|
|
316
|
+
// Accumulate costs
|
|
317
|
+
cumulativeCostUsd += workerResult.costUsd;
|
|
318
|
+
cumulativeTokens += workerResult.inputTokens + workerResult.outputTokens +
|
|
319
|
+
workerResult.cacheReadTokens + workerResult.cacheWriteTokens;
|
|
320
|
+
|
|
321
|
+
// ── TP-106: Poll worker outbox for replies/escalations ─────
|
|
322
|
+
try {
|
|
323
|
+
const outboxMessages = readOutbox(config.stateRoot, config.batchId, workerAgentId);
|
|
324
|
+
for (const msg of outboxMessages) {
|
|
325
|
+
const sanitized = msg.content.replace(/\r?\n/g, " / ").slice(0, 200);
|
|
326
|
+
logExecution(statusPath, `Agent ${msg.type}`, sanitized);
|
|
327
|
+
|
|
328
|
+
if (msg.type === "reply" || msg.type === "escalate") {
|
|
329
|
+
appendAgentEvent(config.stateRoot, config.batchId, workerAgentId, {
|
|
330
|
+
batchId: config.batchId,
|
|
331
|
+
agentId: workerAgentId,
|
|
332
|
+
role: "worker",
|
|
333
|
+
laneNumber: config.laneNumber,
|
|
334
|
+
taskId,
|
|
335
|
+
repoId: config.repoId,
|
|
336
|
+
ts: Date.now(),
|
|
337
|
+
type: msg.type === "reply" ? "reply_sent" : "escalation_sent",
|
|
338
|
+
payload: {
|
|
339
|
+
messageId: msg.id,
|
|
340
|
+
replyTo: msg.replyTo ?? null,
|
|
341
|
+
content: sanitized,
|
|
342
|
+
},
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
appendMailboxAuditEvent(config.stateRoot, config.batchId, {
|
|
346
|
+
type: msg.type === "reply" ? "message_replied" : "message_escalated",
|
|
347
|
+
from: workerAgentId,
|
|
348
|
+
to: "supervisor",
|
|
349
|
+
messageId: msg.id,
|
|
350
|
+
messageType: msg.type,
|
|
351
|
+
contentPreview: sanitized,
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
if (config.onSupervisorAlert) {
|
|
355
|
+
const isEscalation = msg.type === "escalate";
|
|
356
|
+
try {
|
|
357
|
+
config.onSupervisorAlert({
|
|
358
|
+
category: "agent-message",
|
|
359
|
+
summary:
|
|
360
|
+
`${isEscalation ? "🚨" : "📨"} Agent ${isEscalation ? "escalation" : "reply"} from ${workerAgentId}\n` +
|
|
361
|
+
` Task: ${taskId}\n` +
|
|
362
|
+
` Lane: lane-${config.laneNumber}\n` +
|
|
363
|
+
` Message: ${sanitized}`,
|
|
364
|
+
context: {
|
|
365
|
+
taskId,
|
|
366
|
+
laneId: `lane-${config.laneNumber}`,
|
|
367
|
+
laneNumber: config.laneNumber,
|
|
368
|
+
agentId: workerAgentId,
|
|
369
|
+
messageId: msg.id,
|
|
370
|
+
exitReason: `${isEscalation ? "agent_escalation" : "agent_reply"}: ${sanitized}`,
|
|
371
|
+
},
|
|
372
|
+
});
|
|
373
|
+
} catch { /* best effort */ }
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// Consume outbox message to prevent duplicate processing in later iterations.
|
|
378
|
+
ackOutboxMessage(config.stateRoot, config.batchId, workerAgentId, msg.id);
|
|
379
|
+
}
|
|
380
|
+
} catch { /* best effort */ }
|
|
381
|
+
|
|
382
|
+
// ── Steering annotation ─────────────────────────────────────
|
|
383
|
+
try {
|
|
384
|
+
if (existsSync(steeringPendingPath)) {
|
|
385
|
+
const raw = readFileSync(steeringPendingPath, "utf-8");
|
|
386
|
+
for (const line of raw.split("\n").filter(l => l.trim())) {
|
|
387
|
+
try {
|
|
388
|
+
const entry = JSON.parse(line) as { ts: number; content: string; id: string };
|
|
389
|
+
const sanitized = entry.content.replace(/\r?\n/g, " / ").replace(/\|/g, "\\|").slice(0, 200);
|
|
390
|
+
const ts = new Date(entry.ts).toISOString().slice(0, 16).replace("T", " ");
|
|
391
|
+
logExecution(statusPath, "⚠️ Steering", sanitized);
|
|
392
|
+
} catch { /* skip malformed */ }
|
|
393
|
+
}
|
|
394
|
+
unlinkSync(steeringPendingPath);
|
|
395
|
+
}
|
|
396
|
+
} catch { /* non-fatal */ }
|
|
397
|
+
|
|
398
|
+
// Log iteration result
|
|
399
|
+
const statusMsg = workerResult.killed
|
|
400
|
+
? `killed (${workerKillReason === "context" ? "context limit" : "wall-clock timeout"})`
|
|
401
|
+
: (workerResult.exitCode === 0 ? "done" : `error (code ${workerResult.exitCode})`);
|
|
402
|
+
logExecution(statusPath, `Worker iter ${totalIterations}`,
|
|
403
|
+
`${statusMsg} in ${Math.round(workerResult.durationMs / 1000)}s, tools: ${workerResult.toolCalls}`);
|
|
404
|
+
|
|
405
|
+
// ── Check progress ──────────────────────────────────────────
|
|
406
|
+
const afterStatus = parseStatusMd(readFileSync(statusPath, "utf-8"));
|
|
407
|
+
const afterTotalChecked = afterStatus.steps.reduce((sum, s) => sum + s.totalChecked, 0);
|
|
408
|
+
const progressDelta = afterTotalChecked - prevTotalChecked;
|
|
409
|
+
|
|
410
|
+
if (progressDelta <= 0) {
|
|
411
|
+
noProgressCount++;
|
|
412
|
+
logExecution(statusPath, "No progress",
|
|
413
|
+
`Iteration ${totalIterations}: 0 new checkboxes (${noProgressCount}/${config.noProgressLimit} stall limit)`);
|
|
414
|
+
if (noProgressCount >= config.noProgressLimit) {
|
|
415
|
+
logExecution(statusPath, "Task blocked", `No progress after ${noProgressCount} iterations`);
|
|
416
|
+
return makeResult(taskId, workerAgentId, "failed", startTime,
|
|
417
|
+
`No progress after ${noProgressCount} iterations`, false, totalIterations, cumulativeCostUsd, cumulativeTokens, config, statusPath, lastTelemetry);
|
|
418
|
+
}
|
|
419
|
+
} else {
|
|
420
|
+
noProgressCount = 0;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// Mark completed steps
|
|
424
|
+
for (const step of parsed.steps) {
|
|
425
|
+
const ss = afterStatus.steps.find(s => s.number === step.number);
|
|
426
|
+
if (isStepComplete(ss)) {
|
|
427
|
+
updateStepStatus(statusPath, step.number, "complete");
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// Check if all steps are now complete
|
|
432
|
+
const allComplete = parsed.steps.every(step => {
|
|
433
|
+
const ss = afterStatus.steps.find(s => s.number === step.number);
|
|
434
|
+
return isStepComplete(ss);
|
|
435
|
+
});
|
|
436
|
+
if (allComplete) break;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// ── 3. Post-loop completion check ───────────────────────────────
|
|
440
|
+
const finalStatus = parseStatusMd(readFileSync(statusPath, "utf-8"));
|
|
441
|
+
const parsed = parsePromptMd(readFileSync(promptPath, "utf-8"), promptPath);
|
|
442
|
+
const allStepsComplete = parsed.steps.every(step => {
|
|
443
|
+
const ss = finalStatus.steps.find(s => s.number === step.number);
|
|
444
|
+
return isStepComplete(ss);
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
if (!allStepsComplete) {
|
|
448
|
+
const incomplete = parsed.steps
|
|
449
|
+
.filter(step => {
|
|
450
|
+
const ss = finalStatus.steps.find(s => s.number === step.number);
|
|
451
|
+
return !isStepComplete(ss);
|
|
452
|
+
})
|
|
453
|
+
.map(s => `Step ${s.number}`)
|
|
454
|
+
.join(", ");
|
|
455
|
+
logExecution(statusPath, "Task incomplete", `Max iterations reached. Incomplete: ${incomplete}`);
|
|
456
|
+
return makeResult(taskId, workerAgentId, "failed", startTime,
|
|
457
|
+
`Max iterations (${config.maxIterations}) reached with incomplete steps: ${incomplete}`,
|
|
458
|
+
false, totalIterations, cumulativeCostUsd, cumulativeTokens, config, statusPath, lastTelemetry);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
// Create .DONE if not already present
|
|
462
|
+
if (!existsSync(donePath)) {
|
|
463
|
+
writeFileSync(donePath, `Completed: ${new Date().toISOString()}\nTask: ${taskId}\n`);
|
|
464
|
+
}
|
|
465
|
+
updateStatusField(statusPath, "Status", "✅ Complete");
|
|
466
|
+
logExecution(statusPath, "Task complete", ".DONE created");
|
|
467
|
+
|
|
468
|
+
return makeResult(taskId, workerAgentId, "succeeded", startTime,
|
|
469
|
+
".DONE file created by lane-runner", true, totalIterations, cumulativeCostUsd, cumulativeTokens, config, statusPath, lastTelemetry);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// ── Helpers ──────────────────────────────────────────────────────────
|
|
473
|
+
|
|
474
|
+
export function mapLaneTaskStatusToTerminalSnapshotStatus(
|
|
475
|
+
status: LaneTaskStatus,
|
|
476
|
+
): "idle" | "complete" | "failed" {
|
|
477
|
+
if (status === "succeeded") return "complete";
|
|
478
|
+
if (status === "skipped") return "idle";
|
|
479
|
+
return "failed";
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
export function mapLaneSnapshotStatusToWorkerStatus(
|
|
483
|
+
status: "running" | "idle" | "complete" | "failed",
|
|
484
|
+
): RuntimeAgentStatus {
|
|
485
|
+
if (status === "running") return "running";
|
|
486
|
+
if (status === "complete") return "exited";
|
|
487
|
+
if (status === "idle") return "wrapping_up";
|
|
488
|
+
return "crashed";
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
function makeResult(
|
|
492
|
+
taskId: string,
|
|
493
|
+
sessionName: string,
|
|
494
|
+
status: LaneTaskStatus,
|
|
495
|
+
startTime: number,
|
|
496
|
+
exitReason: string,
|
|
497
|
+
doneFileFound: boolean,
|
|
498
|
+
iterations: number,
|
|
499
|
+
costUsd: number,
|
|
500
|
+
totalTokens: number,
|
|
501
|
+
config?: LaneRunnerConfig,
|
|
502
|
+
statusPath?: string,
|
|
503
|
+
finalTelemetry?: Partial<AgentHostResult>,
|
|
504
|
+
): LaneRunnerTaskResult {
|
|
505
|
+
const result: LaneRunnerTaskResult = {
|
|
506
|
+
outcome: {
|
|
507
|
+
taskId,
|
|
508
|
+
status,
|
|
509
|
+
startTime,
|
|
510
|
+
endTime: Date.now(),
|
|
511
|
+
exitReason,
|
|
512
|
+
sessionName,
|
|
513
|
+
doneFileFound,
|
|
514
|
+
},
|
|
515
|
+
iterations,
|
|
516
|
+
costUsd,
|
|
517
|
+
totalTokens,
|
|
518
|
+
};
|
|
519
|
+
|
|
520
|
+
// TP-115: Emit terminal snapshot with real telemetry from agent-host result
|
|
521
|
+
if (config && statusPath) {
|
|
522
|
+
const terminalStatus = mapLaneTaskStatusToTerminalSnapshotStatus(status);
|
|
523
|
+
emitSnapshot(config, taskId, terminalStatus, finalTelemetry ?? {}, statusPath);
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
return result;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
function emitSnapshot(
|
|
530
|
+
config: LaneRunnerConfig,
|
|
531
|
+
taskId: string,
|
|
532
|
+
status: "running" | "idle" | "complete" | "failed",
|
|
533
|
+
telemetry: Partial<AgentHostResult>,
|
|
534
|
+
statusPath: string,
|
|
535
|
+
): void {
|
|
536
|
+
// Parse progress from STATUS.md
|
|
537
|
+
let progress: RuntimeTaskProgress | null = null;
|
|
538
|
+
try {
|
|
539
|
+
const content = readFileSync(statusPath, "utf-8");
|
|
540
|
+
const parsed = parseStatusMd(content);
|
|
541
|
+
const currentStepMatch = content.match(/\*\*Current Step:\*\*\s*(.+)/);
|
|
542
|
+
const checked = parsed.steps.reduce((sum, s) => sum + s.totalChecked, 0);
|
|
543
|
+
const total = parsed.steps.reduce((sum, s) => sum + s.totalItems, 0);
|
|
544
|
+
progress = {
|
|
545
|
+
currentStep: currentStepMatch?.[1]?.trim() || "Unknown",
|
|
546
|
+
checked,
|
|
547
|
+
total,
|
|
548
|
+
iteration: parsed.iteration,
|
|
549
|
+
reviews: parsed.reviewCounter,
|
|
550
|
+
};
|
|
551
|
+
} catch { /* best effort */ }
|
|
552
|
+
|
|
553
|
+
const snapshot: RuntimeLaneSnapshot = {
|
|
554
|
+
batchId: config.batchId,
|
|
555
|
+
laneNumber: config.laneNumber,
|
|
556
|
+
laneId: `lane-${config.laneNumber}`,
|
|
557
|
+
repoId: config.repoId,
|
|
558
|
+
taskId,
|
|
559
|
+
segmentId: null,
|
|
560
|
+
status,
|
|
561
|
+
worker: {
|
|
562
|
+
agentId: buildRuntimeAgentId(config.agentIdPrefix, config.laneNumber, "worker"),
|
|
563
|
+
status: mapLaneSnapshotStatusToWorkerStatus(status),
|
|
564
|
+
elapsedMs: telemetry.durationMs ?? 0,
|
|
565
|
+
toolCalls: telemetry.toolCalls ?? 0,
|
|
566
|
+
contextPct: telemetry.contextUsage?.percent ?? 0,
|
|
567
|
+
costUsd: telemetry.costUsd ?? 0,
|
|
568
|
+
lastTool: telemetry.lastTool ?? "",
|
|
569
|
+
inputTokens: telemetry.inputTokens ?? 0,
|
|
570
|
+
outputTokens: telemetry.outputTokens ?? 0,
|
|
571
|
+
cacheReadTokens: telemetry.cacheReadTokens ?? 0,
|
|
572
|
+
cacheWriteTokens: telemetry.cacheWriteTokens ?? 0,
|
|
573
|
+
},
|
|
574
|
+
reviewer: null,
|
|
575
|
+
progress,
|
|
576
|
+
updatedAt: Date.now(),
|
|
577
|
+
};
|
|
578
|
+
|
|
579
|
+
writeLaneSnapshot(config.stateRoot, config.batchId, config.laneNumber, snapshot as any);
|
|
580
|
+
}
|
|
581
|
+
|