@oh-my-pi/pi-coding-agent 16.4.2 → 16.4.3
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 +42 -0
- package/dist/cli.js +17514 -17799
- package/dist/types/advisor/config.d.ts +4 -3
- package/dist/types/commit/agentic/agent.d.ts +1 -0
- package/dist/types/config/settings-schema.d.ts +28 -0
- package/dist/types/extensibility/plugins/legacy-pi-compat.d.ts +10 -22
- package/dist/types/lsp/deferred-diagnostics.d.ts +11 -0
- package/dist/types/modes/components/move-overlay.d.ts +1 -1
- package/dist/types/modes/components/status-line/component.d.ts +3 -0
- package/dist/types/modes/components/status-line/types.d.ts +3 -0
- package/dist/types/modes/interactive-mode.d.ts +8 -0
- package/dist/types/modes/types.d.ts +2 -0
- package/dist/types/session/agent-session.d.ts +16 -0
- package/dist/types/task/executor.d.ts +31 -0
- package/dist/types/tools/__tests__/vibe-render.test.d.ts +1 -0
- package/dist/types/tools/browser/cmux/cmux-tab.d.ts +3 -1
- package/dist/types/tools/browser/cmux/rpc.d.ts +20 -0
- package/dist/types/tools/browser/run-output.d.ts +25 -0
- package/dist/types/tools/browser/tab-worker.d.ts +16 -0
- package/dist/types/tools/index.d.ts +1 -0
- package/dist/types/tools/vibe.d.ts +161 -0
- package/dist/types/utils/changelog.d.ts +35 -1
- package/dist/types/vibe/runtime.d.ts +124 -0
- package/dist/types/vibe/state.d.ts +4 -0
- package/dist/types/web/search/provider.d.ts +2 -0
- package/dist/types/web/search/providers/bing.d.ts +14 -0
- package/dist/types/web/search/providers/browser-headers.d.ts +9 -0
- package/dist/types/web/search/providers/browser-page.d.ts +32 -0
- package/dist/types/web/search/providers/ecosia.d.ts +14 -0
- package/dist/types/web/search/providers/google.d.ts +13 -0
- package/dist/types/web/search/providers/mojeek.d.ts +14 -0
- package/dist/types/web/search/providers/public.d.ts +37 -0
- package/dist/types/web/search/providers/startpage.d.ts +14 -0
- package/dist/types/web/search/providers/yahoo.d.ts +14 -0
- package/dist/types/web/search/types.d.ts +28 -0
- package/package.json +15 -18
- package/scripts/build-binary.ts +56 -73
- package/scripts/bundle-dist.ts +36 -40
- package/scripts/compile-binary.ts +68 -0
- package/scripts/generate-docs-index.ts +3 -93
- package/scripts/legacy-pi-virtual-module.ts +192 -0
- package/src/advisor/__tests__/advisor.test.ts +13 -0
- package/src/advisor/__tests__/config.test.ts +36 -0
- package/src/advisor/config.ts +11 -8
- package/src/commit/agentic/agent.ts +4 -0
- package/src/commit/agentic/index.ts +46 -21
- package/src/edit/index.ts +10 -76
- package/src/exec/non-interactive-env.ts +0 -1
- package/src/extensibility/plugins/legacy-pi-compat.ts +328 -162
- package/src/extensibility/plugins/legacy-pi-virtual-modules.d.ts +4 -0
- package/src/internal-urls/docs-index.ts +2 -1
- package/src/internal-urls/skill-protocol.ts +1 -1
- package/src/lsp/deferred-diagnostics.ts +66 -0
- package/src/main.ts +13 -13
- package/src/mcp/transports/stdio.test.ts +45 -1
- package/src/mcp/transports/stdio.ts +6 -3
- package/src/modes/acp/acp-agent.ts +65 -1
- package/src/modes/acp/acp-event-mapper.ts +5 -0
- package/src/modes/acp/acp-mode.ts +11 -0
- package/src/modes/components/__tests__/move-overlay.test.ts +16 -1
- package/src/modes/components/advisor-config.ts +15 -7
- package/src/modes/components/move-overlay.ts +2 -3
- package/src/modes/components/status-line/component.ts +6 -0
- package/src/modes/components/status-line/segments.ts +6 -0
- package/src/modes/components/status-line/types.ts +3 -0
- package/src/modes/controllers/command-controller.ts +8 -10
- package/src/modes/interactive-mode.ts +117 -1
- package/src/modes/types.ts +2 -0
- package/src/prompts/system/eager-task.md +2 -2
- package/src/prompts/system/system-prompt.md +9 -2
- package/src/prompts/system/vibe-mode-active.md +23 -0
- package/src/prompts/tools/browser.md +3 -3
- package/src/prompts/tools/grep.md +1 -1
- package/src/prompts/tools/vibe-kill.md +3 -0
- package/src/prompts/tools/vibe-list.md +3 -0
- package/src/prompts/tools/vibe-send.md +9 -0
- package/src/prompts/tools/vibe-spawn.md +10 -0
- package/src/prompts/tools/vibe-turn-result.md +19 -0
- package/src/prompts/tools/vibe-wait.md +8 -0
- package/src/sdk.ts +5 -0
- package/src/session/agent-session.ts +103 -13
- package/src/session/snapcompact-inline.ts +3 -19
- package/src/slash-commands/builtin-registry.ts +24 -8
- package/src/task/agents.ts +0 -2
- package/src/task/executor.ts +105 -0
- package/src/tools/__tests__/vibe-render.test.ts +210 -0
- package/src/tools/bash-skill-urls.ts +1 -1
- package/src/tools/browser/cmux/cmux-tab.ts +46 -48
- package/src/tools/browser/cmux/rpc.ts +50 -0
- package/src/tools/browser/run-output.ts +76 -0
- package/src/tools/browser/tab-worker.ts +264 -129
- package/src/tools/glob.ts +20 -6
- package/src/tools/index.ts +1 -0
- package/src/tools/read.ts +17 -9
- package/src/tools/renderers.ts +6 -0
- package/src/tools/vibe.ts +608 -0
- package/src/tools/write.ts +15 -2
- package/src/utils/changelog.ts +106 -9
- package/src/utils/git.ts +0 -1
- package/src/utils/title-generator.ts +70 -7
- package/src/vibe/runtime.ts +710 -0
- package/src/vibe/state.ts +4 -0
- package/src/web/search/index.ts +14 -6
- package/src/web/search/provider.ts +37 -1
- package/src/web/search/providers/bing.ts +197 -0
- package/src/web/search/providers/browser-headers.ts +92 -0
- package/src/web/search/providers/browser-page.ts +123 -0
- package/src/web/search/providers/duckduckgo.ts +13 -34
- package/src/web/search/providers/ecosia.ts +178 -0
- package/src/web/search/providers/google.ts +193 -0
- package/src/web/search/providers/mojeek.ts +206 -0
- package/src/web/search/providers/public.ts +201 -0
- package/src/web/search/providers/startpage.ts +213 -0
- package/src/web/search/providers/yahoo.ts +179 -0
- package/src/web/search/types.ts +35 -0
- package/dist/types/extensibility/plugins/legacy-pi-bundled-keys.d.ts +0 -10
- package/dist/types/extensibility/plugins/legacy-pi-bundled-registry.d.ts +0 -10
- package/scripts/generate-legacy-pi-bundled-registry.ts +0 -420
- package/src/extensibility/plugins/legacy-pi-bundled-keys.ts +0 -1011
- package/src/extensibility/plugins/legacy-pi-bundled-registry.ts +0 -3430
- package/src/internal-urls/docs-index.generated.txt +0 -2
- package/src/prompts/agents/plan.md +0 -47
|
@@ -92,7 +92,7 @@ import planModeApprovedPrompt from "../prompts/system/plan-mode-approved.md" wit
|
|
|
92
92
|
import planModeCompactInstructionsPrompt from "../prompts/system/plan-mode-compact-instructions.md" with {
|
|
93
93
|
type: "text",
|
|
94
94
|
};
|
|
95
|
-
import type
|
|
95
|
+
import { type AgentRegistry, MAIN_AGENT_ID } from "../registry/agent-registry";
|
|
96
96
|
import {
|
|
97
97
|
type AgentSession,
|
|
98
98
|
type AgentSessionEvent,
|
|
@@ -125,6 +125,7 @@ import { getEditorCommand, openInEditor } from "../utils/external-editor";
|
|
|
125
125
|
import { getSessionAccentAnsi, getSessionAccentHex } from "../utils/session-color";
|
|
126
126
|
import { messageHasDisplayableThinking } from "../utils/thinking-display";
|
|
127
127
|
import { popTerminalTitle, pushTerminalTitle, setSessionTerminalTitle } from "../utils/title-generator";
|
|
128
|
+
import { VibeSessionRegistry } from "../vibe/runtime";
|
|
128
129
|
import type { AssistantMessageComponent } from "./components/assistant-message";
|
|
129
130
|
import type { BashExecutionComponent } from "./components/bash-execution";
|
|
130
131
|
import { ChatBlock, type ChatBlockHost } from "./components/chat-block";
|
|
@@ -434,6 +435,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
434
435
|
planModePaused = false;
|
|
435
436
|
goalModeEnabled = false;
|
|
436
437
|
goalModePaused = false;
|
|
438
|
+
vibeModeEnabled = false;
|
|
437
439
|
planModePlanFilePath: string | undefined = undefined;
|
|
438
440
|
loopModeEnabled = false;
|
|
439
441
|
loopPrompt: string | undefined = undefined;
|
|
@@ -528,6 +530,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
528
530
|
readonly #changelogMarkdown: string | undefined;
|
|
529
531
|
#planModePreviousTools: string[] | undefined;
|
|
530
532
|
#goalModePreviousTools: string[] | undefined;
|
|
533
|
+
#vibeModePreviousTools: string[] | undefined;
|
|
531
534
|
#goalContinuationTimer: NodeJS.Timeout | undefined;
|
|
532
535
|
#goalTurnHadToolCalls = false;
|
|
533
536
|
#goalContinuationTurnInFlight = false;
|
|
@@ -1943,6 +1946,11 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1943
1946
|
this.ui.requestRender();
|
|
1944
1947
|
}
|
|
1945
1948
|
|
|
1949
|
+
#updateVibeModeStatus(): void {
|
|
1950
|
+
this.statusLine.setVibeModeStatus(this.vibeModeEnabled ? { enabled: true } : undefined);
|
|
1951
|
+
this.ui.requestRender();
|
|
1952
|
+
}
|
|
1953
|
+
|
|
1946
1954
|
#updateGoalModeStatus(): void {
|
|
1947
1955
|
const status =
|
|
1948
1956
|
this.goalModeEnabled || this.goalModePaused
|
|
@@ -2111,6 +2119,18 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2111
2119
|
this.#cancelGoalContinuation();
|
|
2112
2120
|
this.#updateGoalModeStatus();
|
|
2113
2121
|
}
|
|
2122
|
+
|
|
2123
|
+
if (this.vibeModeEnabled) {
|
|
2124
|
+
await this.session.deactivateVibeTools(this.#vibeModePreviousTools ?? []);
|
|
2125
|
+
this.session.setVibeModeState(undefined);
|
|
2126
|
+
this.vibeModeEnabled = false;
|
|
2127
|
+
this.#vibeModePreviousTools = undefined;
|
|
2128
|
+
await VibeSessionRegistry.global().killAll(
|
|
2129
|
+
this.session.getAgentId() ?? MAIN_AGENT_ID,
|
|
2130
|
+
this.session.asyncJobManager,
|
|
2131
|
+
);
|
|
2132
|
+
this.#updateVibeModeStatus();
|
|
2133
|
+
}
|
|
2114
2134
|
}
|
|
2115
2135
|
|
|
2116
2136
|
/** Reconcile mode state from session entries on resume/switch. */
|
|
@@ -2150,6 +2170,10 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2150
2170
|
return;
|
|
2151
2171
|
}
|
|
2152
2172
|
this.session.goalRuntime.clearAccounting();
|
|
2173
|
+
if (sessionContext.mode === "vibe") {
|
|
2174
|
+
await this.#enterVibeMode();
|
|
2175
|
+
return;
|
|
2176
|
+
}
|
|
2153
2177
|
if (!this.session.settings.get("plan.enabled")) {
|
|
2154
2178
|
// Clear stale plan/plan_paused mode so re-enabling the setting
|
|
2155
2179
|
// later doesn't unexpectedly restore an old plan session.
|
|
@@ -2176,6 +2200,10 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2176
2200
|
this.showWarning("Exit goal mode first.");
|
|
2177
2201
|
return;
|
|
2178
2202
|
}
|
|
2203
|
+
if (this.vibeModeEnabled) {
|
|
2204
|
+
this.showWarning("Exit vibe mode first.");
|
|
2205
|
+
return;
|
|
2206
|
+
}
|
|
2179
2207
|
|
|
2180
2208
|
this.planModePaused = false;
|
|
2181
2209
|
|
|
@@ -2343,6 +2371,10 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2343
2371
|
this.showWarning("Exit plan mode first.");
|
|
2344
2372
|
return;
|
|
2345
2373
|
}
|
|
2374
|
+
if (this.vibeModeEnabled) {
|
|
2375
|
+
this.showWarning("Exit vibe mode first.");
|
|
2376
|
+
return;
|
|
2377
|
+
}
|
|
2346
2378
|
const previousTools = this.session.getActiveToolNames().filter(name => name !== "goal");
|
|
2347
2379
|
const goalTools = [...new Set([...previousTools, "goal"])];
|
|
2348
2380
|
this.#goalModePreviousTools = previousTools;
|
|
@@ -2847,6 +2879,10 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2847
2879
|
this.showWarning("Exit goal mode first.");
|
|
2848
2880
|
return;
|
|
2849
2881
|
}
|
|
2882
|
+
if (this.vibeModeEnabled) {
|
|
2883
|
+
this.showWarning("Exit vibe mode first.");
|
|
2884
|
+
return;
|
|
2885
|
+
}
|
|
2850
2886
|
if (this.planModeEnabled) {
|
|
2851
2887
|
const planFilePath = this.planModePlanFilePath ?? (await this.#getPlanFilePath());
|
|
2852
2888
|
if (await this.#hasPlanModeDraftContent(planFilePath)) {
|
|
@@ -2882,6 +2918,82 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2882
2918
|
}
|
|
2883
2919
|
}
|
|
2884
2920
|
|
|
2921
|
+
/**
|
|
2922
|
+
* `/vibe` toggle. Entering installs the ephemeral vibe tools, strips the
|
|
2923
|
+
* active toolset down to `read` plus those tools, and injects the director
|
|
2924
|
+
* context. Exiting unregisters them, restores the previous toolset, and kills
|
|
2925
|
+
* every worker session so workers cannot outlive the mode that directs them.
|
|
2926
|
+
*/
|
|
2927
|
+
async handleVibeModeCommand(initialPrompt?: string): Promise<void> {
|
|
2928
|
+
if (this.vibeModeEnabled) {
|
|
2929
|
+
await this.#exitVibeMode();
|
|
2930
|
+
return;
|
|
2931
|
+
}
|
|
2932
|
+
if (this.planModeEnabled || this.planModePaused) {
|
|
2933
|
+
this.showWarning("Exit plan mode first.");
|
|
2934
|
+
return;
|
|
2935
|
+
}
|
|
2936
|
+
if (this.goalModeEnabled || this.goalModePaused) {
|
|
2937
|
+
this.showWarning("Exit goal mode first.");
|
|
2938
|
+
return;
|
|
2939
|
+
}
|
|
2940
|
+
await this.#enterVibeMode();
|
|
2941
|
+
if (initialPrompt && this.onInputCallback) {
|
|
2942
|
+
this.onInputCallback(this.startPendingSubmission({ text: initialPrompt }));
|
|
2943
|
+
}
|
|
2944
|
+
}
|
|
2945
|
+
|
|
2946
|
+
async #enterVibeMode(): Promise<void> {
|
|
2947
|
+
if (this.vibeModeEnabled) {
|
|
2948
|
+
return;
|
|
2949
|
+
}
|
|
2950
|
+
if (this.planModeEnabled || this.planModePaused) {
|
|
2951
|
+
this.showWarning("Exit plan mode first.");
|
|
2952
|
+
return;
|
|
2953
|
+
}
|
|
2954
|
+
if (this.goalModeEnabled || this.goalModePaused) {
|
|
2955
|
+
this.showWarning("Exit goal mode first.");
|
|
2956
|
+
return;
|
|
2957
|
+
}
|
|
2958
|
+
|
|
2959
|
+
const previousTools = this.session.getActiveToolNames();
|
|
2960
|
+
await this.session.activateVibeTools(["read"]);
|
|
2961
|
+
this.#vibeModePreviousTools = previousTools;
|
|
2962
|
+
this.vibeModeEnabled = true;
|
|
2963
|
+
// Suppress cache-miss marker on the next turn: vibe mode changes the
|
|
2964
|
+
// injected context, which predictably invalidates the cache.
|
|
2965
|
+
this.lastAssistantUsage = undefined;
|
|
2966
|
+
this.session.setVibeModeState({ enabled: true });
|
|
2967
|
+
if (this.session.isStreaming) {
|
|
2968
|
+
await this.session.sendVibeModeContext({ deliverAs: "steer" });
|
|
2969
|
+
}
|
|
2970
|
+
this.#updateVibeModeStatus();
|
|
2971
|
+
this.sessionManager.appendModeChange("vibe");
|
|
2972
|
+
this.showStatus("Vibe mode enabled. You direct fast/good worker sessions; toolset is read + vibe tools.");
|
|
2973
|
+
}
|
|
2974
|
+
|
|
2975
|
+
async #exitVibeMode(): Promise<void> {
|
|
2976
|
+
if (!this.vibeModeEnabled) {
|
|
2977
|
+
return;
|
|
2978
|
+
}
|
|
2979
|
+
await this.session.deactivateVibeTools(this.#vibeModePreviousTools ?? []);
|
|
2980
|
+
this.session.setVibeModeState(undefined);
|
|
2981
|
+
this.vibeModeEnabled = false;
|
|
2982
|
+
this.#vibeModePreviousTools = undefined;
|
|
2983
|
+
this.lastAssistantUsage = undefined;
|
|
2984
|
+
const killed = await VibeSessionRegistry.global().killAll(
|
|
2985
|
+
this.session.getAgentId() ?? MAIN_AGENT_ID,
|
|
2986
|
+
this.session.asyncJobManager,
|
|
2987
|
+
);
|
|
2988
|
+
this.#updateVibeModeStatus();
|
|
2989
|
+
this.sessionManager.appendModeChange("none");
|
|
2990
|
+
this.showStatus(
|
|
2991
|
+
killed > 0
|
|
2992
|
+
? `Vibe mode disabled. Killed ${killed} worker session${killed === 1 ? "" : "s"}.`
|
|
2993
|
+
: "Vibe mode disabled.",
|
|
2994
|
+
);
|
|
2995
|
+
}
|
|
2996
|
+
|
|
2885
2997
|
async #handleGoalBudgetCommand(rawBudget: string): Promise<void> {
|
|
2886
2998
|
const state = this.session.getGoalModeState();
|
|
2887
2999
|
if (!this.goalModeEnabled || !state?.enabled) {
|
|
@@ -2914,6 +3026,10 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2914
3026
|
this.showWarning("Exit plan mode first.");
|
|
2915
3027
|
return;
|
|
2916
3028
|
}
|
|
3029
|
+
if (this.vibeModeEnabled) {
|
|
3030
|
+
this.showWarning("Exit vibe mode first.");
|
|
3031
|
+
return;
|
|
3032
|
+
}
|
|
2917
3033
|
if (!this.session.settings.get("goal.enabled")) {
|
|
2918
3034
|
this.showWarning("Goal mode is disabled. Enable it in settings (goal.enabled).");
|
|
2919
3035
|
return;
|
package/src/modes/types.ts
CHANGED
|
@@ -153,6 +153,7 @@ export interface InteractiveModeContext {
|
|
|
153
153
|
toolOutputExpanded: boolean;
|
|
154
154
|
todoExpanded: boolean;
|
|
155
155
|
planModeEnabled: boolean;
|
|
156
|
+
vibeModeEnabled: boolean;
|
|
156
157
|
goalModeEnabled: boolean;
|
|
157
158
|
goalModePaused: boolean;
|
|
158
159
|
loopModeEnabled: boolean;
|
|
@@ -394,6 +395,7 @@ export interface InteractiveModeContext {
|
|
|
394
395
|
openExternalEditor(): void;
|
|
395
396
|
registerExtensionShortcuts(): void;
|
|
396
397
|
handlePlanModeCommand(initialPrompt?: string): Promise<void>;
|
|
398
|
+
handleVibeModeCommand(initialPrompt?: string): Promise<void>;
|
|
397
399
|
handleGoalModeCommand(rest?: string): Promise<void>;
|
|
398
400
|
handleGuidedGoalCommand(rest?: string): Promise<void>;
|
|
399
401
|
handleLoopCommand(args?: string): Promise<string | undefined>;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<system-reminder>
|
|
2
2
|
Task delegation is enabled — subagents are the default for this request.
|
|
3
3
|
|
|
4
|
-
Explore and settle the approach FIRST. Once the design is settled, you MUST fan the work out to `{{toolRefs.task}}` subagents instead of implementing it yourself.{{#if taskBatch}} Batch independent slices into ONE parallel `{{toolRefs.task}}` call; never serialize work that can run concurrently.{{/if}}
|
|
4
|
+
Explore and settle the approach FIRST — scoping, top-level decomposition, and cross-slice contracts are YOUR job; NEVER spawn a subagent to produce the overall plan (per-slice design travels with its executor). Once the design is settled, you MUST fan the work out to `{{toolRefs.task}}` subagents instead of implementing it yourself.{{#if taskBatch}} Batch independent slices into ONE parallel `{{toolRefs.task}}` call; never serialize work that can run concurrently.{{/if}}
|
|
5
5
|
|
|
6
|
-
Work alone
|
|
6
|
+
Work alone for: a single-file edit under ~30 lines, a direct answer requiring no code changes, a command the user explicitly asked you to run, or when only ONE runnable slice exists — a lone subagent is a lossy handoff, not parallelism.
|
|
7
7
|
</system-reminder>
|
|
@@ -163,12 +163,19 @@ Everything else—multi-file changes, refactors, new features, tests, investigat
|
|
|
163
163
|
- Use `{{toolRefs.task}}` to map unknown code instead of reading file after file yourself.
|
|
164
164
|
- NEVER abandon phases under scope pressure—delegate, don't shrink.
|
|
165
165
|
- Default to parallel for complex changes. Delegate via `{{toolRefs.task}}` for non-importing file edits, multi-subsystem investigation, and decomposable work.
|
|
166
|
-
|
|
166
|
+
{{/if}}
|
|
167
|
+
|
|
168
|
+
## Delegation gates:
|
|
169
|
+
- **Scope before you spawn.** YOU read the request, map the work, and name the independent slices. Delegation is NEVER the first move on a fresh request — unless the user already enumerated 2+ self-contained runnable slices, in which case dispatch them immediately in one batch.
|
|
170
|
+
- **NEVER outsource the top-level plan.** Scoping the request, the overall decomposition, and cross-slice contracts (formats, schemas, interfaces) are YOUR job. A generic "plan"/"design" subagent as step one starts blank, knows less than you, runs alone, and adds a full round-trip for ZERO parallelism — the canonical dumb spawn. Delegating design WITHIN a slice is fine: each executor details its own slice, and once the top-level split is settled you MAY fan out per-subsystem sub-planning in parallel. (Competing plans or independent reviews the user explicitly asked for are also legitimate.)
|
|
171
|
+
- **Spawn-one-then-wait is a bug.** A lone subagent you sit idle behind is you doing the work with extra latency plus a lossy handoff — do it inline. A single spawn is fine ONLY when you immediately continue another independent slice yourself, or it is a read-only scout keeping bulk exploration out of your context.
|
|
172
|
+
- **Width = real independence.** Fan out exactly as wide as the work genuinely decomposes{{#if taskBatch}}, batched into one `tasks[]` array{{else}}, as parallel calls in one message{{/if}}. NEVER serialize slices that can run concurrently; NEVER pad the batch with invented slices to look parallel.
|
|
173
|
+
- **Prerequisites run inline.** A step every slice depends on (shared schema, core interface, scaffold) has by definition nothing to run beside it — do it yourself, then fan out. "Parallelize" means parallel EXECUTION of the independent slices, not routing sequential steps through agents.
|
|
174
|
+
- **You own the user's intent.** Subagents never see this conversation. Interpreting the request and taste calls stay with you; each assignment carries every requirement its slice needs.
|
|
167
175
|
{{#when MAX_CONCURRENCY ">" 0}}
|
|
168
176
|
- **Concurrency cap:** At most {{pluralize MAX_CONCURRENCY "subagent" "subagents"}} run at once in this session — anything beyond that just queues, so a {{#if taskBatch}}`tasks[]` batch{{else}}set of parallel `task` calls{{/if}} larger than {{MAX_CONCURRENCY}} only delays results. Keep the fan-out at or under the cap.
|
|
169
177
|
{{/when}}
|
|
170
178
|
- **Sequence only when necessary:** The only reason to run A before B is if B strictly requires A's output to function (e.g., a core API contract or schema migration). {{#if taskIrcEnabled}}If the missing piece is small, run them in parallel and have B ask A via `irc`!{{/if}}
|
|
171
|
-
{{/if}}
|
|
172
179
|
{{/has}}
|
|
173
180
|
|
|
174
181
|
EXECUTION WORKFLOW
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<vibe-mode>
|
|
2
|
+
Vibe mode is ON. You are the DIRECTOR. You do not edit, run, grep, or build anything yourself — your hands are off the keyboard. You drive two kinds of worker CLIs, each a full coding agent with every normal tool, and you verify their work by reading files.
|
|
3
|
+
|
|
4
|
+
Your entire toolset: `read`, `vibe_spawn`, `vibe_send`, `vibe_wait`, `vibe_kill`, `vibe_list`.
|
|
5
|
+
|
|
6
|
+
# The two CLIs you drive
|
|
7
|
+
|
|
8
|
+
- `fast` — low-latency model. Mechanical, well-specified work: renames, small fixes, boilerplate, data collection, running tests and reporting output.
|
|
9
|
+
- `good` — strong model. Hard work: design, tricky debugging, multi-file refactors, anything needing judgment.
|
|
10
|
+
|
|
11
|
+
Sessions are persistent conversations, like terminals you keep open. A session remembers everything you told it and everything it did. Spawn once per workstream, then keep talking to the SAME session — never respawn for a follow-up on the same workstream.
|
|
12
|
+
|
|
13
|
+
# How to direct
|
|
14
|
+
|
|
15
|
+
1. Split the request into independent workstreams. One session per workstream; keep each session on its own workstream to build useful context.
|
|
16
|
+
2. `vibe_spawn` with a complete, self-contained brief: files, constraints, acceptance criteria. Workers start blank — they never see this conversation.
|
|
17
|
+
3. Sends and spawns return immediately; results arrive on their own when a worker finishes its turn. Keep directing other sessions meanwhile; call `vibe_wait` only when you cannot proceed without a result.
|
|
18
|
+
4. When a turn result arrives, judge it: `read` the touched files to verify claims before building on them. Follow up with `vibe_send` — corrections, next step, or a review request.
|
|
19
|
+
5. Route by difficulty: draft with `fast`, escalate to `good` when `fast` stalls or the problem needs judgment; have `good` design and `fast` execute the mechanical parts.
|
|
20
|
+
6. `vibe_kill` a session that is stuck or whose workstream is done; `vibe_list` when you lose track of the roster.
|
|
21
|
+
|
|
22
|
+
Run sessions concurrently — one `fast` and one `good` on different workstreams is the normal shape. You stay responsible for the final outcome: verify with `read`, do not take a worker's word for it.
|
|
23
|
+
</vibe-mode>
|
|
@@ -17,9 +17,9 @@ Drives real Chromium tab; full puppeteer access via JS.
|
|
|
17
17
|
- `tab.observe({ includeAll?, viewportOnly? })` — accessibility snapshot: `{ url, title, viewport, scroll, elements: [{ id, role, name, value, states, … }] }`. Ids stable until next observe/goto.
|
|
18
18
|
- `tab.ariaSnapshot(selector?, { depth?, boxes? })` — Playwright-format ARIA-tree YAML (nested roles + accessible names + `/url`/`/placeholder`), scoped to `selector` or the whole document. Every node carries a `[ref=eN]` id; `[cursor=pointer]` flags clickables. Captures dense, hierarchical structure/text that `observe()`'s flat list flattens away. Refs renumber from e1 each call and stay valid until the next `ariaSnapshot()`.
|
|
19
19
|
- `tab.ref("e5")` — `[ref=eN]` from the last ariaSnapshot → element handle with the common action methods (`.click()`, `.type()`, `.fill()`, `.hover()`, `.evaluate()`, …); the primary way to act on a ref. For convenience `aria-ref=e5` also works inline in `tab.click`/`type`/`fill`/`waitFor`/`scrollIntoView` (e.g. `tab.click("aria-ref=e5")`).
|
|
20
|
-
- `tab.id(n)` — id from last observe →
|
|
20
|
+
- `tab.id(n)` — id from last observe → element handle with the same action methods (`.click()`, `.type()`, `.fill()`, …).
|
|
21
21
|
- `tab.click(selector)` / `tab.type(selector, text)` / `tab.fill(selector, value)` / `tab.press(key, { selector? })` / `tab.scroll(dx, dy)`.
|
|
22
|
-
- `tab.waitFor(selector, { timeout? })` / `tab.waitForSelector(selector, { timeout?, visible?, hidden? })` — wait until attached (optionally visible/hidden); returns
|
|
22
|
+
- `tab.waitFor(selector, { timeout? })` / `tab.waitForSelector(selector, { timeout?, visible?, hidden? })` — wait until attached (optionally visible/hidden); returns an action-method handle.
|
|
23
23
|
- `tab.drag(from, to)` — endpoints: selector (center-to-center) or `{ x, y }` viewport point (canvases, sliders).
|
|
24
24
|
- `tab.scrollIntoView(selector)` — center in viewport; before clicking off-screen elements.
|
|
25
25
|
- `tab.select(selector, …values)` — set `<select>` option(s); returns selection. `tab.fill` NEVER works for selects.
|
|
@@ -30,7 +30,7 @@ Drives real Chromium tab; full puppeteer access via JS.
|
|
|
30
30
|
- `tab.evaluate(fn, …args)` — `page.evaluate` for ad-hoc DOM reads.
|
|
31
31
|
- `tab.screenshot({ selector?, fullPage?, save?, silent? })` — capture + attach for viewing (`silent: true` skips). Pass `save` only when a later step needs the file.
|
|
32
32
|
- `tab.extract(format = "markdown")` — readable page content (`"markdown"` | `"text"`); throws when nothing readable.
|
|
33
|
-
- Selectors: CSS + puppeteer handlers `aria/Sign in`, `text/Continue`, `xpath/…`, `pierce/…`; also Playwright-style `p-aria/…`, `p-text/…`. Playwright-only engines/pseudos (`:has-text()`, `:visible`, …) are rejected — use `text/…` or `aria/…`. A stalled action/wait fails fast with a named `tab.<op
|
|
33
|
+
- Selectors: CSS + puppeteer handlers `aria/Sign in`, `text/Continue`, `xpath/…`, `pierce/…`; also Playwright-style `p-aria/…`, `p-text/…`. Playwright-only engines/pseudos (`:has-text()`, `:visible`, …) are rejected — use `text/…` or `aria/…`. A stalled action/wait fails fast with a named `tab.<op>` error carrying a match-count diagnosis, never the whole-cell timeout; a selector matching nothing fails in ~2s (pass an explicit `{ timeout }` to `waitFor`/`waitForSelector` to wait out slow-appearing elements).
|
|
34
34
|
</instruction>
|
|
35
35
|
|
|
36
36
|
<critical>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Greps files using regex.
|
|
2
2
|
|
|
3
3
|
<instruction>
|
|
4
|
-
- Rust regex
|
|
4
|
+
- Supports Rust regex and PCRE2 syntax.
|
|
5
5
|
- `path`: SHOULD scope to a known path (e.g. `src`); pass several as a delimited list (`src; tests`). Use `selector` only for line-number filtering, never path/root selection (`"/"` belongs in `path`).
|
|
6
6
|
- Literal colon filename + line range? Use `selector` (e.g. `{"path":"test:1-2","selector":"1-2"}`), not recursive `path:"test:1-2:1-2"`.
|
|
7
7
|
- Cross-line patterns detected from literal `\n` or `\\n` in `pattern`.
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
Terminates a worker session: aborts its in-flight turn (if any) and discards the session. Its conversation cannot be continued afterwards — the transcript stays readable at `history://<id>`.
|
|
2
|
+
|
|
3
|
+
Kill sessions that are stuck, looping, or whose workstream is complete. Freeing dead weight keeps the roster legible.
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
Shows your worker-session roster: id, CLI flavor (`fast`/`good`), state (`starting`/`running`/`idle`/`dead`), model, turn count, queued messages, and a one-line gist of each session's latest activity.
|
|
2
|
+
|
|
3
|
+
Use it to reorient: which sessions exist, who is busy, who is idle and ready for the next instruction.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
Sends a message to one of your worker sessions (by id from `vibe_spawn` / `vibe_list`). The session keeps its full conversation history — refer to earlier work naturally ("now do the same for the other module").
|
|
2
|
+
|
|
3
|
+
Returns immediately with an ack telling you how the message landed:
|
|
4
|
+
|
|
5
|
+
- `turn` — the worker was idle; a new turn started. Its result self-delivers when done.
|
|
6
|
+
- `steered` — the worker was mid-turn; your message was injected into the running turn as live steering.
|
|
7
|
+
- `queued` — the worker was mid-turn and not steerable right now; your message runs as the next turn automatically.
|
|
8
|
+
|
|
9
|
+
Use it for follow-ups, corrections, scope changes, and review requests. Never re-explain prior context — the session already has it.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
Starts a persistent worker session — a full coding agent (edit, bash, grep, everything) that you drive by conversation. Pick the CLI flavor per task:
|
|
2
|
+
|
|
3
|
+
- `fast`: low-latency model for mechanical, well-specified work (renames, boilerplate, running tests, data collection).
|
|
4
|
+
- `good`: strong model for hard work (design, debugging, multi-file changes, judgment calls).
|
|
5
|
+
|
|
6
|
+
`prompt` is the session's first instruction. The worker starts with NO context beyond it — include files, constraints, and acceptance criteria. `name` (optional) labels the session; otherwise one is generated.
|
|
7
|
+
|
|
8
|
+
Returns immediately with the session id; the turn's result (activity trace + the worker's response) is delivered to you automatically when the worker finishes. Do not wait unless you are blocked — keep directing other sessions.
|
|
9
|
+
|
|
10
|
+
The session persists after the turn: it remembers the whole conversation. Continue it with `vibe_send`; never spawn a second session for a follow-up on the same workstream.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
<vibe-turn session="{{id}}" cli="{{cli}}" turn="{{turn}}" status="{{status}}" duration="{{duration}}"{{#if model}} model="{{model}}"{{/if}}>
|
|
2
|
+
<activity tool-calls="{{toolCount}}" requests="{{requests}}">
|
|
3
|
+
{{#each trace}}
|
|
4
|
+
- {{this}}
|
|
5
|
+
{{/each}}
|
|
6
|
+
{{#if traceOverflow}}
|
|
7
|
+
- … {{traceOverflow}} earlier tool call(s) not shown
|
|
8
|
+
{{/if}}
|
|
9
|
+
</activity>
|
|
10
|
+
<response{{#if responseTruncated}} truncated="true" full-output="agent://{{id}}"{{/if}}>
|
|
11
|
+
{{response}}
|
|
12
|
+
</response>
|
|
13
|
+
{{#if error}}
|
|
14
|
+
<error>{{error}}</error>
|
|
15
|
+
{{/if}}
|
|
16
|
+
{{#if alive}}
|
|
17
|
+
Session `{{id}}` is idle and retains this conversation — continue it with vibe_send. Transcript: history://{{id}}
|
|
18
|
+
{{/if}}
|
|
19
|
+
</vibe-turn>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
Blocks until ONE watched session finishes its current turn, the timeout elapses, or you are interrupted — not until all finish. Re-issue to keep waiting.
|
|
2
|
+
|
|
3
|
+
Turn results normally deliver themselves; you NEVER need this to receive output. Use it only when you are completely blocked and cannot direct any other session.
|
|
4
|
+
|
|
5
|
+
- `sessions` — ids to watch. Omit to watch every session with a turn in flight.
|
|
6
|
+
- `timeout` — seconds to wait (default 30).
|
|
7
|
+
|
|
8
|
+
A finished turn's full result (activity trace + response) is returned here and will not be re-delivered separately.
|
package/src/sdk.ts
CHANGED
|
@@ -161,6 +161,7 @@ import {
|
|
|
161
161
|
BUILTIN_TOOLS,
|
|
162
162
|
computeEssentialBuiltinNames,
|
|
163
163
|
createTools,
|
|
164
|
+
createVibeTools,
|
|
164
165
|
type DeferredDiagnosticsEntry,
|
|
165
166
|
discoverStartupLspServers,
|
|
166
167
|
EditTool,
|
|
@@ -2883,6 +2884,10 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
2883
2884
|
skillsSettings: settings.getGroup("skills"),
|
|
2884
2885
|
modelRegistry,
|
|
2885
2886
|
toolRegistry,
|
|
2887
|
+
createVibeTools:
|
|
2888
|
+
(options.taskDepth ?? 0) === 0 && !options.parentTaskPrefix
|
|
2889
|
+
? () => createVibeTools(toolSession)
|
|
2890
|
+
: undefined,
|
|
2886
2891
|
builtInToolNames: builtInRegistryToolNames,
|
|
2887
2892
|
transformContext,
|
|
2888
2893
|
transformProviderContext,
|
|
@@ -277,6 +277,7 @@ import toolCallLoopRedirectTemplate from "../prompts/system/tool-call-loop-redir
|
|
|
277
277
|
import ttsrInterruptTemplate from "../prompts/system/ttsr-interrupt.md" with { type: "text" };
|
|
278
278
|
import ttsrToolReminderTemplate from "../prompts/system/ttsr-tool-reminder.md" with { type: "text" };
|
|
279
279
|
import unexpectedStopRetryTemplate from "../prompts/system/unexpected-stop-retry.md" with { type: "text" };
|
|
280
|
+
import vibeModeActivePrompt from "../prompts/system/vibe-mode-active.md" with { type: "text" };
|
|
280
281
|
import { AgentRegistry } from "../registry/agent-registry";
|
|
281
282
|
import {
|
|
282
283
|
deobfuscateAssistantContent,
|
|
@@ -330,6 +331,7 @@ import { describeAttachedImagesForTextModel } from "../utils/image-vision-fallba
|
|
|
330
331
|
import { formatLocalCalendarDate } from "../utils/local-date";
|
|
331
332
|
import { generateSessionTitle } from "../utils/title-generator";
|
|
332
333
|
import { buildNamedToolChoice, isToolChoiceActive } from "../utils/tool-choice";
|
|
334
|
+
import type { VibeModeState } from "../vibe/state";
|
|
333
335
|
import type { AuthStorage } from "./auth-storage";
|
|
334
336
|
import type { ClientBridge, ClientBridgePermissionOption, ClientBridgePermissionOutcome } from "./client-bridge";
|
|
335
337
|
import {
|
|
@@ -712,6 +714,8 @@ export interface AgentSessionConfig {
|
|
|
712
714
|
modelRegistry: ModelRegistry;
|
|
713
715
|
/** Tool registry for LSP and settings */
|
|
714
716
|
toolRegistry?: Map<string, AgentTool>;
|
|
717
|
+
/** Creates the tools registered only while `/vibe` mode is active. */
|
|
718
|
+
createVibeTools?: () => AgentTool[];
|
|
715
719
|
/** Tool names whose current registry entry is still the built-in implementation. */
|
|
716
720
|
builtInToolNames?: Iterable<string>;
|
|
717
721
|
/** Update tool-session predicates that render guidance from the live active tool set. */
|
|
@@ -1608,6 +1612,7 @@ export class AgentSession {
|
|
|
1608
1612
|
#advisorPrimaryTurnsCompleted = 0;
|
|
1609
1613
|
#advisorInterruptImmuneTurnStart: number | undefined;
|
|
1610
1614
|
#planModeState: PlanModeState | undefined;
|
|
1615
|
+
#vibeModeState: VibeModeState | undefined;
|
|
1611
1616
|
#goalModeState: GoalModeState | undefined;
|
|
1612
1617
|
#goalRuntime: GoalRuntime;
|
|
1613
1618
|
#advisorEnabled = false;
|
|
@@ -1744,6 +1749,8 @@ export class AgentSession {
|
|
|
1744
1749
|
|
|
1745
1750
|
// Tool registry and prompt builder for extensions
|
|
1746
1751
|
#toolRegistry: Map<string, AgentTool>;
|
|
1752
|
+
#createVibeTools: (() => AgentTool[]) | undefined;
|
|
1753
|
+
#installedVibeToolNames = new Set<string>();
|
|
1747
1754
|
#transformContext: (messages: AgentMessage[], signal?: AbortSignal) => AgentMessage[] | Promise<AgentMessage[]>;
|
|
1748
1755
|
#onPayload: SimpleStreamOptions["onPayload"] | undefined;
|
|
1749
1756
|
#onResponse: SimpleStreamOptions["onResponse"] | undefined;
|
|
@@ -2107,6 +2114,7 @@ export class AgentSession {
|
|
|
2107
2114
|
this.#pruneToolDescriptions = config.pruneToolDescriptions === true;
|
|
2108
2115
|
this.#validateRetryFallbackChains();
|
|
2109
2116
|
this.#toolRegistry = config.toolRegistry ?? new Map();
|
|
2117
|
+
this.#createVibeTools = config.createVibeTools;
|
|
2110
2118
|
this.#builtInToolNames = new Set(config.builtInToolNames ?? []);
|
|
2111
2119
|
this.#requestedToolNames = config.requestedToolNames;
|
|
2112
2120
|
this.#transformContext = config.transformContext ?? (messages => messages);
|
|
@@ -2501,7 +2509,7 @@ export class AgentSession {
|
|
|
2501
2509
|
if (this.#advisorSharedInstructions) systemPrompt.push(this.#advisorSharedInstructions);
|
|
2502
2510
|
if (config.instructions?.trim()) systemPrompt.push(config.instructions.trim());
|
|
2503
2511
|
|
|
2504
|
-
const names = config.tools
|
|
2512
|
+
const names = config.tools === undefined ? ADVISOR_DEFAULT_TOOL_NAMES : new Set(config.tools);
|
|
2505
2513
|
const tools = (this.#advisorTools ?? []).filter(t => names.has(t.name));
|
|
2506
2514
|
|
|
2507
2515
|
const primaryProviderSessionId = this.sessionId;
|
|
@@ -6113,6 +6121,49 @@ export class AgentSession {
|
|
|
6113
6121
|
return Array.from(this.#toolRegistry.keys());
|
|
6114
6122
|
}
|
|
6115
6123
|
|
|
6124
|
+
#wrapRuntimeTool(tool: AgentTool): AgentTool {
|
|
6125
|
+
const wrapped = wrapToolWithMetaNotice(tool);
|
|
6126
|
+
return this.#extensionRunner ? new ExtensionToolWrapper(wrapped, this.#extensionRunner) : wrapped;
|
|
6127
|
+
}
|
|
6128
|
+
|
|
6129
|
+
/**
|
|
6130
|
+
* Registers the ephemeral vibe tools and activates them alongside `baseToolNames`.
|
|
6131
|
+
*
|
|
6132
|
+
* @throws When this session cannot create vibe tools or the factory returns duplicate names.
|
|
6133
|
+
*/
|
|
6134
|
+
async activateVibeTools(baseToolNames: string[]): Promise<void> {
|
|
6135
|
+
const createVibeTools = this.#createVibeTools;
|
|
6136
|
+
if (!createVibeTools) {
|
|
6137
|
+
throw new Error("Vibe tools are unavailable in this session.");
|
|
6138
|
+
}
|
|
6139
|
+
|
|
6140
|
+
const tools = createVibeTools();
|
|
6141
|
+
const vibeToolNames = tools.map(tool => tool.name);
|
|
6142
|
+
if (new Set(vibeToolNames).size !== vibeToolNames.length) {
|
|
6143
|
+
throw new Error("Vibe tool names must be unique.");
|
|
6144
|
+
}
|
|
6145
|
+
|
|
6146
|
+
for (const tool of tools) {
|
|
6147
|
+
if (this.#toolRegistry.has(tool.name)) continue;
|
|
6148
|
+
this.#toolRegistry.set(tool.name, this.#wrapRuntimeTool(tool));
|
|
6149
|
+
this.#builtInToolNames.add(tool.name);
|
|
6150
|
+
this.#installedVibeToolNames.add(tool.name);
|
|
6151
|
+
}
|
|
6152
|
+
|
|
6153
|
+
await this.#applyActiveToolsByName([...new Set([...baseToolNames, ...vibeToolNames])]);
|
|
6154
|
+
}
|
|
6155
|
+
|
|
6156
|
+
/** Removes tools installed by {@link activateVibeTools} and activates `nextToolNames`. */
|
|
6157
|
+
async deactivateVibeTools(nextToolNames: string[]): Promise<void> {
|
|
6158
|
+
for (const name of this.#installedVibeToolNames) {
|
|
6159
|
+
this.#toolRegistry.delete(name);
|
|
6160
|
+
this.#builtInToolNames.delete(name);
|
|
6161
|
+
this.#selectedDiscoveredToolNames.delete(name);
|
|
6162
|
+
}
|
|
6163
|
+
this.#installedVibeToolNames.clear();
|
|
6164
|
+
await this.#applyActiveToolsByName(nextToolNames);
|
|
6165
|
+
}
|
|
6166
|
+
|
|
6116
6167
|
#getEditModeSession() {
|
|
6117
6168
|
return {
|
|
6118
6169
|
settings: this.settings,
|
|
@@ -7071,6 +7122,14 @@ export class AgentSession {
|
|
|
7071
7122
|
this.#goalModeState = state;
|
|
7072
7123
|
}
|
|
7073
7124
|
|
|
7125
|
+
getVibeModeState(): VibeModeState | undefined {
|
|
7126
|
+
return this.#vibeModeState;
|
|
7127
|
+
}
|
|
7128
|
+
|
|
7129
|
+
setVibeModeState(state: VibeModeState | undefined): void {
|
|
7130
|
+
this.#vibeModeState = state;
|
|
7131
|
+
}
|
|
7132
|
+
|
|
7074
7133
|
get goalRuntime(): GoalRuntime {
|
|
7075
7134
|
return this.#goalRuntime;
|
|
7076
7135
|
}
|
|
@@ -7203,6 +7262,21 @@ export class AgentSession {
|
|
|
7203
7262
|
);
|
|
7204
7263
|
}
|
|
7205
7264
|
|
|
7265
|
+
async sendVibeModeContext(options?: { deliverAs?: "steer" | "followUp" | "nextTurn" }): Promise<void> {
|
|
7266
|
+
const message = this.#buildVibeModeMessage();
|
|
7267
|
+
if (!message) return;
|
|
7268
|
+
await this.sendCustomMessage(
|
|
7269
|
+
{
|
|
7270
|
+
customType: message.customType,
|
|
7271
|
+
content: message.content,
|
|
7272
|
+
display: message.display,
|
|
7273
|
+
details: message.details,
|
|
7274
|
+
attribution: message.attribution,
|
|
7275
|
+
},
|
|
7276
|
+
options ? { deliverAs: options.deliverAs } : undefined,
|
|
7277
|
+
);
|
|
7278
|
+
}
|
|
7279
|
+
|
|
7206
7280
|
resolveRoleModel(role: string): Model | undefined {
|
|
7207
7281
|
return this.#resolveRoleModelFull(role, this.#modelRegistry.getAvailable(), this.model).model;
|
|
7208
7282
|
}
|
|
@@ -7331,6 +7405,18 @@ export class AgentSession {
|
|
|
7331
7405
|
};
|
|
7332
7406
|
}
|
|
7333
7407
|
|
|
7408
|
+
#buildVibeModeMessage(): CustomMessage | null {
|
|
7409
|
+
if (!this.#vibeModeState?.enabled) return null;
|
|
7410
|
+
return {
|
|
7411
|
+
role: "custom",
|
|
7412
|
+
customType: "vibe-mode-context",
|
|
7413
|
+
content: prompt.render(vibeModeActivePrompt),
|
|
7414
|
+
display: false,
|
|
7415
|
+
attribution: "agent",
|
|
7416
|
+
timestamp: Date.now(),
|
|
7417
|
+
};
|
|
7418
|
+
}
|
|
7419
|
+
|
|
7334
7420
|
#sanitizeGoalTodoText(text: string): string {
|
|
7335
7421
|
return escapeXmlText(text)
|
|
7336
7422
|
.replace(/\r\n/g, "\\n")
|
|
@@ -7768,6 +7854,10 @@ export class AgentSession {
|
|
|
7768
7854
|
if (goalModeMessage) {
|
|
7769
7855
|
messages.push(goalModeMessage);
|
|
7770
7856
|
}
|
|
7857
|
+
const vibeModeMessage = this.#buildVibeModeMessage();
|
|
7858
|
+
if (vibeModeMessage) {
|
|
7859
|
+
messages.push(vibeModeMessage);
|
|
7860
|
+
}
|
|
7771
7861
|
if (options?.prependMessages) {
|
|
7772
7862
|
messages.push(...options.prependMessages);
|
|
7773
7863
|
}
|
|
@@ -10874,21 +10964,21 @@ export class AgentSession {
|
|
|
10874
10964
|
|
|
10875
10965
|
this.#emptyStopRetryCount++;
|
|
10876
10966
|
if (this.#emptyStopRetryCount > EMPTY_STOP_MAX_RETRIES) {
|
|
10877
|
-
|
|
10878
|
-
|
|
10967
|
+
const attempts = this.#emptyStopRetryCount - 1;
|
|
10968
|
+
const finalError = "Assistant returned empty stop after retry cap";
|
|
10969
|
+
logger.warn(finalError, {
|
|
10970
|
+
attempts,
|
|
10879
10971
|
model: assistantMessage.model,
|
|
10880
10972
|
provider: assistantMessage.provider,
|
|
10881
10973
|
});
|
|
10882
|
-
|
|
10883
|
-
|
|
10884
|
-
|
|
10885
|
-
|
|
10886
|
-
|
|
10887
|
-
|
|
10888
|
-
|
|
10889
|
-
|
|
10890
|
-
this.#retryAttempt = 0;
|
|
10891
|
-
}
|
|
10974
|
+
await this.#emitSessionEvent({
|
|
10975
|
+
type: "auto_retry_end",
|
|
10976
|
+
success: false,
|
|
10977
|
+
attempt: this.#retryAttempt > 0 ? this.#retryAttempt : attempts,
|
|
10978
|
+
finalError,
|
|
10979
|
+
});
|
|
10980
|
+
this.#clearPendingRecoveredRetryErrors();
|
|
10981
|
+
this.#retryAttempt = 0;
|
|
10892
10982
|
this.#resolveRetry();
|
|
10893
10983
|
// Tool-use orphans corrupt Anthropic message history (tool_result without
|
|
10894
10984
|
// matching tool_use). Always remove them even when the retry cap is hit.
|
|
@@ -16,7 +16,6 @@
|
|
|
16
16
|
|
|
17
17
|
import { countTokens } from "@oh-my-pi/pi-agent-core";
|
|
18
18
|
import type { Context, ImageContent, Model, TextContent, ToolResultMessage, UserMessage } from "@oh-my-pi/pi-ai";
|
|
19
|
-
import { isPersonalGitHubCopilotBaseUrl } from "@oh-my-pi/pi-catalog/wire/github-copilot";
|
|
20
19
|
import * as snapcompact from "@oh-my-pi/snapcompact";
|
|
21
20
|
import contextFramesNote from "../prompts/system/snapcompact-context-frames-note.md" with { type: "text" };
|
|
22
21
|
import contextStub from "../prompts/system/snapcompact-context-stub.md" with { type: "text" };
|
|
@@ -78,19 +77,6 @@ function passesSavingsGate(frames: number, shape: snapcompact.Shape, textTokens:
|
|
|
78
77
|
return frames * shape.frameTokenEstimate <= textTokens * SAVINGS_MARGIN;
|
|
79
78
|
}
|
|
80
79
|
|
|
81
|
-
/**
|
|
82
|
-
* The model is vision-capable for the endpoint we're actually about to hit.
|
|
83
|
-
* GitHub Copilot business and enterprise hosts respond `400 vision is not
|
|
84
|
-
* supported` on image inputs (issue #3387), so even if a stale cached spec
|
|
85
|
-
* still advertises `["text","image"]` we MUST not rasterize transcripts when
|
|
86
|
-
* the resolved `baseUrl` is non-personal.
|
|
87
|
-
*/
|
|
88
|
-
function canSendImages(model: Model): boolean {
|
|
89
|
-
if (!model.input.includes("image")) return false;
|
|
90
|
-
if (model.provider === "github-copilot" && !isPersonalGitHubCopilotBaseUrl(model.baseUrl)) return false;
|
|
91
|
-
return true;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
80
|
interface SystemPromptImageTarget {
|
|
95
81
|
scope: Exclude<SnapcompactSystemPromptMode, "none">;
|
|
96
82
|
text: string;
|
|
@@ -291,7 +277,7 @@ export function estimateInlineSavings(input: {
|
|
|
291
277
|
messages: readonly InlineMessageView[];
|
|
292
278
|
}): SnapcompactSavingsEstimate {
|
|
293
279
|
const { options, model } = input;
|
|
294
|
-
if (!model
|
|
280
|
+
if (!model?.input.includes("image")) {
|
|
295
281
|
return { visionCapable: false, savedTokens: 0 };
|
|
296
282
|
}
|
|
297
283
|
|
|
@@ -430,10 +416,8 @@ export class SnapcompactInlineTransformer {
|
|
|
430
416
|
|
|
431
417
|
async transform(context: Context, model: Model): Promise<Context> {
|
|
432
418
|
// Vision gate: providers silently DROP images on text-only models —
|
|
433
|
-
// rendering would lose the content entirely.
|
|
434
|
-
|
|
435
|
-
// (issue #3387: Copilot business endpoint).
|
|
436
|
-
if (!canSendImages(model)) return context;
|
|
419
|
+
// rendering would lose the content entirely.
|
|
420
|
+
if (!model.input.includes("image")) return context;
|
|
437
421
|
|
|
438
422
|
const shape = snapcompact.resolveShape(model, this.options.shape);
|
|
439
423
|
const budget = snapcompact.providerImageBudget(model.provider) - countContextImages(context);
|