@groeponline/pi-wishcraft 0.17.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/AGENTS.md +68 -0
- package/CHANGELOG.md +724 -0
- package/CONTRIBUTING.md +37 -0
- package/README.md +648 -0
- package/RELEASE.md +117 -0
- package/ROADMAP.md +52 -0
- package/bash-mode/completion-providers.ts +269 -0
- package/bash-mode/completion.ts +416 -0
- package/bash-mode/editor-ghost.ts +40 -0
- package/bash-mode/editor-input.ts +80 -0
- package/bash-mode/editor.ts +437 -0
- package/bash-mode/history.ts +263 -0
- package/bash-mode/shell-session.ts +286 -0
- package/bash-mode/transcript.ts +108 -0
- package/bash-mode/types.ts +80 -0
- package/index.ts +6 -0
- package/package.json +55 -0
- package/queue/store.ts +443 -0
- package/queue/types.ts +54 -0
- package/src/config/custom-items.ts +182 -0
- package/src/config/extension-statuses.ts +51 -0
- package/src/config/layout.ts +60 -0
- package/src/config/parse.ts +127 -0
- package/src/config/powerline-config.ts +18 -0
- package/src/config/presets.ts +245 -0
- package/src/config/primitives.ts +117 -0
- package/src/config/segment-ids.ts +114 -0
- package/src/config/segment-options.ts +128 -0
- package/src/config/settings-patch.ts +26 -0
- package/src/config/types.ts +277 -0
- package/src/core/frontmatter.ts +40 -0
- package/src/editor/autocomplete-chain.ts +41 -0
- package/src/extension/activate.ts +28 -0
- package/src/extension/bash-mode-actions.ts +104 -0
- package/src/extension/commands.ts +268 -0
- package/src/extension/constants.ts +46 -0
- package/src/extension/custom-editor.ts +406 -0
- package/src/extension/git-invalidation.ts +40 -0
- package/src/extension/layout.ts +160 -0
- package/src/extension/menu-views.ts +393 -0
- package/src/extension/powerline-widgets.ts +95 -0
- package/src/extension/prompt-history.ts +219 -0
- package/src/extension/queue-commands.ts +245 -0
- package/src/extension/queue-context.ts +12 -0
- package/src/extension/queue-integration.ts +434 -0
- package/src/extension/segment-context.ts +212 -0
- package/src/extension/session-lifecycle.ts +373 -0
- package/src/extension/settings-io.ts +202 -0
- package/src/extension/shortcuts-config.ts +357 -0
- package/src/extension/shortcuts-router.ts +383 -0
- package/src/extension/skills/inline-invocation.ts +174 -0
- package/src/extension/skills/ook.md +6 -0
- package/src/extension/skills/test.md +6 -0
- package/src/extension/stale-context.ts +10 -0
- package/src/extension/stash-history.ts +103 -0
- package/src/extension/state.ts +159 -0
- package/src/extension/status-line-renderers.ts +222 -0
- package/src/extension/types.ts +97 -0
- package/src/extension/vibe-command.ts +160 -0
- package/src/extension/welcome-control.ts +27 -0
- package/src/extension/welcome-integration.ts +153 -0
- package/src/git/status.ts +332 -0
- package/src/paths/agent-dirs.ts +67 -0
- package/src/render/timer.ts +46 -0
- package/src/segments/core.ts +256 -0
- package/src/segments/custom.ts +114 -0
- package/src/segments/index.ts +3 -0
- package/src/segments/registry.ts +87 -0
- package/src/segments/shared.ts +36 -0
- package/src/segments/system.ts +235 -0
- package/src/segments/usage.ts +178 -0
- package/src/shell/cd-command.ts +190 -0
- package/src/shortcuts/matching.ts +61 -0
- package/src/theme/colors.ts +60 -0
- package/src/theme/icons.ts +175 -0
- package/src/theme/separators.ts +41 -0
- package/src/theme/theme.ts +211 -0
- package/src/tools/graph.ts +75 -0
- package/src/tools/patch.ts +179 -0
- package/src/tools/ripgrep.ts +104 -0
- package/src/usage/context.ts +97 -0
- package/src/usage/ledger.ts +293 -0
- package/src/usage/rates.ts +155 -0
- package/src/welcome/auto-dismiss.ts +43 -0
- package/src/welcome/banner.ts +68 -0
- package/src/welcome/discover.ts +234 -0
- package/src/welcome/format.ts +18 -0
- package/src/welcome/index.ts +5 -0
- package/src/welcome/layout.ts +36 -0
- package/src/welcome/overlay.ts +80 -0
- package/src/welcome/renderer.ts +157 -0
- package/src/welcome/sessions.ts +107 -0
- package/src/welcome/types.ts +41 -0
- package/src/welcome/widgets/graph-widget.ts +25 -0
- package/src/welcome/widgets/index.ts +20 -0
- package/src/welcome/widgets/queue-widget.ts +26 -0
- package/src/welcome/widgets/sessions-widget.ts +23 -0
- package/src/welcome/widgets/shortcuts-widget.ts +17 -0
- package/src/welcome/widgets/system-widget.ts +29 -0
- package/src/working-vibes/generate.ts +144 -0
- package/src/working-vibes/index.ts +24 -0
- package/src/working-vibes/manager.ts +198 -0
- package/src/working-vibes/provider.ts +163 -0
- package/src/working-vibes/storage.ts +357 -0
- package/theme.example.json +24 -0
- package/tsconfig.json +13 -0
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ReadonlyFooterDataProvider,
|
|
3
|
+
Theme,
|
|
4
|
+
} from "@earendil-works/pi-coding-agent";
|
|
5
|
+
|
|
6
|
+
import type { SegmentContext, ColorScheme } from "../config/types.ts";
|
|
7
|
+
import { getPreset } from "../config/presets.ts";
|
|
8
|
+
import {
|
|
9
|
+
collectHiddenExtensionStatusKeys,
|
|
10
|
+
mergeSegmentOptions,
|
|
11
|
+
} from "../config/powerline-config.ts";
|
|
12
|
+
import { getDefaultColors } from "../theme/theme.ts";
|
|
13
|
+
import { getGitStatus } from "../git/status.ts";
|
|
14
|
+
import { getQueueContext } from "./queue-context.ts";
|
|
15
|
+
import { getUsageTokenTotal } from "../usage/ledger.ts";
|
|
16
|
+
import {
|
|
17
|
+
CUSTOM_COMPACTION_STATUS_KEY,
|
|
18
|
+
EDITOR_STATUS_DEFER_MS,
|
|
19
|
+
} from "./constants.ts";
|
|
20
|
+
import {
|
|
21
|
+
config,
|
|
22
|
+
customCompactionEnabled,
|
|
23
|
+
} from "./state.ts";
|
|
24
|
+
import type { RuntimeState } from "./types.ts";
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Reset the cached layout + underlying session-scan caches. Called on
|
|
28
|
+
* session boundaries and whenever config that affects layout changes.
|
|
29
|
+
*/
|
|
30
|
+
export function resetLayoutCache(rt: RuntimeState): void {
|
|
31
|
+
rt.lastLayoutResult = null;
|
|
32
|
+
rt.layoutDirty = true;
|
|
33
|
+
rt.sessionBranchCache.reset();
|
|
34
|
+
rt.tokenStatsCache.reset();
|
|
35
|
+
rt.coreContextUsageCache.reset();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function requestStatusRender(rt: RuntimeState, delayMs?: number): void {
|
|
39
|
+
rt.layoutDirty = true;
|
|
40
|
+
rt.statusRenderScheduler.schedule(delayMs);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function requestImmediateStatusRender(
|
|
44
|
+
rt: RuntimeState,
|
|
45
|
+
options: { deferDuringTyping?: boolean } = {},
|
|
46
|
+
): void {
|
|
47
|
+
rt.layoutDirty = true;
|
|
48
|
+
if (
|
|
49
|
+
options.deferDuringTyping !== false &&
|
|
50
|
+
Date.now() - rt.lastEditorInputAt < EDITOR_STATUS_DEFER_MS
|
|
51
|
+
) {
|
|
52
|
+
rt.statusRenderScheduler.schedule();
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
rt.forceNextLayoutRecompute = true;
|
|
57
|
+
rt.statusRenderScheduler.cancel();
|
|
58
|
+
rt.statusRenderScheduler.schedule(0);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function installFooterStatusRepaintHook(
|
|
62
|
+
rt: RuntimeState,
|
|
63
|
+
footerData: ReadonlyFooterDataProvider,
|
|
64
|
+
): void {
|
|
65
|
+
rt.restoreFooterStatusRepaintHook?.();
|
|
66
|
+
rt.restoreFooterStatusRepaintHook = null;
|
|
67
|
+
|
|
68
|
+
const writableFooterData = footerData as ReadonlyFooterDataProvider & {
|
|
69
|
+
setExtensionStatus?: (key: string, text: string | undefined) => void;
|
|
70
|
+
clearExtensionStatuses?: () => void;
|
|
71
|
+
};
|
|
72
|
+
if (typeof writableFooterData.setExtensionStatus !== "function") return;
|
|
73
|
+
|
|
74
|
+
const originalSetExtensionStatus = writableFooterData.setExtensionStatus;
|
|
75
|
+
const originalClearExtensionStatuses =
|
|
76
|
+
writableFooterData.clearExtensionStatuses;
|
|
77
|
+
const setExtensionStatusAndRepaint = function setExtensionStatusAndRepaint(
|
|
78
|
+
this: unknown,
|
|
79
|
+
key: string,
|
|
80
|
+
text: string | undefined,
|
|
81
|
+
) {
|
|
82
|
+
originalSetExtensionStatus.call(this, key, text);
|
|
83
|
+
requestImmediateStatusRender(rt);
|
|
84
|
+
};
|
|
85
|
+
writableFooterData.setExtensionStatus = setExtensionStatusAndRepaint;
|
|
86
|
+
|
|
87
|
+
let clearExtensionStatusesAndRepaint: (() => void) | null = null;
|
|
88
|
+
if (typeof originalClearExtensionStatuses === "function") {
|
|
89
|
+
clearExtensionStatusesAndRepaint =
|
|
90
|
+
function clearExtensionStatusesAndRepaint(this: unknown) {
|
|
91
|
+
originalClearExtensionStatuses.call(this);
|
|
92
|
+
requestImmediateStatusRender(rt);
|
|
93
|
+
};
|
|
94
|
+
writableFooterData.clearExtensionStatuses =
|
|
95
|
+
clearExtensionStatusesAndRepaint;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
rt.restoreFooterStatusRepaintHook = () => {
|
|
99
|
+
if (
|
|
100
|
+
writableFooterData.setExtensionStatus === setExtensionStatusAndRepaint
|
|
101
|
+
) {
|
|
102
|
+
writableFooterData.setExtensionStatus = originalSetExtensionStatus;
|
|
103
|
+
}
|
|
104
|
+
if (
|
|
105
|
+
clearExtensionStatusesAndRepaint &&
|
|
106
|
+
writableFooterData.clearExtensionStatuses ===
|
|
107
|
+
clearExtensionStatusesAndRepaint
|
|
108
|
+
) {
|
|
109
|
+
writableFooterData.clearExtensionStatuses =
|
|
110
|
+
originalClearExtensionStatuses;
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function buildSegmentContext(
|
|
116
|
+
rt: RuntimeState,
|
|
117
|
+
ctx: any,
|
|
118
|
+
theme: Theme,
|
|
119
|
+
): SegmentContext {
|
|
120
|
+
const presetDef = getPreset(config.preset);
|
|
121
|
+
const colors: ColorScheme = presetDef.colors ?? getDefaultColors();
|
|
122
|
+
|
|
123
|
+
// Build usage stats and get thinking level from session (cached; the full
|
|
124
|
+
// event list is only re-scanned when events are appended or the trailing
|
|
125
|
+
// event's stats-relevant fields change, e.g. in-place streaming updates)
|
|
126
|
+
const sessionEvents = rt.sessionBranchCache.get(ctx.sessionManager);
|
|
127
|
+
const tokenStats = rt.tokenStatsCache.get(sessionEvents);
|
|
128
|
+
const { input, output, cacheRead, cacheWrite, cost, subagentCost } =
|
|
129
|
+
tokenStats;
|
|
130
|
+
const lastAssistant = tokenStats.lastAssistant;
|
|
131
|
+
const thinkingLevelFromSession = tokenStats.thinkingLevelFromSession;
|
|
132
|
+
|
|
133
|
+
// Calculate context percentage.
|
|
134
|
+
const latestUsage = rt.isStreaming
|
|
135
|
+
? (rt.liveAssistantUsage ?? lastAssistant?.usage)
|
|
136
|
+
: lastAssistant?.usage;
|
|
137
|
+
const coreContextUsage =
|
|
138
|
+
rt.isStreaming && rt.liveAssistantUsage
|
|
139
|
+
? null
|
|
140
|
+
: rt.coreContextUsageCache.get(ctx);
|
|
141
|
+
const contextTokens =
|
|
142
|
+
coreContextUsage?.contextTokens ??
|
|
143
|
+
(latestUsage ? getUsageTokenTotal(latestUsage) : 0);
|
|
144
|
+
const contextWindow =
|
|
145
|
+
coreContextUsage?.contextWindow ?? ctx.model?.contextWindow ?? 0;
|
|
146
|
+
const contextPercent =
|
|
147
|
+
coreContextUsage?.contextPercent ??
|
|
148
|
+
(contextWindow > 0 ? (contextTokens / contextWindow) * 100 : 0);
|
|
149
|
+
|
|
150
|
+
const segmentOptions = mergeSegmentOptions(
|
|
151
|
+
presetDef.segmentOptions,
|
|
152
|
+
config.segmentOptions,
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
// Get git status (cached)
|
|
156
|
+
const gitBranch = rt.footerDataRef?.getGitBranch() ?? null;
|
|
157
|
+
const gitStatus = getGitStatus(gitBranch, segmentOptions.git?.polling);
|
|
158
|
+
const extensionStatuses =
|
|
159
|
+
rt.footerDataRef?.getExtensionStatuses() ?? new Map();
|
|
160
|
+
const customItemsById = new Map(
|
|
161
|
+
config.customItems.map((item) => [item.id, item]),
|
|
162
|
+
);
|
|
163
|
+
const hiddenExtensionStatusKeys = collectHiddenExtensionStatusKeys(
|
|
164
|
+
config.customItems,
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
// Check if using OAuth subscription
|
|
168
|
+
const usingSubscription = ctx.model
|
|
169
|
+
? (ctx.modelRegistry?.isUsingOAuth?.(ctx.model) ?? false)
|
|
170
|
+
: false;
|
|
171
|
+
|
|
172
|
+
const thinkingLevel =
|
|
173
|
+
rt.currentThinkingLevel ??
|
|
174
|
+
thinkingLevelFromSession ??
|
|
175
|
+
rt.getThinkingLevelFn?.() ??
|
|
176
|
+
"off";
|
|
177
|
+
const queueSummary = rt.queueStore.summarize(
|
|
178
|
+
getQueueContext(ctx),
|
|
179
|
+
rt.powerlineCompacting,
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
return {
|
|
183
|
+
model: ctx.model,
|
|
184
|
+
thinkingLevel,
|
|
185
|
+
sessionId: ctx.sessionManager?.getSessionId?.(),
|
|
186
|
+
cwd: ctx.cwd,
|
|
187
|
+
usageStats: { input, output, cacheRead, cacheWrite, cost, subagentCost },
|
|
188
|
+
contextTokens,
|
|
189
|
+
contextPercent,
|
|
190
|
+
contextWindow,
|
|
191
|
+
autoCompactEnabled:
|
|
192
|
+
ctx.settingsManager?.getCompactionSettings?.()?.enabled ?? true,
|
|
193
|
+
customCompactionEnabled:
|
|
194
|
+
customCompactionEnabled ||
|
|
195
|
+
extensionStatuses.has(CUSTOM_COMPACTION_STATUS_KEY),
|
|
196
|
+
usingSubscription,
|
|
197
|
+
queueSummary,
|
|
198
|
+
sessionStartTime: rt.sessionStartTime,
|
|
199
|
+
shellModeActive: rt.bashModeActive,
|
|
200
|
+
shellRunning: rt.shellSession?.state.running ?? false,
|
|
201
|
+
shellName: rt.shellSession?.state.shellName ?? null,
|
|
202
|
+
shellCwd: rt.shellSession?.state.cwd ?? null,
|
|
203
|
+
git: gitStatus,
|
|
204
|
+
extensionStatuses,
|
|
205
|
+
hiddenExtensionStatusKeys,
|
|
206
|
+
customItemsById,
|
|
207
|
+
options: segmentOptions,
|
|
208
|
+
segmentLabels: new Map(Object.entries(config.segmentLabels)),
|
|
209
|
+
theme,
|
|
210
|
+
colors,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
|
|
3
|
+
import { BashTranscriptStore } from "../../bash-mode/transcript.ts";
|
|
4
|
+
import { BashCompletionEngine } from "../../bash-mode/completion.ts";
|
|
5
|
+
import { parsePowerlineConfig } from "../config/powerline-config.ts";
|
|
6
|
+
import { registerCustomSegments } from "../segments/index.ts";
|
|
7
|
+
import { registerCustomPresets } from "../config/presets.ts";
|
|
8
|
+
import { invalidateGitStatus } from "../git/status.ts";
|
|
9
|
+
import { invalidateGitForCommand } from "./git-invalidation.ts";
|
|
10
|
+
import {
|
|
11
|
+
initVibeManager,
|
|
12
|
+
onVibeAgentEnd,
|
|
13
|
+
onVibeAgentStart,
|
|
14
|
+
onVibeBeforeAgentStart,
|
|
15
|
+
onVibeToolCall,
|
|
16
|
+
} from "../working-vibes/index.ts";
|
|
17
|
+
import {
|
|
18
|
+
getUsageTokenTotal,
|
|
19
|
+
hasSessionAssistantUsage,
|
|
20
|
+
isSessionAssistantMessage,
|
|
21
|
+
} from "../usage/ledger.ts";
|
|
22
|
+
import {
|
|
23
|
+
detectCustomCompactionEnabled,
|
|
24
|
+
readSettings,
|
|
25
|
+
} from "./settings-io.ts";
|
|
26
|
+
import {
|
|
27
|
+
resolveShortcutConfig,
|
|
28
|
+
parseBashModeSettings,
|
|
29
|
+
} from "./shortcuts-config.ts";
|
|
30
|
+
import { warnInvalidSegmentSettings } from "./layout.ts";
|
|
31
|
+
import { readPersistedStashHistory } from "./stash-history.ts";
|
|
32
|
+
import {
|
|
33
|
+
requestImmediateStatusRender,
|
|
34
|
+
requestStatusRender,
|
|
35
|
+
resetLayoutCache,
|
|
36
|
+
} from "./segment-context.ts";
|
|
37
|
+
import { getQueueContext } from "./queue-context.ts";
|
|
38
|
+
import {
|
|
39
|
+
requestQueueRender,
|
|
40
|
+
schedulePostCompactionDelivery,
|
|
41
|
+
finishFailedCompaction,
|
|
42
|
+
} from "./queue-integration.ts";
|
|
43
|
+
import { setupCustomEditor } from "./custom-editor.ts";
|
|
44
|
+
import {
|
|
45
|
+
setupWelcomeHeader,
|
|
46
|
+
setupWelcomeOverlay,
|
|
47
|
+
} from "./welcome-integration.ts";
|
|
48
|
+
import {
|
|
49
|
+
config,
|
|
50
|
+
PRESET_NAMES,
|
|
51
|
+
setConfig,
|
|
52
|
+
setCustomCompactionEnabled,
|
|
53
|
+
} from "./state.ts";
|
|
54
|
+
import { CONTEXT_STATUS_RENDER_MS } from "./constants.ts";
|
|
55
|
+
import type { RuntimeState } from "./types.ts";
|
|
56
|
+
import { isStaleExtensionContextError } from "./stale-context.ts";
|
|
57
|
+
import {
|
|
58
|
+
dismissWelcome,
|
|
59
|
+
scheduleDismissWelcome,
|
|
60
|
+
} from "./welcome-control.ts";
|
|
61
|
+
|
|
62
|
+
// Helper to extract recent agent response text (skipping thinking blocks)
|
|
63
|
+
function getRecentAgentContext(ctx: any): string | undefined {
|
|
64
|
+
const sessionEvents = ctx.sessionManager?.getBranch?.() ?? [];
|
|
65
|
+
|
|
66
|
+
// Find the most recent assistant message
|
|
67
|
+
for (let i = sessionEvents.length - 1; i >= 0; i--) {
|
|
68
|
+
const e = sessionEvents[i];
|
|
69
|
+
if (e.type === "message" && e.message?.role === "assistant") {
|
|
70
|
+
const content = e.message.content;
|
|
71
|
+
if (!Array.isArray(content)) continue;
|
|
72
|
+
|
|
73
|
+
// Extract text content, skip thinking blocks
|
|
74
|
+
for (const block of content) {
|
|
75
|
+
if (block.type === "text" && block.text) {
|
|
76
|
+
// Return first ~200 chars of non-empty text
|
|
77
|
+
const text = block.text.trim();
|
|
78
|
+
if (text.length > 0) {
|
|
79
|
+
return text.slice(0, 200);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return undefined;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function shouldShowStartupWelcome(reason: unknown, welcomeEnabled: boolean): boolean {
|
|
89
|
+
return reason === "startup" && welcomeEnabled;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function registerSessionLifecycle(
|
|
93
|
+
pi: ExtensionAPI,
|
|
94
|
+
rt: RuntimeState,
|
|
95
|
+
): void {
|
|
96
|
+
// Track session start
|
|
97
|
+
pi.on("session_start", async (event, ctx) => {
|
|
98
|
+
rt.shellSession?.dispose();
|
|
99
|
+
rt.shellSession = null;
|
|
100
|
+
rt.sessionGeneration++;
|
|
101
|
+
rt.sessionStartTime = Date.now();
|
|
102
|
+
rt.currentCtx = ctx;
|
|
103
|
+
setCustomCompactionEnabled(detectCustomCompactionEnabled(ctx.cwd));
|
|
104
|
+
rt.lastUserPrompt = "";
|
|
105
|
+
rt.isStreaming = false;
|
|
106
|
+
rt.liveAssistantUsage = null;
|
|
107
|
+
rt.powerlineCompacting = false;
|
|
108
|
+
rt.deliverAfterRetrySettles = false;
|
|
109
|
+
rt.stashedEditorText = null;
|
|
110
|
+
|
|
111
|
+
const settings = readSettings(ctx.cwd);
|
|
112
|
+
rt.resolvedShortcuts = resolveShortcutConfig(settings);
|
|
113
|
+
rt.bashModeSettings = parseBashModeSettings(settings, rt.resolvedShortcuts);
|
|
114
|
+
rt.showLastPrompt = settings.showLastPrompt !== false;
|
|
115
|
+
setConfig(parsePowerlineConfig(settings.powerline, PRESET_NAMES));
|
|
116
|
+
registerCustomSegments(config.segments);
|
|
117
|
+
registerCustomPresets(config.presets);
|
|
118
|
+
warnInvalidSegmentSettings(ctx);
|
|
119
|
+
rt.stashedPromptHistory = readPersistedStashHistory();
|
|
120
|
+
rt.bashModeActive = false;
|
|
121
|
+
rt.bashTranscript = new BashTranscriptStore(rt.bashModeSettings);
|
|
122
|
+
rt.bashCompletionEngine = new BashCompletionEngine();
|
|
123
|
+
|
|
124
|
+
rt.getThinkingLevelFn = () => ctx.thinkingLevel ?? "off";
|
|
125
|
+
rt.currentThinkingLevel = rt.getThinkingLevelFn();
|
|
126
|
+
|
|
127
|
+
if (ctx.hasUI) {
|
|
128
|
+
ctx.ui.setStatus("stash", undefined);
|
|
129
|
+
const pendingIdeas = rt.queueStore
|
|
130
|
+
.activeItems(getQueueContext(ctx))
|
|
131
|
+
.filter((item) => item.intent === "idea").length;
|
|
132
|
+
if (pendingIdeas > 0) {
|
|
133
|
+
ctx.ui.notify(
|
|
134
|
+
`${pendingIdeas} idea${pendingIdeas === 1 ? "" : "s"} waiting — /ideas`,
|
|
135
|
+
"info",
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Initialize vibe manager (needs modelRegistry from ctx)
|
|
141
|
+
initVibeManager(ctx);
|
|
142
|
+
|
|
143
|
+
if (rt.enabled && ctx.hasUI) {
|
|
144
|
+
setupCustomEditor(pi, rt, ctx);
|
|
145
|
+
if (shouldShowStartupWelcome(event.reason, config.welcome)) {
|
|
146
|
+
if (settings.quietStartup === true) {
|
|
147
|
+
setupWelcomeHeader(rt, ctx);
|
|
148
|
+
} else {
|
|
149
|
+
setupWelcomeOverlay(rt, ctx);
|
|
150
|
+
}
|
|
151
|
+
} else {
|
|
152
|
+
dismissWelcome(rt, ctx);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
pi.on("session_shutdown", async (_event, ctx) => {
|
|
158
|
+
rt.sessionGeneration++;
|
|
159
|
+
rt.dismissWelcomeOverlay?.();
|
|
160
|
+
rt.dismissWelcomeOverlay = null;
|
|
161
|
+
rt.welcomeHeaderActive = false;
|
|
162
|
+
rt.welcomeOverlayShouldDismiss = false;
|
|
163
|
+
rt.welcomeDismissScheduler.cancel();
|
|
164
|
+
rt.statusRenderScheduler.cancel();
|
|
165
|
+
rt.restoreFooterStatusRepaintHook?.();
|
|
166
|
+
rt.restoreFooterStatusRepaintHook = null;
|
|
167
|
+
rt.stashShortcutInputUnsubscribe?.();
|
|
168
|
+
rt.stashShortcutInputUnsubscribe = null;
|
|
169
|
+
rt.shellSession?.dispose();
|
|
170
|
+
rt.shellSession = null;
|
|
171
|
+
if (rt.queueDeliveryTimer) {
|
|
172
|
+
clearTimeout(rt.queueDeliveryTimer);
|
|
173
|
+
rt.queueDeliveryTimer = null;
|
|
174
|
+
}
|
|
175
|
+
rt.powerlineCompacting = false;
|
|
176
|
+
rt.deliverAfterRetrySettles = false;
|
|
177
|
+
rt.bashModeActive = false;
|
|
178
|
+
rt.currentCtx = null;
|
|
179
|
+
rt.footerDataRef = null;
|
|
180
|
+
rt.getThinkingLevelFn = null;
|
|
181
|
+
rt.currentThinkingLevel = null;
|
|
182
|
+
rt.liveAssistantUsage = null;
|
|
183
|
+
rt.tuiRef = null;
|
|
184
|
+
rt.currentEditor = null;
|
|
185
|
+
resetLayoutCache(rt);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
// Invalidate git status on file changes, trigger re-render on potential branch changes
|
|
189
|
+
pi.on("tool_result", async (event) => {
|
|
190
|
+
if (event.toolName === "write" || event.toolName === "edit") {
|
|
191
|
+
invalidateGitStatus();
|
|
192
|
+
requestStatusRender(rt);
|
|
193
|
+
}
|
|
194
|
+
// Check for bash commands that might change git branch
|
|
195
|
+
if (event.toolName === "bash" && event.input?.command) {
|
|
196
|
+
invalidateGitForCommand(rt, String(event.input.command));
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
// Also catch user escape commands (! prefix)
|
|
201
|
+
// Note: This fires BEFORE execution, so we use a longer delay and multiple re-renders
|
|
202
|
+
// to ensure we catch the update after the command completes.
|
|
203
|
+
pi.on("user_bash", async (event) => {
|
|
204
|
+
invalidateGitForCommand(rt, event.command, { stagger: true });
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
pi.on("model_select", async (_event, ctx) => {
|
|
208
|
+
rt.currentCtx = ctx;
|
|
209
|
+
rt.coreContextUsageCache.reset();
|
|
210
|
+
requestStatusRender(rt);
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
pi.on("thinking_level_select", async (event, ctx) => {
|
|
214
|
+
rt.currentCtx = ctx;
|
|
215
|
+
rt.currentThinkingLevel =
|
|
216
|
+
rt.getThinkingLevelFn?.() ??
|
|
217
|
+
(typeof event.level === "string" ? event.level : null);
|
|
218
|
+
requestImmediateStatusRender(rt, { deferDuringTyping: false });
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
pi.on("session_tree", async (_event, ctx) => {
|
|
222
|
+
rt.currentCtx = ctx;
|
|
223
|
+
rt.currentThinkingLevel = null;
|
|
224
|
+
rt.liveAssistantUsage = null;
|
|
225
|
+
requestImmediateStatusRender(rt, { deferDuringTyping: false });
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
// Generate themed working message before agent starts (has access to user's prompt)
|
|
229
|
+
pi.on("before_agent_start", async (event, ctx) => {
|
|
230
|
+
rt.lastUserPrompt = event.prompt;
|
|
231
|
+
if (ctx.hasUI) {
|
|
232
|
+
onVibeBeforeAgentStart(event.prompt, ctx.ui.setWorkingMessage);
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
// Track streaming state (footer only shows status during streaming)
|
|
237
|
+
// Also dismiss welcome when agent starts responding (handles `p "command"` case)
|
|
238
|
+
pi.on("agent_start", async (_event, ctx) => {
|
|
239
|
+
rt.isStreaming = true;
|
|
240
|
+
rt.liveAssistantUsage = null;
|
|
241
|
+
onVibeAgentStart();
|
|
242
|
+
dismissWelcome(rt, ctx);
|
|
243
|
+
rt.currentCtx = ctx;
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
pi.on("message_update", async (event, ctx) => {
|
|
247
|
+
if (
|
|
248
|
+
isSessionAssistantMessage(event.message) &&
|
|
249
|
+
event.message.stopReason !== "error" &&
|
|
250
|
+
event.message.stopReason !== "aborted" &&
|
|
251
|
+
getUsageTokenTotal(event.message.usage) > 0
|
|
252
|
+
) {
|
|
253
|
+
rt.liveAssistantUsage = event.message.usage;
|
|
254
|
+
rt.currentCtx = ctx;
|
|
255
|
+
rt.layoutDirty = true;
|
|
256
|
+
rt.statusRenderScheduler.schedule(CONTEXT_STATUS_RENDER_MS);
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
pi.on("message_end", async (event, ctx) => {
|
|
261
|
+
rt.currentCtx = ctx;
|
|
262
|
+
rt.coreContextUsageCache.reset();
|
|
263
|
+
if (isSessionAssistantMessage(event.message)) {
|
|
264
|
+
if (
|
|
265
|
+
event.message.stopReason === "error" ||
|
|
266
|
+
event.message.stopReason === "aborted"
|
|
267
|
+
) {
|
|
268
|
+
rt.liveAssistantUsage = null;
|
|
269
|
+
} else if (getUsageTokenTotal(event.message.usage) > 0) {
|
|
270
|
+
rt.liveAssistantUsage = event.message.usage;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
requestImmediateStatusRender(rt, { deferDuringTyping: false });
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
pi.on("turn_end", async (_event, ctx) => {
|
|
277
|
+
rt.currentCtx = ctx;
|
|
278
|
+
rt.coreContextUsageCache.reset();
|
|
279
|
+
requestImmediateStatusRender(rt, { deferDuringTyping: false });
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
pi.on("session_before_compact", async (_event, ctx) => {
|
|
283
|
+
rt.powerlineCompacting = true;
|
|
284
|
+
rt.currentCtx = ctx;
|
|
285
|
+
requestQueueRender(rt);
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
pi.on("session_compact", async (event, ctx) => {
|
|
289
|
+
rt.powerlineCompacting = false;
|
|
290
|
+
rt.currentCtx = ctx;
|
|
291
|
+
// Compaction rewrites the conversation, so the cached context-usage (tokens/window/percent)
|
|
292
|
+
// is stale. Reset it and force a redraw — otherwise the bar keeps showing the pre-compact fill.
|
|
293
|
+
rt.coreContextUsageCache.reset();
|
|
294
|
+
requestImmediateStatusRender(rt, { deferDuringTyping: false });
|
|
295
|
+
if (event.willRetry) {
|
|
296
|
+
rt.deliverAfterRetrySettles = true;
|
|
297
|
+
} else {
|
|
298
|
+
rt.deliverAfterRetrySettles = false;
|
|
299
|
+
schedulePostCompactionDelivery(pi, rt, ctx);
|
|
300
|
+
}
|
|
301
|
+
requestQueueRender(rt);
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
pi.on("agent_settled", async (_event, ctx) => {
|
|
305
|
+
if (rt.powerlineCompacting) {
|
|
306
|
+
finishFailedCompaction(rt, ctx, "Compaction did not complete");
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
if (rt.deliverAfterRetrySettles) {
|
|
310
|
+
rt.deliverAfterRetrySettles = false;
|
|
311
|
+
schedulePostCompactionDelivery(pi, rt, ctx);
|
|
312
|
+
}
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
// Also dismiss on tool calls (agent is working) + refresh vibe if rate limit allows
|
|
316
|
+
pi.on("tool_call", async (event, ctx) => {
|
|
317
|
+
dismissWelcome(rt, ctx);
|
|
318
|
+
if (ctx.hasUI) {
|
|
319
|
+
// Extract recent agent context from session for richer vibe generation
|
|
320
|
+
const agentContext = getRecentAgentContext(ctx);
|
|
321
|
+
onVibeToolCall(
|
|
322
|
+
event.toolName,
|
|
323
|
+
event.input,
|
|
324
|
+
ctx.ui.setWorkingMessage,
|
|
325
|
+
agentContext,
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
pi.on("agent_end", async (_event, ctx) => {
|
|
331
|
+
rt.isStreaming = false;
|
|
332
|
+
rt.liveAssistantUsage = null;
|
|
333
|
+
rt.coreContextUsageCache.reset();
|
|
334
|
+
|
|
335
|
+
let hasUI = false;
|
|
336
|
+
try {
|
|
337
|
+
hasUI = Boolean(ctx.hasUI);
|
|
338
|
+
} catch (error) {
|
|
339
|
+
if (!isStaleExtensionContextError(error)) throw error;
|
|
340
|
+
rt.currentCtx = null;
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
rt.currentCtx = ctx;
|
|
345
|
+
try {
|
|
346
|
+
if (hasUI) {
|
|
347
|
+
onVibeAgentEnd(ctx.ui.setWorkingMessage); // working-vibes internal state + reset message
|
|
348
|
+
if (rt.stashedEditorText !== null) {
|
|
349
|
+
if (ctx.ui.getEditorText().trim() === "") {
|
|
350
|
+
ctx.ui.setEditorText(rt.stashedEditorText);
|
|
351
|
+
rt.stashedEditorText = null;
|
|
352
|
+
ctx.ui.setStatus("stash", undefined);
|
|
353
|
+
ctx.ui.notify("Stash restored", "info");
|
|
354
|
+
} else {
|
|
355
|
+
ctx.ui.notify(
|
|
356
|
+
"Stash preserved — clear editor then Alt+S to restore",
|
|
357
|
+
"info",
|
|
358
|
+
);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
} catch (error) {
|
|
363
|
+
if (!isStaleExtensionContextError(error)) throw error;
|
|
364
|
+
rt.currentCtx = null;
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
requestStatusRender(rt);
|
|
369
|
+
if (!rt.powerlineCompacting && !rt.deliverAfterRetrySettles) {
|
|
370
|
+
schedulePostCompactionDelivery(pi, rt, ctx);
|
|
371
|
+
}
|
|
372
|
+
});
|
|
373
|
+
}
|