@oh-my-pi/pi-coding-agent 17.3.4 → 17.3.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.
- package/CHANGELOG.md +88 -0
- package/dist/{CHANGELOG-trcc215s.md → CHANGELOG-1hmwwt45.md} +88 -0
- package/dist/cli.js +3156 -3163
- package/dist/docs-index.generated.txt +1 -1
- package/dist/types/cli/stats-cli.d.ts +1 -6
- package/dist/types/commands/stats.d.ts +4 -0
- package/dist/types/config/model-discovery.d.ts +2 -0
- package/dist/types/config/model-registry.d.ts +6 -1
- package/dist/types/config/settings-schema.d.ts +10 -0
- package/dist/types/debug/report-bundle.d.ts +7 -2
- package/dist/types/extensibility/extensions/loader.d.ts +2 -0
- package/dist/types/extensibility/extensions/runner.d.ts +24 -6
- package/dist/types/extensibility/extensions/types.d.ts +60 -0
- package/dist/types/extensibility/legacy-pi-ai-shim.d.ts +4 -0
- package/dist/types/launch/presence.d.ts +11 -0
- package/dist/types/lsp/client.d.ts +22 -0
- package/dist/types/lsp/workspace-diagnostics.d.ts +2 -0
- package/dist/types/mcp/config.d.ts +3 -1
- package/dist/types/modes/components/tool-execution.d.ts +3 -1
- package/dist/types/registry/agent-lifecycle.d.ts +11 -0
- package/dist/types/registry/agent-registry.d.ts +4 -0
- package/dist/types/session/agent-session-types.d.ts +0 -2
- package/dist/types/session/agent-session.d.ts +20 -0
- package/dist/types/session/date-cwd-reminder.d.ts +22 -0
- package/dist/types/session/irc-bridge.d.ts +2 -0
- package/dist/types/session/messages.d.ts +4 -0
- package/dist/types/session/session-loader.d.ts +18 -11
- package/dist/types/session/session-maintenance.d.ts +6 -2
- package/dist/types/session/session-manager.d.ts +19 -3
- package/dist/types/session/session-tools.d.ts +0 -1
- package/dist/types/session/turn-recovery.d.ts +7 -7
- package/dist/types/slash-commands/helpers/stats-dashboard.d.ts +1 -0
- package/dist/types/tools/browser/attach.d.ts +16 -0
- package/dist/types/tools/browser/launch.d.ts +2 -2
- package/dist/types/tools/browser.d.ts +1 -1
- package/dist/types/tools/builtin-names.d.ts +1 -1
- package/dist/types/tools/file-write-fallback.d.ts +124 -0
- package/dist/types/tools/hub/jobs.d.ts +6 -0
- package/dist/types/tools/hub/types.d.ts +6 -0
- package/dist/types/tools/index.d.ts +1 -0
- package/dist/types/tools/path-utils.d.ts +23 -0
- package/dist/types/vibe/runtime.d.ts +3 -3
- package/package.json +13 -13
- package/src/auto-thinking/classifier.ts +37 -23
- package/src/cli/models-cli.ts +1 -1
- package/src/cli/stats-cli.ts +6 -72
- package/src/commands/stats.ts +7 -4
- package/src/commit/analysis/conventional.ts +15 -9
- package/src/commit/analysis/summary.ts +15 -9
- package/src/commit/changelog/generate.ts +15 -9
- package/src/commit/map-reduce/map-phase.ts +3 -19
- package/src/commit/map-reduce/reduce-phase.ts +15 -9
- package/src/config/model-discovery.ts +3 -3
- package/src/config/model-registry.ts +29 -16
- package/src/config/settings-schema.ts +13 -0
- package/src/debug/report-bundle.ts +25 -59
- package/src/discovery/claude-plugins.ts +2 -1
- package/src/discovery/claude.ts +4 -2
- package/src/discovery/helpers.ts +5 -0
- package/src/edit/hashline/filesystem.ts +9 -3
- package/src/edit/modes/patch.ts +31 -5
- package/src/edit/renderer.ts +48 -19
- package/src/extensibility/extensions/loader.ts +20 -5
- package/src/extensibility/extensions/runner.ts +239 -24
- package/src/extensibility/extensions/types.ts +62 -0
- package/src/extensibility/extensions/wrapper.ts +26 -11
- package/src/extensibility/legacy-pi-ai-shim.ts +4 -0
- package/src/extensibility/plugins/marketplace/manager.ts +15 -18
- package/src/irc/bus.ts +1 -1
- package/src/launch/broker.ts +8 -1
- package/src/launch/presence.ts +77 -1
- package/src/lsp/client.ts +35 -3
- package/src/lsp/clients/biome-client.ts +47 -93
- package/src/lsp/servers.ts +17 -11
- package/src/lsp/tool.ts +2 -4
- package/src/lsp/workspace-diagnostics.ts +24 -2
- package/src/lsp/writethrough.ts +20 -10
- package/src/mcp/config.ts +50 -6
- package/src/memories/index.ts +29 -25
- package/src/mnemopi/backend.ts +13 -11
- package/src/modes/components/ask-dialog.ts +25 -5
- package/src/modes/components/tool-execution.ts +10 -6
- package/src/modes/components/welcome.ts +4 -1
- package/src/modes/controllers/extension-ui-controller.ts +52 -32
- package/src/modes/controllers/selector-controller.ts +6 -0
- package/src/modes/controllers/tan-command-controller.ts +1 -0
- package/src/modes/interactive-mode.ts +49 -10
- package/src/modes/theme/defaults/birch.json +2 -2
- package/src/prompts/system/checkpoint-active-notice.md +5 -0
- package/src/prompts/system/date-cwd-reminder.md +3 -0
- package/src/prompts/system/project-prompt.md +0 -1
- package/src/prompts/system/rewind-report.md +1 -3
- package/src/prompts/tools/browser.md +1 -0
- package/src/prompts/tools/rewind.md +1 -13
- package/src/registry/agent-lifecycle.ts +34 -0
- package/src/registry/agent-registry.ts +27 -2
- package/src/registry/persisted-agents.ts +40 -20
- package/src/sdk.ts +32 -2
- package/src/session/agent-session-types.ts +0 -2
- package/src/session/agent-session.ts +178 -46
- package/src/session/date-cwd-reminder.ts +77 -0
- package/src/session/irc-bridge.ts +5 -0
- package/src/session/messages.ts +5 -1
- package/src/session/session-loader.ts +106 -64
- package/src/session/session-maintenance.ts +189 -115
- package/src/session/session-manager.ts +142 -35
- package/src/session/session-persistence.ts +4 -4
- package/src/session/session-storage.ts +18 -5
- package/src/session/session-tools.ts +5 -10
- package/src/session/turn-recovery.ts +102 -20
- package/src/session/unexpected-stop-classifier.ts +33 -21
- package/src/slash-commands/builtin-session.ts +1 -1
- package/src/slash-commands/helpers/stats-dashboard.ts +23 -9
- package/src/system-prompt.ts +0 -5
- package/src/task/executor.ts +4 -15
- package/src/task/persisted-revive.ts +3 -6
- package/src/tools/ask.ts +10 -3
- package/src/tools/browser/attach.ts +80 -25
- package/src/tools/browser/launch.ts +44 -15
- package/src/tools/browser/relay/daemon.ts +3 -7
- package/src/tools/browser/render.ts +1 -1
- package/src/tools/browser/shared-daemon.ts +3 -7
- package/src/tools/browser.ts +5 -5
- package/src/tools/builtin-names.ts +7 -3
- package/src/tools/checkpoint.ts +1 -7
- package/src/tools/file-write-fallback.ts +467 -0
- package/src/tools/hub/index.ts +1 -1
- package/src/tools/hub/jobs.ts +20 -3
- package/src/tools/hub/types.ts +6 -0
- package/src/tools/index.ts +1 -0
- package/src/tools/path-utils.ts +79 -0
- package/src/tools/render-utils.ts +53 -21
- package/src/tools/think.ts +15 -3
- package/src/tts/speech-enhancer.ts +19 -14
- package/src/utils/commit-message-generator.ts +14 -12
- package/src/utils/title-generator.ts +23 -19
- package/src/vibe/runtime.ts +32 -17
|
@@ -599,79 +599,111 @@ export function truncateDiffByHunk(
|
|
|
599
599
|
let keptHunks = 0;
|
|
600
600
|
|
|
601
601
|
for (const seg of segments) {
|
|
602
|
+
if (kept.length >= maxLines) break;
|
|
602
603
|
if (seg.isChange) {
|
|
604
|
+
if (keptHunks >= maxHunks) break;
|
|
603
605
|
keptHunks++;
|
|
604
|
-
if (keptHunks > maxHunks) break;
|
|
605
606
|
}
|
|
606
|
-
|
|
607
|
-
|
|
607
|
+
const take = Math.min(seg.lines.length, maxLines - kept.length);
|
|
608
|
+
for (let i = 0; i < take; i++) {
|
|
609
|
+
kept.push(seg.lines[i]!);
|
|
610
|
+
}
|
|
608
611
|
}
|
|
609
612
|
|
|
610
|
-
const keptStats = getDiffStats(kept.join("\n"));
|
|
611
613
|
return {
|
|
612
614
|
text: kept.join("\n"),
|
|
613
|
-
hiddenHunks: Math.max(0, totalStats.hunks -
|
|
615
|
+
hiddenHunks: Math.max(0, totalStats.hunks - keptHunks),
|
|
614
616
|
hiddenLines: Math.max(0, lines.length - kept.length),
|
|
615
617
|
};
|
|
616
618
|
}
|
|
617
619
|
|
|
618
620
|
const contextBudget = maxLines - changeLineCount;
|
|
619
|
-
const contextSegments = segments.filter(s => !s.isChange
|
|
621
|
+
const contextSegments = segments.filter(s => !s.isChange);
|
|
620
622
|
const totalContextLines = contextSegments.reduce((sum, s) => sum + s.lines.length, 0);
|
|
621
623
|
|
|
622
624
|
const kept: string[] = [];
|
|
623
625
|
let keptHunks = 0;
|
|
626
|
+
let keptSourceLines = 0;
|
|
624
627
|
|
|
625
628
|
if (totalContextLines <= contextBudget) {
|
|
626
629
|
for (const seg of segments) {
|
|
627
630
|
if (seg.isChange) {
|
|
631
|
+
if (keptHunks >= maxHunks) break;
|
|
628
632
|
keptHunks++;
|
|
629
|
-
if (keptHunks > maxHunks) break;
|
|
630
633
|
}
|
|
631
634
|
kept.push(...seg.lines);
|
|
635
|
+
keptSourceLines += seg.lines.length;
|
|
632
636
|
}
|
|
633
637
|
} else {
|
|
634
|
-
const contextRatio =
|
|
638
|
+
const contextRatio = totalContextLines > 0 ? contextBudget / totalContextLines : 0;
|
|
639
|
+
let remainingContextBudget = contextBudget;
|
|
635
640
|
|
|
636
641
|
for (let i = 0; i < segments.length; i++) {
|
|
637
642
|
const seg = segments[i];
|
|
638
643
|
|
|
639
644
|
if (seg.isChange) {
|
|
645
|
+
if (keptHunks >= maxHunks) break;
|
|
640
646
|
keptHunks++;
|
|
641
|
-
if (keptHunks > maxHunks) break;
|
|
642
|
-
kept.push(...seg.lines);
|
|
643
|
-
} else if (seg.isEllipsis) {
|
|
644
647
|
kept.push(...seg.lines);
|
|
648
|
+
keptSourceLines += seg.lines.length;
|
|
649
|
+
continue;
|
|
650
|
+
}
|
|
651
|
+
if (remainingContextBudget <= 0) continue;
|
|
652
|
+
|
|
653
|
+
const allowedLines = Math.min(
|
|
654
|
+
remainingContextBudget,
|
|
655
|
+
Math.max(1, Math.floor(seg.lines.length * contextRatio)),
|
|
656
|
+
);
|
|
657
|
+
const outputStart = kept.length;
|
|
658
|
+
let sourceLinesAdded = 0;
|
|
659
|
+
|
|
660
|
+
if (seg.isEllipsis || seg.lines.length <= allowedLines) {
|
|
661
|
+
for (let j = 0; j < allowedLines; j++) {
|
|
662
|
+
kept.push(seg.lines[j]!);
|
|
663
|
+
}
|
|
664
|
+
sourceLinesAdded = allowedLines;
|
|
645
665
|
} else {
|
|
646
|
-
const allowedLines = Math.max(1, Math.floor(seg.lines.length * contextRatio));
|
|
647
666
|
const isBeforeChange = segments[i + 1]?.isChange;
|
|
648
667
|
const isAfterChange = segments[i - 1]?.isChange;
|
|
649
668
|
|
|
650
669
|
if (isBeforeChange && isAfterChange) {
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
670
|
+
if (allowedLines >= 3) {
|
|
671
|
+
const sourceBudget = allowedLines - 1;
|
|
672
|
+
const firstCount = Math.ceil(sourceBudget / 2);
|
|
673
|
+
const lastCount = sourceBudget - firstCount;
|
|
674
|
+
kept.push(...seg.lines.slice(0, firstCount));
|
|
654
675
|
kept.push("");
|
|
655
|
-
kept.push(...seg.lines.slice(-
|
|
676
|
+
if (lastCount > 0) kept.push(...seg.lines.slice(-lastCount));
|
|
677
|
+
sourceLinesAdded = sourceBudget;
|
|
656
678
|
} else {
|
|
657
|
-
|
|
679
|
+
const firstCount = Math.ceil(allowedLines / 2);
|
|
680
|
+
const lastCount = allowedLines - firstCount;
|
|
681
|
+
kept.push(...seg.lines.slice(0, firstCount));
|
|
682
|
+
if (lastCount > 0) kept.push(...seg.lines.slice(-lastCount));
|
|
683
|
+
sourceLinesAdded = allowedLines;
|
|
658
684
|
}
|
|
659
685
|
} else if (isBeforeChange) {
|
|
660
686
|
kept.push(...seg.lines.slice(-allowedLines));
|
|
687
|
+
sourceLinesAdded = allowedLines;
|
|
661
688
|
} else if (isAfterChange) {
|
|
662
689
|
kept.push(...seg.lines.slice(0, allowedLines));
|
|
690
|
+
sourceLinesAdded = allowedLines;
|
|
663
691
|
} else {
|
|
664
|
-
|
|
692
|
+
const take = Math.min(allowedLines, 2);
|
|
693
|
+
kept.push(...seg.lines.slice(0, take));
|
|
694
|
+
sourceLinesAdded = take;
|
|
665
695
|
}
|
|
666
696
|
}
|
|
697
|
+
|
|
698
|
+
keptSourceLines += sourceLinesAdded;
|
|
699
|
+
remainingContextBudget -= kept.length - outputStart;
|
|
667
700
|
}
|
|
668
701
|
}
|
|
669
702
|
|
|
670
|
-
const keptStats = getDiffStats(kept.join("\n"));
|
|
671
703
|
return {
|
|
672
704
|
text: kept.join("\n"),
|
|
673
|
-
hiddenHunks: Math.max(0, totalStats.hunks -
|
|
674
|
-
hiddenLines: Math.max(0, lines.length -
|
|
705
|
+
hiddenHunks: Math.max(0, totalStats.hunks - keptHunks),
|
|
706
|
+
hiddenLines: Math.max(0, lines.length - keptSourceLines),
|
|
675
707
|
};
|
|
676
708
|
}
|
|
677
709
|
|
package/src/tools/think.ts
CHANGED
|
@@ -8,14 +8,26 @@ import { getMarkdownTheme, type Theme } from "../modes/theme/theme";
|
|
|
8
8
|
/** Whether a model transport can suppress native reasoning while private scratchpad thoughts are active. */
|
|
9
9
|
export function supportsExternalThinking(model: Model | null | undefined): boolean {
|
|
10
10
|
if (!model) return false;
|
|
11
|
+
const compat = model.compat;
|
|
11
12
|
const requiresThinking =
|
|
12
13
|
model.api === "anthropic-messages" &&
|
|
13
|
-
|
|
14
|
-
"requiresThinkingEnabled" in
|
|
15
|
-
|
|
14
|
+
compat !== undefined &&
|
|
15
|
+
"requiresThinkingEnabled" in compat &&
|
|
16
|
+
compat.requiresThinkingEnabled === true;
|
|
16
17
|
if (model.reasoning && (requiresThinking || (model.thinking?.requiresEffort && !model.thinking.suppressWhenOff))) {
|
|
17
18
|
return false;
|
|
18
19
|
}
|
|
20
|
+
// Transports that reject `reasoning.effort` (xAI Grok 4 and the other
|
|
21
|
+
// reasoning-only Responses models) cannot honour `forceReasoningOff`, so the
|
|
22
|
+
// scratchpad would run alongside native reasoning instead of replacing it.
|
|
23
|
+
if (
|
|
24
|
+
model.reasoning &&
|
|
25
|
+
compat !== undefined &&
|
|
26
|
+
(("omitReasoningEffort" in compat && compat.omitReasoningEffort === true) ||
|
|
27
|
+
("supportsReasoningEffort" in compat && compat.supportsReasoningEffort === false))
|
|
28
|
+
) {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
19
31
|
if (model.api === "google-generative-ai" || model.api === "google-gemini-cli" || model.api === "google-vertex") {
|
|
20
32
|
return !model.reasoning || model.thinking?.mode === "budget" || model.thinking?.suppressWhenOff === true;
|
|
21
33
|
}
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* mechanical {@link SpeakableStream} cleanup — speech never blocks on the
|
|
16
16
|
* model.
|
|
17
17
|
*/
|
|
18
|
-
import { type AssistantMessage, completeSimple } from "@oh-my-pi/pi-ai";
|
|
18
|
+
import { type AssistantMessage, completeSimple, retryTransientCompletion } from "@oh-my-pi/pi-ai";
|
|
19
19
|
import { logger, prompt } from "@oh-my-pi/pi-utils";
|
|
20
20
|
import type { ModelRegistry } from "../config/model-registry";
|
|
21
21
|
import { getModelMatchPreferences, resolveModelRoleValue } from "../config/model-resolver";
|
|
@@ -82,20 +82,25 @@ export class SpeechEnhancer {
|
|
|
82
82
|
if (!apiKey) return null;
|
|
83
83
|
// Resolve metadata after getApiKey so the session-sticky credential is recorded first.
|
|
84
84
|
const metadata = this.#deps.metadataResolver?.(model.provider);
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
85
|
+
const response = await retryTransientCompletion(
|
|
86
|
+
() => {
|
|
87
|
+
const timeout = AbortSignal.timeout(REWRITE_TIMEOUT_MS);
|
|
88
|
+
return completeSimple(
|
|
89
|
+
model,
|
|
90
|
+
{
|
|
91
|
+
systemPrompt: [SYSTEM_PROMPT],
|
|
92
|
+
messages: [{ role: "user", content: boundBlock(block), timestamp: Date.now() }],
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
apiKey: registry.resolver(model, sessionId),
|
|
96
|
+
maxTokens: ANSWER_MAX_TOKENS,
|
|
97
|
+
disableReasoning: true,
|
|
98
|
+
metadata,
|
|
99
|
+
signal: signal ? AbortSignal.any([signal, timeout]) : timeout,
|
|
100
|
+
},
|
|
101
|
+
);
|
|
98
102
|
},
|
|
103
|
+
{ signal },
|
|
99
104
|
);
|
|
100
105
|
if (response.stopReason === "error") {
|
|
101
106
|
logger.debug("speech-enhancer: rewrite errored", { error: response.errorMessage });
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
6
6
|
import type { Api, Model } from "@oh-my-pi/pi-ai";
|
|
7
|
-
import { completeSimple } from "@oh-my-pi/pi-ai";
|
|
7
|
+
import { completeSimple, retryTransientCompletion } from "@oh-my-pi/pi-ai";
|
|
8
8
|
import { logger, prompt } from "@oh-my-pi/pi-utils";
|
|
9
9
|
|
|
10
10
|
import type { ModelRegistry } from "../config/model-registry";
|
|
@@ -106,17 +106,19 @@ export async function generateCommitMessage(
|
|
|
106
106
|
|
|
107
107
|
try {
|
|
108
108
|
const maxTokens = COMMIT_MAX_TOKENS;
|
|
109
|
-
const response = await
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
109
|
+
const response = await retryTransientCompletion(() =>
|
|
110
|
+
completeSimple(
|
|
111
|
+
candidate.model,
|
|
112
|
+
{
|
|
113
|
+
systemPrompt: [COMMIT_SYSTEM_PROMPT],
|
|
114
|
+
messages: [{ role: "user", content: userMessage, timestamp: Date.now() }],
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
apiKey: registry.resolver(candidate.model, sessionId),
|
|
118
|
+
maxTokens,
|
|
119
|
+
reasoning: toReasoningEffort(candidate.thinkingLevel),
|
|
120
|
+
},
|
|
121
|
+
),
|
|
120
122
|
);
|
|
121
123
|
|
|
122
124
|
if (response.stopReason === "error") {
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import { dlopen, FFIType, ptr } from "bun:ffi";
|
|
5
5
|
import * as path from "node:path";
|
|
6
6
|
|
|
7
|
-
import { type Api, type AssistantMessage, completeSimple, type Model } from "@oh-my-pi/pi-ai";
|
|
7
|
+
import { type Api, type AssistantMessage, completeSimple, type Model, retryTransientCompletion } from "@oh-my-pi/pi-ai";
|
|
8
8
|
import { StreamMarkupHealing } from "@oh-my-pi/pi-ai/utils/stream-markup-healing";
|
|
9
9
|
import { isConPTYHosted } from "@oh-my-pi/pi-tui";
|
|
10
10
|
import { isTerminalHeadless, logger, prompt } from "@oh-my-pi/pi-utils";
|
|
@@ -254,24 +254,28 @@ export async function generateTitleOnline(
|
|
|
254
254
|
const maxTokens = TITLE_MAX_TOKENS;
|
|
255
255
|
logger.debug("title-generator: request", { ...modelContext, maxTokens });
|
|
256
256
|
|
|
257
|
-
const response = await
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
257
|
+
const response = await retryTransientCompletion(
|
|
258
|
+
() =>
|
|
259
|
+
completeSimple(
|
|
260
|
+
model,
|
|
261
|
+
{
|
|
262
|
+
systemPrompt,
|
|
263
|
+
messages: [{ role: "user", content: userMessage, timestamp: Date.now() }],
|
|
264
|
+
},
|
|
265
|
+
{
|
|
266
|
+
apiKey: registry.resolver(model, sessionId),
|
|
267
|
+
maxTokens,
|
|
268
|
+
disableReasoning: true,
|
|
269
|
+
// Greedy decode: titling is extraction, not generation. Backends that
|
|
270
|
+
// default temperature high (e.g. Ollama's 0.8) otherwise garble names
|
|
271
|
+
// from the message ("hashline" → "HasHroshi"). Providers whose models
|
|
272
|
+
// reject sampling params drop this via `supportsSamplingParams`.
|
|
273
|
+
temperature: 0,
|
|
274
|
+
metadata,
|
|
275
|
+
signal,
|
|
276
|
+
},
|
|
277
|
+
),
|
|
278
|
+
{ signal },
|
|
275
279
|
);
|
|
276
280
|
|
|
277
281
|
if (response.stopReason === "error") {
|
package/src/vibe/runtime.ts
CHANGED
|
@@ -421,11 +421,17 @@ export class VibeSessionRegistry {
|
|
|
421
421
|
}
|
|
422
422
|
|
|
423
423
|
/**
|
|
424
|
-
* Insert a bare worker record without the spawn
|
|
425
|
-
* lets
|
|
426
|
-
* fake roster + AgentRegistry session without driving a real turn.
|
|
424
|
+
* Insert a bare worker record without the spawn machinery. Test-only —
|
|
425
|
+
* lets focused runtime tests attach an optional synthetic in-flight job.
|
|
427
426
|
*/
|
|
428
|
-
registerRecordForTests(record: {
|
|
427
|
+
registerRecordForTests(record: {
|
|
428
|
+
id: string;
|
|
429
|
+
cli?: VibeCli;
|
|
430
|
+
ownerId: string;
|
|
431
|
+
state?: VibeSessionState;
|
|
432
|
+
jobId?: string;
|
|
433
|
+
}): void {
|
|
434
|
+
const now = Date.now();
|
|
429
435
|
this.#records.set(record.id, {
|
|
430
436
|
id: record.id,
|
|
431
437
|
cli: record.cli ?? "fast",
|
|
@@ -434,8 +440,11 @@ export class VibeSessionRegistry {
|
|
|
434
440
|
parentSessionFile: null,
|
|
435
441
|
agent: getBundledAgent("sonic")!,
|
|
436
442
|
state: record.state ?? "running",
|
|
437
|
-
createdAt:
|
|
438
|
-
lastActivityAt:
|
|
443
|
+
createdAt: now,
|
|
444
|
+
lastActivityAt: now,
|
|
445
|
+
turn: record.jobId
|
|
446
|
+
? { jobId: record.jobId, message: "test turn", startedAt: now, trace: [], toolCount: 0 }
|
|
447
|
+
: undefined,
|
|
439
448
|
queue: [],
|
|
440
449
|
turnCount: 0,
|
|
441
450
|
killed: false,
|
|
@@ -1113,25 +1122,31 @@ export class VibeSessionRegistry {
|
|
|
1113
1122
|
if (job?.status === "running") runningJobs.push(job);
|
|
1114
1123
|
}
|
|
1115
1124
|
|
|
1116
|
-
let
|
|
1125
|
+
let waitEndedByTimeout = false;
|
|
1117
1126
|
if (runningJobs.length > 0 && collectSettled().length === 0) {
|
|
1118
|
-
waited = true;
|
|
1119
1127
|
const timeoutMs = Math.max(1, Math.trunc(args.timeoutMs ?? DEFAULT_WAIT_TIMEOUT_MS));
|
|
1120
1128
|
const watchedJobIds = runningJobs.map(job => job.id);
|
|
1121
1129
|
manager.watchJobs(watchedJobIds);
|
|
1122
|
-
const { promise: timeoutPromise, resolve: timeoutResolve } = Promise.withResolvers<
|
|
1123
|
-
const timeoutHandle = setTimeout(() => timeoutResolve(), timeoutMs);
|
|
1124
|
-
const racePromises: Promise<
|
|
1130
|
+
const { promise: timeoutPromise, resolve: timeoutResolve } = Promise.withResolvers<"timeout">();
|
|
1131
|
+
const timeoutHandle = setTimeout(() => timeoutResolve("timeout"), timeoutMs);
|
|
1132
|
+
const racePromises: Array<Promise<"settled" | "timeout" | "aborted">> = [
|
|
1133
|
+
...runningJobs.map(job => job.promise.then(() => "settled" as const)),
|
|
1134
|
+
timeoutPromise,
|
|
1135
|
+
];
|
|
1125
1136
|
let abortCleanup: (() => void) | undefined;
|
|
1126
1137
|
if (args.signal) {
|
|
1127
|
-
const { promise: abortPromise, resolve: abortResolve } = Promise.withResolvers<
|
|
1128
|
-
const onAbort = () => abortResolve();
|
|
1129
|
-
args.signal.
|
|
1130
|
-
|
|
1138
|
+
const { promise: abortPromise, resolve: abortResolve } = Promise.withResolvers<"aborted">();
|
|
1139
|
+
const onAbort = () => abortResolve("aborted");
|
|
1140
|
+
if (args.signal.aborted) {
|
|
1141
|
+
onAbort();
|
|
1142
|
+
} else {
|
|
1143
|
+
args.signal.addEventListener("abort", onAbort, { once: true });
|
|
1144
|
+
abortCleanup = () => args.signal?.removeEventListener("abort", onAbort);
|
|
1145
|
+
}
|
|
1131
1146
|
racePromises.push(abortPromise);
|
|
1132
1147
|
}
|
|
1133
1148
|
try {
|
|
1134
|
-
await Promise.race(racePromises);
|
|
1149
|
+
waitEndedByTimeout = (await Promise.race(racePromises)) === "timeout";
|
|
1135
1150
|
} finally {
|
|
1136
1151
|
manager.unwatchJobs(watchedJobIds);
|
|
1137
1152
|
clearTimeout(timeoutHandle);
|
|
@@ -1144,7 +1159,7 @@ export class VibeSessionRegistry {
|
|
|
1144
1159
|
// Current in-flight state, independent of the snapshot: a session whose
|
|
1145
1160
|
// watched turn settled may already be mid queued follow-up.
|
|
1146
1161
|
const stillRunning = watched.filter(record => record.turn !== undefined).map(record => record.id);
|
|
1147
|
-
return { settled, stillRunning, timedOut:
|
|
1162
|
+
return { settled, stillRunning, timedOut: waitEndedByTimeout && settled.length === 0 };
|
|
1148
1163
|
}
|
|
1149
1164
|
|
|
1150
1165
|
/** Detach one parent's process-local workers without tombstoning their persisted conversations. */
|