@oh-my-pi/pi-coding-agent 13.3.7 → 13.3.8
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/CHANGELOG.md +82 -0
- package/package.json +9 -18
- package/scripts/format-prompts.ts +7 -172
- package/src/config/prompt-templates.ts +2 -54
- package/src/config/settings-schema.ts +24 -0
- package/src/discovery/codex.ts +1 -2
- package/src/discovery/helpers.ts +0 -5
- package/src/lsp/client.ts +8 -0
- package/src/lsp/config.ts +2 -3
- package/src/lsp/index.ts +379 -99
- package/src/lsp/render.ts +21 -31
- package/src/lsp/types.ts +21 -8
- package/src/lsp/utils.ts +193 -1
- package/src/mcp/config-writer.ts +3 -0
- package/src/modes/components/settings-defs.ts +9 -0
- package/src/modes/interactive-mode.ts +8 -1
- package/src/modes/theme/mermaid-cache.ts +4 -4
- package/src/modes/theme/theme.ts +33 -0
- package/src/prompts/system/subagent-user-prompt.md +2 -0
- package/src/prompts/system/system-prompt.md +12 -1
- package/src/prompts/tools/ast-find.md +20 -0
- package/src/prompts/tools/ast-replace.md +21 -0
- package/src/prompts/tools/bash.md +2 -0
- package/src/prompts/tools/hashline.md +26 -8
- package/src/prompts/tools/lsp.md +22 -5
- package/src/sdk.ts +11 -1
- package/src/session/agent-session.ts +261 -82
- package/src/task/executor.ts +8 -5
- package/src/tools/ast-find.ts +316 -0
- package/src/tools/ast-replace.ts +294 -0
- package/src/tools/bash.ts +2 -1
- package/src/tools/browser.ts +2 -8
- package/src/tools/fetch.ts +55 -18
- package/src/tools/index.ts +8 -0
- package/src/tools/path-utils.ts +34 -0
- package/src/tools/python.ts +2 -1
- package/src/tools/renderers.ts +4 -0
- package/src/tools/ssh.ts +2 -1
- package/src/tools/todo-write.ts +34 -0
- package/src/tools/tool-timeouts.ts +29 -0
- package/src/utils/mime.ts +37 -14
- package/src/utils/prompt-format.ts +172 -0
- package/src/web/scrapers/arxiv.ts +12 -12
- package/src/web/scrapers/go-pkg.ts +2 -2
- package/src/web/scrapers/iacr.ts +17 -9
- package/src/web/scrapers/readthedocs.ts +3 -3
- package/src/web/scrapers/twitter.ts +11 -11
- package/src/web/scrapers/wikipedia.ts +4 -5
- package/src/utils/ignore-files.ts +0 -119
package/src/prompts/tools/lsp.md
CHANGED
|
@@ -1,16 +1,33 @@
|
|
|
1
1
|
Interacts with Language Server Protocol servers for code intelligence.
|
|
2
2
|
|
|
3
3
|
<operations>
|
|
4
|
-
- `
|
|
5
|
-
- `
|
|
4
|
+
- `diagnostics`: Get errors/warnings for file, glob, or entire workspace (no file)
|
|
5
|
+
- `definition`: Go to symbol definition → file path + position + 3-line source context
|
|
6
|
+
- `type_definition`: Go to symbol type definition → file path + position + 3-line source context
|
|
7
|
+
- `implementation`: Find concrete implementations → file path + position + 3-line source context
|
|
8
|
+
- `references`: Find references → locations with 3-line source context (first 50), remaining location-only
|
|
6
9
|
- `hover`: Get type info and documentation → type signature + docs
|
|
7
|
-
- `symbols`: List symbols in file, or search workspace (with query, no file)
|
|
8
|
-
- `rename`: Rename symbol across codebase →
|
|
9
|
-
- `
|
|
10
|
+
- `symbols`: List symbols in file, or search workspace (with query, no file)
|
|
11
|
+
- `rename`: Rename symbol across codebase → preview or apply edits
|
|
12
|
+
- `code_actions`: List available quick-fixes/refactors/import actions; apply one when `apply: true` and `query` matches title or index
|
|
13
|
+
- `status`: Show active language servers
|
|
10
14
|
- `reload`: Restart the language server
|
|
11
15
|
</operations>
|
|
12
16
|
|
|
17
|
+
<parameters>
|
|
18
|
+
- `file`: File path; for diagnostics it may be a glob pattern (e.g., `src/**/*.ts`)
|
|
19
|
+
- `line`: 1-indexed line number for position-based actions
|
|
20
|
+
- `symbol`: Substring on the target line used to resolve column automatically
|
|
21
|
+
- `occurrence`: 1-indexed match index when `symbol` appears multiple times on the same line
|
|
22
|
+
- `query`: Symbol search query, code-action kind filter (list mode), or code-action selector (apply mode)
|
|
23
|
+
- `new_name`: Required for rename
|
|
24
|
+
- `apply`: Apply edits for rename/code_actions (default true for rename, list mode for code_actions unless explicitly true)
|
|
25
|
+
- `timeout`: Request timeout in seconds (clamped to 5-60, default 20)
|
|
26
|
+
</parameters>
|
|
27
|
+
|
|
13
28
|
<caution>
|
|
14
29
|
- Requires running LSP server for target language
|
|
15
30
|
- Some operations require file to be saved to disk
|
|
31
|
+
- Diagnostics glob mode samples up to 20 files per request to avoid long-running stalls on broad patterns
|
|
32
|
+
- When `symbol` is provided for position-based actions, missing symbols or out-of-bounds `occurrence` values return an explicit error instead of silently falling back
|
|
16
33
|
</caution>
|
package/src/sdk.ts
CHANGED
|
@@ -1295,7 +1295,17 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1295
1295
|
return key;
|
|
1296
1296
|
},
|
|
1297
1297
|
cursorExecHandlers,
|
|
1298
|
-
transformToolCallArguments:
|
|
1298
|
+
transformToolCallArguments: (args, _toolName) => {
|
|
1299
|
+
let result = args;
|
|
1300
|
+
const maxTimeout = settings.get("tools.maxTimeout");
|
|
1301
|
+
if (maxTimeout > 0 && typeof result.timeout === "number") {
|
|
1302
|
+
result = { ...result, timeout: Math.min(result.timeout, maxTimeout) };
|
|
1303
|
+
}
|
|
1304
|
+
if (obfuscator?.hasSecrets()) {
|
|
1305
|
+
result = obfuscator.deobfuscateObject(result);
|
|
1306
|
+
}
|
|
1307
|
+
return result;
|
|
1308
|
+
},
|
|
1299
1309
|
intentTracing: !!intentField,
|
|
1300
1310
|
});
|
|
1301
1311
|
cursorEventEmitter = event => agent.emitExternalEvent(event);
|
|
@@ -355,6 +355,13 @@ export class AgentSession {
|
|
|
355
355
|
#pendingTtsrInjections: Rule[] = [];
|
|
356
356
|
#ttsrAbortPending = false;
|
|
357
357
|
#ttsrRetryToken = 0;
|
|
358
|
+
#ttsrResumePromise: Promise<void> | undefined = undefined;
|
|
359
|
+
#ttsrResumeResolve: (() => void) | undefined = undefined;
|
|
360
|
+
#postPromptTaskCounter = 0;
|
|
361
|
+
#postPromptTaskIds = new Set<number>();
|
|
362
|
+
#postPromptTasksPromise: Promise<void> | undefined = undefined;
|
|
363
|
+
#postPromptTasksResolve: (() => void) | undefined = undefined;
|
|
364
|
+
#postPromptTasksAbortController = new AbortController();
|
|
358
365
|
|
|
359
366
|
#streamingEditAbortTriggered = false;
|
|
360
367
|
#streamingEditCheckedLineCounts = new Map<string, number>();
|
|
@@ -509,6 +516,7 @@ export class AgentSession {
|
|
|
509
516
|
if (this.#shouldInterruptForTtsrMatch(matchContext)) {
|
|
510
517
|
// Abort the stream immediately — do not gate on extension callbacks
|
|
511
518
|
this.#ttsrAbortPending = true;
|
|
519
|
+
this.#ensureTtsrResumePromise();
|
|
512
520
|
this.agent.abort();
|
|
513
521
|
// Notify extensions (fire-and-forget, does not block abort)
|
|
514
522
|
this.#emitSessionEvent({ type: "ttsr_triggered", rules: matches }).catch(() => {});
|
|
@@ -517,49 +525,58 @@ export class AgentSession {
|
|
|
517
525
|
const generation = this.#promptGeneration;
|
|
518
526
|
const targetMessageTimestamp =
|
|
519
527
|
event.message.role === "assistant" ? event.message.timestamp : undefined;
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
528
|
+
this.#schedulePostPromptTask(
|
|
529
|
+
async () => {
|
|
530
|
+
if (this.#ttsrRetryToken !== retryToken) {
|
|
531
|
+
this.#resolveTtsrResume();
|
|
532
|
+
return;
|
|
533
|
+
}
|
|
524
534
|
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
535
|
+
const targetAssistantIndex = this.#findTtsrAssistantIndex(targetMessageTimestamp);
|
|
536
|
+
if (
|
|
537
|
+
!this.#ttsrAbortPending ||
|
|
538
|
+
this.#promptGeneration !== generation ||
|
|
539
|
+
targetAssistantIndex === -1
|
|
540
|
+
) {
|
|
541
|
+
this.#ttsrAbortPending = false;
|
|
542
|
+
this.#pendingTtsrInjections = [];
|
|
543
|
+
this.#resolveTtsrResume();
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
531
546
|
this.#ttsrAbortPending = false;
|
|
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
|
-
|
|
547
|
+
const ttsrSettings = this.#ttsrManager?.getSettings();
|
|
548
|
+
if (ttsrSettings?.contextMode === "discard") {
|
|
549
|
+
// Remove the partial/aborted assistant turn from agent state
|
|
550
|
+
this.agent.replaceMessages(this.agent.state.messages.slice(0, targetAssistantIndex));
|
|
551
|
+
}
|
|
552
|
+
// Inject TTSR rules as system reminder before retry
|
|
553
|
+
const injection = this.#getTtsrInjectionContent();
|
|
554
|
+
if (injection) {
|
|
555
|
+
const details = { rules: injection.rules.map(rule => rule.name) };
|
|
556
|
+
this.agent.appendMessage({
|
|
557
|
+
role: "custom",
|
|
558
|
+
customType: "ttsr-injection",
|
|
559
|
+
content: injection.content,
|
|
560
|
+
display: false,
|
|
561
|
+
details,
|
|
562
|
+
timestamp: Date.now(),
|
|
563
|
+
});
|
|
564
|
+
this.sessionManager.appendCustomMessageEntry(
|
|
565
|
+
"ttsr-injection",
|
|
566
|
+
injection.content,
|
|
567
|
+
false,
|
|
568
|
+
details,
|
|
569
|
+
);
|
|
570
|
+
this.#markTtsrInjected(details.rules);
|
|
571
|
+
}
|
|
572
|
+
try {
|
|
573
|
+
await this.agent.continue();
|
|
574
|
+
} catch {
|
|
575
|
+
this.#resolveTtsrResume();
|
|
576
|
+
}
|
|
577
|
+
},
|
|
578
|
+
{ delayMs: 50 },
|
|
579
|
+
);
|
|
563
580
|
return;
|
|
564
581
|
}
|
|
565
582
|
}
|
|
@@ -607,6 +624,13 @@ export class AgentSession {
|
|
|
607
624
|
if (event.message.role === "assistant") {
|
|
608
625
|
this.#lastAssistantMessage = event.message;
|
|
609
626
|
const assistantMsg = event.message as AssistantMessage;
|
|
627
|
+
// Resolve TTSR resume gate before checking for new deferred injections.
|
|
628
|
+
// Gate on #ttsrAbortPending, not stopReason: a non-TTSR abort (e.g. streaming
|
|
629
|
+
// edit) also produces stopReason === "aborted" but has no continuation coming.
|
|
630
|
+
// Only skip when #ttsrAbortPending is true (TTSR continuation is imminent).
|
|
631
|
+
if (!this.#ttsrAbortPending) {
|
|
632
|
+
this.#resolveTtsrResume();
|
|
633
|
+
}
|
|
610
634
|
this.#queueDeferredTtsrInjectionIfNeeded(assistantMsg);
|
|
611
635
|
if (this.#handoffAbortController) {
|
|
612
636
|
this.#skipPostTurnMaintenanceAssistantTimestamp = assistantMsg.timestamp;
|
|
@@ -683,8 +707,9 @@ export class AgentSession {
|
|
|
683
707
|
if (didRetry) return; // Retry was initiated, don't proceed to compaction
|
|
684
708
|
}
|
|
685
709
|
|
|
686
|
-
|
|
687
|
-
|
|
710
|
+
const compactionTask = this.#checkCompaction(msg);
|
|
711
|
+
this.#trackPostPromptTask(compactionTask);
|
|
712
|
+
await compactionTask;
|
|
688
713
|
// Check for incomplete todos (unless there was an error or abort)
|
|
689
714
|
if (msg.stopReason !== "error" && msg.stopReason !== "aborted") {
|
|
690
715
|
await this.#checkTodoCompletion();
|
|
@@ -701,6 +726,141 @@ export class AgentSession {
|
|
|
701
726
|
}
|
|
702
727
|
}
|
|
703
728
|
|
|
729
|
+
/** Create the TTSR resume gate promise if one doesn't already exist. */
|
|
730
|
+
#ensureTtsrResumePromise(): void {
|
|
731
|
+
if (this.#ttsrResumePromise) return;
|
|
732
|
+
const { promise, resolve } = Promise.withResolvers<void>();
|
|
733
|
+
this.#ttsrResumePromise = promise;
|
|
734
|
+
this.#ttsrResumeResolve = resolve;
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
/** Resolve and clear the TTSR resume gate. */
|
|
738
|
+
#resolveTtsrResume(): void {
|
|
739
|
+
if (!this.#ttsrResumeResolve) return;
|
|
740
|
+
this.#ttsrResumeResolve();
|
|
741
|
+
this.#ttsrResumeResolve = undefined;
|
|
742
|
+
this.#ttsrResumePromise = undefined;
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
#ensurePostPromptTasksPromise(): void {
|
|
746
|
+
if (this.#postPromptTasksPromise) return;
|
|
747
|
+
const { promise, resolve } = Promise.withResolvers<void>();
|
|
748
|
+
this.#postPromptTasksPromise = promise;
|
|
749
|
+
this.#postPromptTasksResolve = resolve;
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
#resolvePostPromptTasks(): void {
|
|
753
|
+
if (!this.#postPromptTasksResolve) return;
|
|
754
|
+
this.#postPromptTasksResolve();
|
|
755
|
+
this.#postPromptTasksResolve = undefined;
|
|
756
|
+
this.#postPromptTasksPromise = undefined;
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
#trackPostPromptTask(task: Promise<void>): void {
|
|
760
|
+
const taskId = ++this.#postPromptTaskCounter;
|
|
761
|
+
this.#postPromptTaskIds.add(taskId);
|
|
762
|
+
this.#ensurePostPromptTasksPromise();
|
|
763
|
+
void task
|
|
764
|
+
.catch(() => {})
|
|
765
|
+
.finally(() => {
|
|
766
|
+
this.#postPromptTaskIds.delete(taskId);
|
|
767
|
+
if (this.#postPromptTaskIds.size === 0) {
|
|
768
|
+
this.#resolvePostPromptTasks();
|
|
769
|
+
}
|
|
770
|
+
});
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
#schedulePostPromptTask(
|
|
774
|
+
task: (signal: AbortSignal) => Promise<void>,
|
|
775
|
+
options?: { delayMs?: number; generation?: number; onSkip?: () => void },
|
|
776
|
+
): void {
|
|
777
|
+
const delayMs = options?.delayMs ?? 0;
|
|
778
|
+
const signal = this.#postPromptTasksAbortController.signal;
|
|
779
|
+
const scheduled = (async () => {
|
|
780
|
+
if (delayMs > 0) {
|
|
781
|
+
try {
|
|
782
|
+
await abortableSleep(delayMs, signal);
|
|
783
|
+
} catch {
|
|
784
|
+
return;
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
if (signal.aborted) {
|
|
788
|
+
options?.onSkip?.();
|
|
789
|
+
return;
|
|
790
|
+
}
|
|
791
|
+
if (options?.generation !== undefined && this.#promptGeneration !== options.generation) {
|
|
792
|
+
options.onSkip?.();
|
|
793
|
+
return;
|
|
794
|
+
}
|
|
795
|
+
await task(signal);
|
|
796
|
+
})();
|
|
797
|
+
this.#trackPostPromptTask(scheduled);
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
#scheduleAgentContinue(options?: {
|
|
801
|
+
delayMs?: number;
|
|
802
|
+
generation?: number;
|
|
803
|
+
shouldContinue?: () => boolean;
|
|
804
|
+
onSkip?: () => void;
|
|
805
|
+
onError?: () => void;
|
|
806
|
+
}): void {
|
|
807
|
+
this.#schedulePostPromptTask(
|
|
808
|
+
async () => {
|
|
809
|
+
if (options?.shouldContinue && !options.shouldContinue()) {
|
|
810
|
+
options.onSkip?.();
|
|
811
|
+
return;
|
|
812
|
+
}
|
|
813
|
+
try {
|
|
814
|
+
await this.agent.continue();
|
|
815
|
+
} catch {
|
|
816
|
+
options?.onError?.();
|
|
817
|
+
}
|
|
818
|
+
},
|
|
819
|
+
{
|
|
820
|
+
delayMs: options?.delayMs,
|
|
821
|
+
generation: options?.generation,
|
|
822
|
+
onSkip: options?.onSkip,
|
|
823
|
+
},
|
|
824
|
+
);
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
#cancelPostPromptTasks(): void {
|
|
828
|
+
this.#postPromptTasksAbortController.abort();
|
|
829
|
+
this.#postPromptTasksAbortController = new AbortController();
|
|
830
|
+
this.#postPromptTaskIds.clear();
|
|
831
|
+
this.#resolvePostPromptTasks();
|
|
832
|
+
}
|
|
833
|
+
/**
|
|
834
|
+
* Wait for retry, TTSR resume, and any background continuation to settle.
|
|
835
|
+
* Loops because a TTSR continuation can trigger a retry (or vice-versa),
|
|
836
|
+
* and fire-and-forget `agent.continue()` may still be streaming after
|
|
837
|
+
* the TTSR resume gate resolves.
|
|
838
|
+
*/
|
|
839
|
+
async #waitForPostPromptRecovery(): Promise<void> {
|
|
840
|
+
while (true) {
|
|
841
|
+
if (this.#retryPromise) {
|
|
842
|
+
await this.#retryPromise;
|
|
843
|
+
continue;
|
|
844
|
+
}
|
|
845
|
+
if (this.#ttsrResumePromise) {
|
|
846
|
+
await this.#ttsrResumePromise;
|
|
847
|
+
continue;
|
|
848
|
+
}
|
|
849
|
+
if (this.#postPromptTasksPromise) {
|
|
850
|
+
await this.#postPromptTasksPromise;
|
|
851
|
+
continue;
|
|
852
|
+
}
|
|
853
|
+
// Tracked post-prompt tasks cover deferred continuations scheduled from
|
|
854
|
+
// event handlers. Keep the streaming fallback for direct agent activity
|
|
855
|
+
// outside the scheduler.
|
|
856
|
+
if (this.agent.state.isStreaming) {
|
|
857
|
+
await this.agent.waitForIdle();
|
|
858
|
+
continue;
|
|
859
|
+
}
|
|
860
|
+
break;
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
|
|
704
864
|
/** Get TTSR injection payload and clear pending injections. */
|
|
705
865
|
#getTtsrInjectionContent(): { content: string; rules: Rule[] } | undefined {
|
|
706
866
|
if (this.#pendingTtsrInjections.length === 0) return undefined;
|
|
@@ -792,14 +952,26 @@ export class AgentSession {
|
|
|
792
952
|
details: { rules: injection.rules.map(rule => rule.name) },
|
|
793
953
|
timestamp: Date.now(),
|
|
794
954
|
});
|
|
955
|
+
this.#ensureTtsrResumePromise();
|
|
795
956
|
// Mark as injected after this custom message is delivered and persisted (handled in message_end).
|
|
796
957
|
// followUp() only enqueues; resume on the next tick once streaming settles.
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
958
|
+
this.#scheduleAgentContinue({
|
|
959
|
+
delayMs: 1,
|
|
960
|
+
generation: this.#promptGeneration,
|
|
961
|
+
onSkip: () => {
|
|
962
|
+
this.#resolveTtsrResume();
|
|
963
|
+
},
|
|
964
|
+
shouldContinue: () => {
|
|
965
|
+
if (this.agent.state.isStreaming || !this.agent.hasQueuedMessages()) {
|
|
966
|
+
this.#resolveTtsrResume();
|
|
967
|
+
return false;
|
|
968
|
+
}
|
|
969
|
+
return true;
|
|
970
|
+
},
|
|
971
|
+
onError: () => {
|
|
972
|
+
this.#resolveTtsrResume();
|
|
973
|
+
},
|
|
974
|
+
});
|
|
803
975
|
}
|
|
804
976
|
|
|
805
977
|
/** Build TTSR match context for tool call argument deltas. */
|
|
@@ -1271,6 +1443,7 @@ export class AgentSession {
|
|
|
1271
1443
|
} catch (error) {
|
|
1272
1444
|
logger.warn("Failed to emit session_shutdown event", { error: String(error) });
|
|
1273
1445
|
}
|
|
1446
|
+
this.#cancelPostPromptTasks();
|
|
1274
1447
|
const drained = await this.#asyncJobManager?.dispose({ timeoutMs: 3_000 });
|
|
1275
1448
|
const deliveryState = this.#asyncJobManager?.getDeliveryState();
|
|
1276
1449
|
if (drained === false && deliveryState) {
|
|
@@ -1322,6 +1495,16 @@ export class AgentSession {
|
|
|
1322
1495
|
return this.agent.state.isStreaming || this.#promptInFlight;
|
|
1323
1496
|
}
|
|
1324
1497
|
|
|
1498
|
+
/** Wait until streaming and deferred recovery work are fully settled. */
|
|
1499
|
+
async waitForIdle(): Promise<void> {
|
|
1500
|
+
await this.agent.waitForIdle();
|
|
1501
|
+
await this.#waitForPostPromptRecovery();
|
|
1502
|
+
}
|
|
1503
|
+
|
|
1504
|
+
/** Most recent assistant message in agent state. */
|
|
1505
|
+
getLastAssistantMessage(): AssistantMessage | undefined {
|
|
1506
|
+
return this.#findLastAssistantMessage();
|
|
1507
|
+
}
|
|
1325
1508
|
/** Current effective system prompt (includes any per-turn extension modifications) */
|
|
1326
1509
|
get systemPrompt(): string {
|
|
1327
1510
|
return this.agent.state.systemPrompt;
|
|
@@ -1721,7 +1904,7 @@ export class AgentSession {
|
|
|
1721
1904
|
async #promptWithMessage(
|
|
1722
1905
|
message: AgentMessage,
|
|
1723
1906
|
expandedText: string,
|
|
1724
|
-
options?: Pick<PromptOptions, "toolChoice" | "images"
|
|
1907
|
+
options?: Pick<PromptOptions, "toolChoice" | "images"> & { skipPostPromptRecoveryWait?: boolean },
|
|
1725
1908
|
): Promise<void> {
|
|
1726
1909
|
this.#promptInFlight = true;
|
|
1727
1910
|
const generation = this.#promptGeneration;
|
|
@@ -1826,7 +2009,9 @@ export class AgentSession {
|
|
|
1826
2009
|
|
|
1827
2010
|
const agentPromptOptions = options?.toolChoice ? { toolChoice: options.toolChoice } : undefined;
|
|
1828
2011
|
await this.#promptAgentWithIdleRetry(messages, agentPromptOptions);
|
|
1829
|
-
|
|
2012
|
+
if (!options?.skipPostPromptRecoveryWait) {
|
|
2013
|
+
await this.#waitForPostPromptRecovery();
|
|
2014
|
+
}
|
|
1830
2015
|
} finally {
|
|
1831
2016
|
this.#promptInFlight = false;
|
|
1832
2017
|
}
|
|
@@ -1886,7 +2071,7 @@ export class AgentSession {
|
|
|
1886
2071
|
},
|
|
1887
2072
|
hasQueuedMessages: () => this.queuedMessageCount > 0,
|
|
1888
2073
|
getContextUsage: () => this.getContextUsage(),
|
|
1889
|
-
waitForIdle: () => this.
|
|
2074
|
+
waitForIdle: () => this.waitForIdle(),
|
|
1890
2075
|
newSession: async options => {
|
|
1891
2076
|
const success = await this.newSession({ parentSession: options?.parentSession });
|
|
1892
2077
|
if (!success) {
|
|
@@ -2217,6 +2402,8 @@ export class AgentSession {
|
|
|
2217
2402
|
async abort(): Promise<void> {
|
|
2218
2403
|
this.abortRetry();
|
|
2219
2404
|
this.#promptGeneration++;
|
|
2405
|
+
this.#resolveTtsrResume();
|
|
2406
|
+
this.#cancelPostPromptTasks();
|
|
2220
2407
|
this.agent.abort();
|
|
2221
2408
|
await this.agent.waitForIdle();
|
|
2222
2409
|
// Clear promptInFlight: waitForIdle resolves when the agent loop's finally
|
|
@@ -3004,6 +3191,7 @@ Be thorough - include exact file paths, function names, error messages, and tech
|
|
|
3004
3191
|
// Skip if message was aborted (user cancelled) - unless skipAbortedCheck is false
|
|
3005
3192
|
if (skipAbortedCheck && assistantMessage.stopReason === "aborted") return;
|
|
3006
3193
|
const contextWindow = this.model?.contextWindow ?? 0;
|
|
3194
|
+
const generation = this.#promptGeneration;
|
|
3007
3195
|
// Skip overflow check if the message came from a different model.
|
|
3008
3196
|
// This handles the case where user switched from a smaller-context model (e.g. opus)
|
|
3009
3197
|
// to a larger-context model (e.g. codex) - the overflow error from the old model
|
|
@@ -3029,9 +3217,7 @@ Be thorough - include exact file paths, function names, error messages, and tech
|
|
|
3029
3217
|
const promoted = await this.#tryContextPromotion(assistantMessage);
|
|
3030
3218
|
if (promoted) {
|
|
3031
3219
|
// Retry on the promoted (larger) model without compacting
|
|
3032
|
-
|
|
3033
|
-
this.agent.continue().catch(() => {});
|
|
3034
|
-
}, 100);
|
|
3220
|
+
this.#scheduleAgentContinue({ delayMs: 100, generation });
|
|
3035
3221
|
return;
|
|
3036
3222
|
}
|
|
3037
3223
|
|
|
@@ -3132,7 +3318,7 @@ Be thorough - include exact file paths, function names, error messages, and tech
|
|
|
3132
3318
|
content: [{ type: "text", text: reminder }],
|
|
3133
3319
|
timestamp: Date.now(),
|
|
3134
3320
|
});
|
|
3135
|
-
this
|
|
3321
|
+
this.#scheduleAgentContinue({ generation: this.#promptGeneration });
|
|
3136
3322
|
}
|
|
3137
3323
|
|
|
3138
3324
|
/**
|
|
@@ -3302,6 +3488,7 @@ Be thorough - include exact file paths, function names, error messages, and tech
|
|
|
3302
3488
|
async #runAutoCompaction(reason: "overflow" | "threshold", willRetry: boolean): Promise<void> {
|
|
3303
3489
|
const compactionSettings = this.settings.getGroup("compaction");
|
|
3304
3490
|
|
|
3491
|
+
const generation = this.#promptGeneration;
|
|
3305
3492
|
await this.#emitSessionEvent({ type: "auto_compaction_start", reason });
|
|
3306
3493
|
// Properly abort and null existing controller before replacing
|
|
3307
3494
|
if (this.#autoCompactionAbortController) {
|
|
@@ -3541,10 +3728,15 @@ Be thorough - include exact file paths, function names, error messages, and tech
|
|
|
3541
3728
|
await this.#emitSessionEvent({ type: "auto_compaction_end", result, aborted: false, willRetry });
|
|
3542
3729
|
|
|
3543
3730
|
if (!willRetry && compactionSettings.autoContinue !== false) {
|
|
3544
|
-
await this
|
|
3545
|
-
|
|
3546
|
-
|
|
3547
|
-
|
|
3731
|
+
await this.#promptWithMessage(
|
|
3732
|
+
{
|
|
3733
|
+
role: "developer",
|
|
3734
|
+
content: [{ type: "text", text: "Continue if you have next steps." }],
|
|
3735
|
+
timestamp: Date.now(),
|
|
3736
|
+
},
|
|
3737
|
+
"Continue if you have next steps.",
|
|
3738
|
+
{ skipPostPromptRecoveryWait: true },
|
|
3739
|
+
);
|
|
3548
3740
|
}
|
|
3549
3741
|
|
|
3550
3742
|
if (willRetry) {
|
|
@@ -3554,15 +3746,15 @@ Be thorough - include exact file paths, function names, error messages, and tech
|
|
|
3554
3746
|
this.agent.replaceMessages(messages.slice(0, -1));
|
|
3555
3747
|
}
|
|
3556
3748
|
|
|
3557
|
-
|
|
3558
|
-
this.agent.continue().catch(() => {});
|
|
3559
|
-
}, 100);
|
|
3749
|
+
this.#scheduleAgentContinue({ delayMs: 100, generation });
|
|
3560
3750
|
} else if (this.agent.hasQueuedMessages()) {
|
|
3561
3751
|
// Auto-compaction can complete while follow-up/steering/custom messages are waiting.
|
|
3562
3752
|
// Kick the loop so queued messages are actually delivered.
|
|
3563
|
-
|
|
3564
|
-
|
|
3565
|
-
|
|
3753
|
+
this.#scheduleAgentContinue({
|
|
3754
|
+
delayMs: 100,
|
|
3755
|
+
generation,
|
|
3756
|
+
shouldContinue: () => this.agent.hasQueuedMessages(),
|
|
3757
|
+
});
|
|
3566
3758
|
}
|
|
3567
3759
|
} catch (error) {
|
|
3568
3760
|
if (this.#autoCompactionAbortController?.signal.aborted) {
|
|
@@ -3685,6 +3877,7 @@ Be thorough - include exact file paths, function names, error messages, and tech
|
|
|
3685
3877
|
const retrySettings = this.settings.getGroup("retry");
|
|
3686
3878
|
if (!retrySettings.enabled) return false;
|
|
3687
3879
|
|
|
3880
|
+
const generation = this.#promptGeneration;
|
|
3688
3881
|
this.#retryAttempt++;
|
|
3689
3882
|
|
|
3690
3883
|
// Create retry promise on first attempt so waitForRetry() can await it
|
|
@@ -3764,12 +3957,8 @@ Be thorough - include exact file paths, function names, error messages, and tech
|
|
|
3764
3957
|
}
|
|
3765
3958
|
this.#retryAbortController = undefined;
|
|
3766
3959
|
|
|
3767
|
-
// Retry via continue()
|
|
3768
|
-
|
|
3769
|
-
this.agent.continue().catch(() => {
|
|
3770
|
-
// Retry failed - will be caught by next agent_end
|
|
3771
|
-
});
|
|
3772
|
-
}, 0);
|
|
3960
|
+
// Retry via continue() outside the agent_end event callback chain.
|
|
3961
|
+
this.#scheduleAgentContinue({ delayMs: 1, generation });
|
|
3773
3962
|
|
|
3774
3963
|
return true;
|
|
3775
3964
|
}
|
|
@@ -3783,16 +3972,6 @@ Be thorough - include exact file paths, function names, error messages, and tech
|
|
|
3783
3972
|
this.#resolveRetry();
|
|
3784
3973
|
}
|
|
3785
3974
|
|
|
3786
|
-
/**
|
|
3787
|
-
* Wait for any in-progress retry to complete.
|
|
3788
|
-
* Returns immediately if no retry is in progress.
|
|
3789
|
-
*/
|
|
3790
|
-
async #waitForRetry(): Promise<void> {
|
|
3791
|
-
if (this.#retryPromise) {
|
|
3792
|
-
await this.#retryPromise;
|
|
3793
|
-
}
|
|
3794
|
-
}
|
|
3795
|
-
|
|
3796
3975
|
async #promptAgentWithIdleRetry(messages: AgentMessage[], options?: { toolChoice?: ToolChoice }): Promise<void> {
|
|
3797
3976
|
const deadline = Date.now() + 30_000;
|
|
3798
3977
|
for (;;) {
|
package/src/task/executor.ts
CHANGED
|
@@ -1070,6 +1070,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
1070
1070
|
});
|
|
1071
1071
|
|
|
1072
1072
|
await session.prompt(task);
|
|
1073
|
+
await session.waitForIdle();
|
|
1073
1074
|
|
|
1074
1075
|
const reminderToolChoice = buildSubmitResultToolChoice(session.model);
|
|
1075
1076
|
|
|
@@ -1083,6 +1084,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
1083
1084
|
});
|
|
1084
1085
|
|
|
1085
1086
|
await session.prompt(reminder, reminderToolChoice ? { toolChoice: reminderToolChoice } : undefined);
|
|
1087
|
+
await session.waitForIdle();
|
|
1086
1088
|
} catch (err) {
|
|
1087
1089
|
logger.error("Subagent prompt failed", {
|
|
1088
1090
|
error: err instanceof Error ? err.message : String(err),
|
|
@@ -1090,20 +1092,21 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
1090
1092
|
}
|
|
1091
1093
|
}
|
|
1092
1094
|
|
|
1095
|
+
await session.waitForIdle();
|
|
1093
1096
|
if (!submitResultCalled && !abortSignal.aborted) {
|
|
1094
1097
|
aborted = true;
|
|
1095
1098
|
exitCode = 1;
|
|
1096
1099
|
error ??= SUBAGENT_WARNING_MISSING_SUBMIT_RESULT;
|
|
1097
1100
|
}
|
|
1098
1101
|
|
|
1099
|
-
const
|
|
1100
|
-
if (
|
|
1101
|
-
if (
|
|
1102
|
+
const lastAssistant = session.getLastAssistantMessage();
|
|
1103
|
+
if (lastAssistant) {
|
|
1104
|
+
if (lastAssistant.stopReason === "aborted") {
|
|
1102
1105
|
aborted = abortReason === "signal" || abortReason === undefined;
|
|
1103
1106
|
exitCode = 1;
|
|
1104
|
-
} else if (
|
|
1107
|
+
} else if (lastAssistant.stopReason === "error") {
|
|
1105
1108
|
exitCode = 1;
|
|
1106
|
-
error ??=
|
|
1109
|
+
error ??= lastAssistant.errorMessage || "Subagent failed";
|
|
1107
1110
|
}
|
|
1108
1111
|
}
|
|
1109
1112
|
} catch (err) {
|