@bermudi/pi-delegate 0.1.0 → 0.1.2
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/README.md +55 -10
- package/agents.ts +176 -19
- package/concurrency.ts +70 -7
- package/constants.ts +3 -0
- package/delegate.ts +26 -1
- package/dispatch.ts +65 -9
- package/extension.ts +86 -6
- package/file-tracking.ts +27 -5
- package/format.ts +46 -7
- package/host-compat.ts +47 -12
- package/host.ts +93 -16
- package/leaf.ts +48 -0
- package/lifecycle.ts +292 -95
- package/manual.ts +45 -11
- package/model.ts +3 -4
- package/package.json +25 -20
- package/patches/@marcfargas%2Fpi-test-harness@0.6.1.patch +13 -0
- package/pool.ts +169 -51
- package/render-branches.ts +52 -16
- package/render-result.ts +12 -0
- package/runner.ts +255 -51
- package/schema.ts +252 -63
- package/status.ts +269 -0
- package/task-resolution.ts +196 -77
- package/tickets.ts +173 -62
- package/tools.ts +16 -15
- package/types.ts +62 -13
package/runner.ts
CHANGED
|
@@ -19,6 +19,16 @@ import type {
|
|
|
19
19
|
ToolActivity,
|
|
20
20
|
} from "./types.ts";
|
|
21
21
|
|
|
22
|
+
/** Human-facing error for an expired `deadlineMs` budget.
|
|
23
|
+
*
|
|
24
|
+
* This is exported so the lifecycle pre-check can return the same text as the
|
|
25
|
+
* runner, keeping the sync and (future) async paths consistent. */
|
|
26
|
+
export function formatDeadlineExceededError(budgetMs: number): string {
|
|
27
|
+
return `Deadline exceeded: task exceeded its ${fmtDuration(
|
|
28
|
+
Math.max(0, budgetMs),
|
|
29
|
+
)} wall-clock budget and was cooperatively aborted (not a hard kill). Completed writes and commands remain.`;
|
|
30
|
+
}
|
|
31
|
+
|
|
22
32
|
/**
|
|
23
33
|
* Run a single prompt against a live `AgentSession` and report progress.
|
|
24
34
|
*
|
|
@@ -34,6 +44,15 @@ import type {
|
|
|
34
44
|
* 4. snapshots usage before/after the prompt for token delta accounting, and
|
|
35
45
|
* 5. computes touched files from activity + git diff.
|
|
36
46
|
*
|
|
47
|
+
* Touched-file tracking is best-effort. Explicit edit/write tool calls are
|
|
48
|
+
* captured from the activity log. bash mutations are only captured via git
|
|
49
|
+
* status when the cwd is a git repo and git is available; in a non-git
|
|
50
|
+
* directory, bash-mutated files are not reported. Git failures on either the
|
|
51
|
+
* baseline or post-run snapshot degrade to an empty diff, and a failed baseline
|
|
52
|
+
* suppresses git-based attribution entirely so pre-existing dirty files are not
|
|
53
|
+
* blamed on this task. The resulting list is a lower bound, not a complete
|
|
54
|
+
* record.
|
|
55
|
+
*
|
|
37
56
|
* Output is captured from AgentSession events so compaction cannot erase it
|
|
38
57
|
* before collection; `session.messages` is only a guarded fallback for
|
|
39
58
|
* providers that fail before emitting message_end. AgentSession's internal
|
|
@@ -46,16 +65,24 @@ export async function runAgentSession(
|
|
|
46
65
|
config: { cwd: string },
|
|
47
66
|
signal: AbortSignal | undefined,
|
|
48
67
|
onProgress: ((update: AgentProgressUpdate) => void) | undefined,
|
|
49
|
-
gitBaseline: Set<string
|
|
68
|
+
gitBaseline: Set<string> | undefined,
|
|
50
69
|
start: number,
|
|
70
|
+
deadlineAt?: number,
|
|
51
71
|
): Promise<{
|
|
52
72
|
output: string;
|
|
53
73
|
error?: string;
|
|
54
74
|
durationMs: number;
|
|
55
75
|
tokens: number;
|
|
56
76
|
usage: Usage;
|
|
77
|
+
/** Best-effort union of activity- and git-derived touched files for display. */
|
|
57
78
|
touchedFiles: string[];
|
|
79
|
+
/** Files directly attributable to this run's edit/write tool calls. */
|
|
80
|
+
attributedFiles: string[];
|
|
58
81
|
failureKind?: TaskFailureKind;
|
|
82
|
+
/** Whether `session.prompt()` was actually invoked. Distinguishes a pre-prompt
|
|
83
|
+
* deadline (session never used) from a mid-prompt deadline (session was
|
|
84
|
+
* prompted and may have partial state mutations). */
|
|
85
|
+
prompted: boolean;
|
|
59
86
|
}> {
|
|
60
87
|
const startTime = start ?? Date.now();
|
|
61
88
|
const stallTimeoutMs = getStallTimeoutMs();
|
|
@@ -65,6 +92,9 @@ export async function runAgentSession(
|
|
|
65
92
|
let stalled = false;
|
|
66
93
|
let stalledPhase: string | undefined;
|
|
67
94
|
let clearStallDeadline: (() => void) | undefined;
|
|
95
|
+
let deadlineExceeded = false;
|
|
96
|
+
let clearDeadline: (() => void) | undefined;
|
|
97
|
+
let prompted = false;
|
|
68
98
|
const activities: ToolActivity[] = [];
|
|
69
99
|
const pendingById = new Map<string, ToolActivity>();
|
|
70
100
|
let sessionEventGeneration = 0;
|
|
@@ -110,7 +140,7 @@ export async function runAgentSession(
|
|
|
110
140
|
new Promise<void>((resolve) => setImmediate(resolve));
|
|
111
141
|
|
|
112
142
|
const requestSessionCancellation = (
|
|
113
|
-
source: "parent-aborted" | "stalled",
|
|
143
|
+
source: "parent-aborted" | "stalled" | "deadline",
|
|
114
144
|
): void => {
|
|
115
145
|
const logFailure = (operation: string, error: unknown) => {
|
|
116
146
|
console.error(`[delegate] ${source} subagent ${operation} failed`, error);
|
|
@@ -171,9 +201,14 @@ export async function runAgentSession(
|
|
|
171
201
|
*/
|
|
172
202
|
const waitForSessionQuiescence = async (): Promise<void> => {
|
|
173
203
|
const cancelledGraceMs = 50;
|
|
174
|
-
const cancellationRequested = () =>
|
|
175
|
-
|
|
176
|
-
|
|
204
|
+
const cancellationRequested = () =>
|
|
205
|
+
signal?.aborted || stalled || deadlineExceeded;
|
|
206
|
+
const cancellationSource = (): "parent-aborted" | "stalled" | "deadline" =>
|
|
207
|
+
signal?.aborted
|
|
208
|
+
? "parent-aborted"
|
|
209
|
+
: deadlineExceeded
|
|
210
|
+
? "deadline"
|
|
211
|
+
: "stalled";
|
|
177
212
|
let quietTurns = 0;
|
|
178
213
|
let graceWaited = false;
|
|
179
214
|
while (quietTurns < 2) {
|
|
@@ -235,14 +270,24 @@ export async function runAgentSession(
|
|
|
235
270
|
const fireProgress = () => {
|
|
236
271
|
if (!onProgress) return;
|
|
237
272
|
const delta = currentUsage().totalTokens;
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
273
|
+
try {
|
|
274
|
+
onProgress({
|
|
275
|
+
tokens: delta,
|
|
276
|
+
toolUses,
|
|
277
|
+
durationMs: Date.now() - startTime,
|
|
278
|
+
lastActivityAt,
|
|
279
|
+
activities: [...activities],
|
|
280
|
+
failureKind: signal?.aborted
|
|
281
|
+
? undefined
|
|
282
|
+
: deadlineExceeded
|
|
283
|
+
? "deadline_exceeded"
|
|
284
|
+
: stalled
|
|
285
|
+
? "stalled"
|
|
286
|
+
: undefined,
|
|
287
|
+
});
|
|
288
|
+
} catch (error) {
|
|
289
|
+
console.error("[delegate] progress callback threw; continuing", error);
|
|
290
|
+
}
|
|
246
291
|
};
|
|
247
292
|
|
|
248
293
|
const stallError = () =>
|
|
@@ -252,10 +297,12 @@ export async function runAgentSession(
|
|
|
252
297
|
clearStallDeadline = undefined;
|
|
253
298
|
};
|
|
254
299
|
const abortForStall = () => {
|
|
255
|
-
if (stalled || signal?.aborted) return;
|
|
300
|
+
if (stalled || signal?.aborted || deadlineExceeded) return;
|
|
256
301
|
stalled = true;
|
|
257
302
|
stalledPhase = phase;
|
|
258
303
|
clearStallWatchdog();
|
|
304
|
+
clearDeadline?.();
|
|
305
|
+
clearDeadline = undefined;
|
|
259
306
|
console.warn(
|
|
260
307
|
`[delegate] stalled subagent detected after ${fmtDuration(stallTimeoutMs)} while ${phase}; requesting cooperative cancellation`,
|
|
261
308
|
);
|
|
@@ -269,12 +316,41 @@ export async function runAgentSession(
|
|
|
269
316
|
};
|
|
270
317
|
const armStallWatchdog = (graceMs = 0) => {
|
|
271
318
|
clearStallWatchdog();
|
|
272
|
-
if (!stallTimeoutMs || stalled || signal?.aborted)
|
|
319
|
+
if (!stallTimeoutMs || stalled || signal?.aborted || deadlineExceeded)
|
|
320
|
+
return;
|
|
273
321
|
|
|
274
322
|
const grace = Number.isFinite(graceMs) && graceMs > 0 ? graceMs : 0;
|
|
275
323
|
const deadline = Date.now() + stallTimeoutMs + grace;
|
|
276
324
|
clearStallDeadline = scheduleDeadline(deadline, abortForStall);
|
|
277
325
|
};
|
|
326
|
+
|
|
327
|
+
const deadlineError = () =>
|
|
328
|
+
formatDeadlineExceededError(deadlineAt ? deadlineAt - startTime : 0);
|
|
329
|
+
const clearDeadlineWatchdog = () => {
|
|
330
|
+
clearDeadline?.();
|
|
331
|
+
clearDeadline = undefined;
|
|
332
|
+
};
|
|
333
|
+
const abortForDeadline = () => {
|
|
334
|
+
if (deadlineExceeded || signal?.aborted) return;
|
|
335
|
+
deadlineExceeded = true;
|
|
336
|
+
clearDeadlineWatchdog();
|
|
337
|
+
clearStallWatchdog();
|
|
338
|
+
console.warn(
|
|
339
|
+
`[delegate] subagent exceeded its wall-clock deadline; requesting cooperative cancellation`,
|
|
340
|
+
);
|
|
341
|
+
requestSessionCancellation("deadline");
|
|
342
|
+
fireProgress();
|
|
343
|
+
};
|
|
344
|
+
const armDeadlineWatchdog = () => {
|
|
345
|
+
clearDeadlineWatchdog();
|
|
346
|
+
if (!deadlineAt || deadlineExceeded || stalled || signal?.aborted) return;
|
|
347
|
+
if (Date.now() >= deadlineAt) {
|
|
348
|
+
abortForDeadline();
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
clearDeadline = scheduleDeadline(deadlineAt, abortForDeadline);
|
|
352
|
+
};
|
|
353
|
+
|
|
278
354
|
const noteActivity = (nextPhase: string, graceMs = 0) => {
|
|
279
355
|
lastActivityAt = Date.now();
|
|
280
356
|
phase = nextPhase;
|
|
@@ -296,7 +372,8 @@ export async function runAgentSession(
|
|
|
296
372
|
const initialMessages = session.messages;
|
|
297
373
|
const initialMessageCount = initialMessages.length;
|
|
298
374
|
const initialMessageSnapshot = initialMessages.slice();
|
|
299
|
-
let
|
|
375
|
+
let compactionInProgress = false;
|
|
376
|
+
let completedCompaction = false;
|
|
300
377
|
const assistantMessagesForAttempt: AgentMessage[] = [];
|
|
301
378
|
let partialAssistantMessage: AgentMessage | undefined;
|
|
302
379
|
type AttemptCapture = {
|
|
@@ -426,11 +503,13 @@ export async function runAgentSession(
|
|
|
426
503
|
// Keep an append-only fallback for providers/fakes that reject before
|
|
427
504
|
// emitting message_end. It is deliberately used only when event capture
|
|
428
505
|
// is empty; event capture is authoritative across compaction and retries.
|
|
429
|
-
//
|
|
430
|
-
//
|
|
506
|
+
// An aborted compaction may leave the original append-only transcript
|
|
507
|
+
// untouched. Completed or indeterminate compaction remains fail-closed even
|
|
508
|
+
// if a host happened to preserve the array identity.
|
|
431
509
|
const currentMessages = session.messages;
|
|
432
510
|
if (
|
|
433
|
-
|
|
511
|
+
compactionInProgress ||
|
|
512
|
+
completedCompaction ||
|
|
434
513
|
currentMessages !== initialMessages ||
|
|
435
514
|
currentMessages.length < initialMessageCount
|
|
436
515
|
) {
|
|
@@ -525,17 +604,23 @@ export async function runAgentSession(
|
|
|
525
604
|
// delay before ordinary inactivity detection resumes.
|
|
526
605
|
noteActivity("waiting to retry", event.delayMs);
|
|
527
606
|
break;
|
|
528
|
-
case "auto_retry_end":
|
|
607
|
+
case "auto_retry_end": {
|
|
608
|
+
const autoRetry = event as {
|
|
609
|
+
success?: unknown;
|
|
610
|
+
};
|
|
611
|
+
if (autoRetry.success === false && pendingCompactionAttempt) {
|
|
612
|
+
pendingCompactionAttempt.omitFinalAssistant = false;
|
|
613
|
+
}
|
|
529
614
|
noteActivity("waiting for model output");
|
|
530
615
|
break;
|
|
616
|
+
}
|
|
531
617
|
case "compaction_start":
|
|
532
|
-
|
|
533
|
-
// its array, historical messages are no longer a safe fallback source.
|
|
534
|
-
transcriptMayHaveBeenReplaced = true;
|
|
618
|
+
compactionInProgress = true;
|
|
535
619
|
noteActivity("compacting context");
|
|
536
620
|
break;
|
|
537
621
|
case "compaction_end":
|
|
538
|
-
|
|
622
|
+
compactionInProgress = false;
|
|
623
|
+
if (!event.aborted) completedCompaction = true;
|
|
539
624
|
// Context-overflow agent_end is intentionally emitted with
|
|
540
625
|
// willRetry=false because Pi's retry decision belongs to compaction.
|
|
541
626
|
// Only an overflow compaction that actually retries may retract that
|
|
@@ -584,6 +669,7 @@ export async function runAgentSession(
|
|
|
584
669
|
if (signal) {
|
|
585
670
|
abortHandler = () => {
|
|
586
671
|
clearStallWatchdog();
|
|
672
|
+
clearDeadlineWatchdog();
|
|
587
673
|
// Fire-and-forget: prompt()/the quiescence barrier observe cancellation
|
|
588
674
|
// and then return the partial evidence that this runner reports.
|
|
589
675
|
requestSessionCancellation("parent-aborted");
|
|
@@ -612,34 +698,107 @@ export async function runAgentSession(
|
|
|
612
698
|
tokens: 0,
|
|
613
699
|
usage: emptyUsage(),
|
|
614
700
|
touchedFiles: [],
|
|
701
|
+
attributedFiles: [],
|
|
702
|
+
prompted: false,
|
|
615
703
|
};
|
|
616
704
|
}
|
|
617
705
|
|
|
618
|
-
//
|
|
619
|
-
//
|
|
620
|
-
|
|
621
|
-
|
|
706
|
+
// If the deadline is already in the past, request cooperative cancellation
|
|
707
|
+
// and wait for the session to settle before returning ownership. Starting a
|
|
708
|
+
// prompt after the deadline would let the session do work without an active
|
|
709
|
+
// deadline watchdog.
|
|
710
|
+
if (deadlineAt && Date.now() >= deadlineAt) {
|
|
711
|
+
abortForDeadline();
|
|
712
|
+
// A pre-prompt deadline still calls session.abort(), so any in-flight
|
|
713
|
+
// compaction or extension work must settle before lifecycle disposes or
|
|
714
|
+
// reuses the session. The same quiescence barrier used after prompt()
|
|
715
|
+
// handles a never-prompted session safely — it just observes idle state.
|
|
716
|
+
//
|
|
717
|
+
// Keep the parent abort listener armed during the quiescence wait. If the
|
|
718
|
+
// parent signal aborts while we are waiting, the runner should report the
|
|
719
|
+
// parent cancellation ("Aborted") rather than the pre-expired deadline.
|
|
720
|
+
await waitForSessionQuiescence();
|
|
721
|
+
clearStallWatchdog();
|
|
722
|
+
clearDeadlineWatchdog();
|
|
723
|
+
if (signal && abortHandler)
|
|
724
|
+
signal.removeEventListener("abort", abortHandler);
|
|
725
|
+
unsubscribe();
|
|
726
|
+
if (signal?.aborted) {
|
|
727
|
+
return {
|
|
728
|
+
output: "",
|
|
729
|
+
error: "Aborted",
|
|
730
|
+
durationMs: Date.now() - startTime,
|
|
731
|
+
tokens: 0,
|
|
732
|
+
usage: emptyUsage(),
|
|
733
|
+
touchedFiles: [],
|
|
734
|
+
attributedFiles: [],
|
|
735
|
+
prompted: false,
|
|
736
|
+
};
|
|
737
|
+
}
|
|
738
|
+
return {
|
|
739
|
+
output: "(no output)",
|
|
740
|
+
error: deadlineError(),
|
|
741
|
+
durationMs: Date.now() - startTime,
|
|
742
|
+
tokens: 0,
|
|
743
|
+
usage: emptyUsage(),
|
|
744
|
+
touchedFiles: [],
|
|
745
|
+
attributedFiles: [],
|
|
746
|
+
failureKind: "deadline_exceeded",
|
|
747
|
+
prompted: false,
|
|
748
|
+
};
|
|
749
|
+
}
|
|
622
750
|
|
|
623
751
|
try {
|
|
752
|
+
// Start detection only once the session is ready to receive its prompt;
|
|
753
|
+
// queued delegate tasks never enter this runner and therefore never time out.
|
|
754
|
+
armStallWatchdog();
|
|
755
|
+
armDeadlineWatchdog();
|
|
756
|
+
fireProgress();
|
|
757
|
+
|
|
758
|
+
prompted = true;
|
|
624
759
|
await session.prompt(prompt);
|
|
625
760
|
await waitForSessionQuiescence();
|
|
761
|
+
|
|
762
|
+
// The model is done; inactivity is no longer the right watchdog. Git
|
|
763
|
+
// evidence collection can take several seconds (up to 5s per git call),
|
|
764
|
+
// so keep the wall-clock deadline armed through it while preventing the
|
|
765
|
+
// stall watchdog from firing on the silent git commands.
|
|
626
766
|
clearStallWatchdog();
|
|
767
|
+
phase = "collecting git evidence";
|
|
768
|
+
lastActivityAt = Date.now();
|
|
627
769
|
|
|
770
|
+
const gitAfter = await getGitChangedFiles(config.cwd);
|
|
771
|
+
if (deadlineAt && Date.now() >= deadlineAt && !deadlineExceeded) {
|
|
772
|
+
abortForDeadline();
|
|
773
|
+
}
|
|
774
|
+
clearDeadlineWatchdog();
|
|
775
|
+
// If the deadline or parent abort fired after the first quiescence barrier,
|
|
776
|
+
// including while Git was running, the fire-and-forget
|
|
777
|
+
// cancellation is still unwinding. Wait for the same quiescence barrier so
|
|
778
|
+
// lifecycle does not dispose/reuse the session while compaction or an
|
|
779
|
+
// extension callback is still active.
|
|
780
|
+
if (deadlineExceeded || signal?.aborted) await waitForSessionQuiescence();
|
|
781
|
+
|
|
782
|
+
// Recompute evidence after the final quiescence wait. Output, usage,
|
|
783
|
+
// activity-derived touched files, and the session error state can all be
|
|
784
|
+
// mutated by the unwinding work that the barrier just waited for.
|
|
628
785
|
const state = session.state as { errorMessage?: string };
|
|
629
786
|
const output = capturedOutput();
|
|
630
787
|
const usage = currentUsage();
|
|
631
|
-
|
|
632
|
-
// Compute touched files: union of activity-based (edit/write) and git diff
|
|
633
|
-
// against the pre-prompt baseline. Independent of the runner's event model.
|
|
634
788
|
const fromActivities = extractTouchedFromActivities(activities, config.cwd);
|
|
635
|
-
const
|
|
636
|
-
|
|
789
|
+
const fromGit =
|
|
790
|
+
gitBaseline && gitAfter
|
|
791
|
+
? [...gitAfter].filter((f) => !gitBaseline.has(f))
|
|
792
|
+
: [];
|
|
637
793
|
const touchedFiles = [...new Set([...fromActivities, ...fromGit])];
|
|
638
|
-
const
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
794
|
+
const attributedFiles = fromActivities;
|
|
795
|
+
const errorMessage = signal?.aborted
|
|
796
|
+
? "Aborted"
|
|
797
|
+
: deadlineExceeded
|
|
798
|
+
? deadlineError()
|
|
799
|
+
: stalled
|
|
800
|
+
? stallError()
|
|
801
|
+
: state.errorMessage;
|
|
643
802
|
|
|
644
803
|
return {
|
|
645
804
|
output: output || "(no output)",
|
|
@@ -648,26 +807,62 @@ export async function runAgentSession(
|
|
|
648
807
|
tokens: usage.totalTokens,
|
|
649
808
|
usage,
|
|
650
809
|
touchedFiles,
|
|
651
|
-
|
|
810
|
+
attributedFiles,
|
|
811
|
+
failureKind: signal?.aborted
|
|
812
|
+
? undefined
|
|
813
|
+
: deadlineExceeded
|
|
814
|
+
? "deadline_exceeded"
|
|
815
|
+
: stalled
|
|
816
|
+
? "stalled"
|
|
817
|
+
: undefined,
|
|
818
|
+
prompted,
|
|
652
819
|
};
|
|
653
820
|
} catch (err) {
|
|
654
821
|
// Preserve partial-work evidence: whatever assistant output, token spend,
|
|
655
|
-
// and touched files accumulated before the failure/abort.
|
|
822
|
+
// and touched files accumulated before the failure/abort. The touched-file
|
|
823
|
+
// union is still best-effort; any edit/write activity and git-visible
|
|
824
|
+
// changes observed so far are retained. Keep the stall watchdog armed
|
|
825
|
+
// through the quiescence barrier so a busy session that stops emitting
|
|
826
|
+
// events is still rescued; clear it only once the barrier completes.
|
|
827
|
+
await waitForSessionQuiescence();
|
|
828
|
+
clearStallWatchdog();
|
|
829
|
+
phase = "collecting git evidence";
|
|
830
|
+
lastActivityAt = Date.now();
|
|
831
|
+
|
|
832
|
+
const gitAfter = await getGitChangedFiles(config.cwd);
|
|
833
|
+
if (deadlineAt && Date.now() >= deadlineAt && !deadlineExceeded) {
|
|
834
|
+
abortForDeadline();
|
|
835
|
+
}
|
|
836
|
+
clearDeadlineWatchdog();
|
|
837
|
+
// If the deadline or parent abort fired after the first quiescence barrier,
|
|
838
|
+
// including while Git was running, the fire-and-forget
|
|
839
|
+
// cancellation is still unwinding. Wait for the same quiescence barrier so
|
|
840
|
+
// lifecycle does not dispose/reuse the session while compaction or an
|
|
841
|
+
// extension callback is still active.
|
|
842
|
+
if (deadlineExceeded || signal?.aborted) await waitForSessionQuiescence();
|
|
843
|
+
|
|
844
|
+
// Recompute evidence after the final quiescence wait. Output, usage, and
|
|
845
|
+
// activity-derived touched files can all be mutated by the unwinding work
|
|
846
|
+
// that the barrier just waited for.
|
|
656
847
|
const partialOutput = capturedOutput();
|
|
657
848
|
const usage = currentUsage();
|
|
658
|
-
|
|
659
849
|
const fromActivities = extractTouchedFromActivities(activities, config.cwd);
|
|
660
|
-
const
|
|
661
|
-
|
|
850
|
+
const fromGit =
|
|
851
|
+
gitBaseline && gitAfter
|
|
852
|
+
? [...gitAfter].filter((f) => !gitBaseline.has(f))
|
|
853
|
+
: [];
|
|
662
854
|
const touchedFiles = [...new Set([...fromActivities, ...fromGit])];
|
|
855
|
+
const attributedFiles = fromActivities;
|
|
663
856
|
|
|
664
|
-
const msg =
|
|
665
|
-
?
|
|
666
|
-
:
|
|
667
|
-
?
|
|
668
|
-
:
|
|
669
|
-
?
|
|
670
|
-
:
|
|
857
|
+
const msg = signal?.aborted
|
|
858
|
+
? "Aborted"
|
|
859
|
+
: deadlineExceeded
|
|
860
|
+
? deadlineError()
|
|
861
|
+
: stalled
|
|
862
|
+
? stallError()
|
|
863
|
+
: err instanceof Error
|
|
864
|
+
? err.message
|
|
865
|
+
: String(err);
|
|
671
866
|
return {
|
|
672
867
|
output: partialOutput || "(no output)",
|
|
673
868
|
error: msg,
|
|
@@ -675,10 +870,19 @@ export async function runAgentSession(
|
|
|
675
870
|
tokens: usage.totalTokens,
|
|
676
871
|
usage,
|
|
677
872
|
touchedFiles,
|
|
678
|
-
|
|
873
|
+
attributedFiles,
|
|
874
|
+
failureKind: signal?.aborted
|
|
875
|
+
? undefined
|
|
876
|
+
: deadlineExceeded
|
|
877
|
+
? "deadline_exceeded"
|
|
878
|
+
: stalled
|
|
879
|
+
? "stalled"
|
|
880
|
+
: undefined,
|
|
881
|
+
prompted,
|
|
679
882
|
};
|
|
680
883
|
} finally {
|
|
681
884
|
clearStallWatchdog();
|
|
885
|
+
clearDeadlineWatchdog();
|
|
682
886
|
if (signal && abortHandler)
|
|
683
887
|
signal.removeEventListener("abort", abortHandler);
|
|
684
888
|
unsubscribe();
|