@groeponline/pi-wishcraft 0.20.0 → 0.22.0
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 +17 -0
- package/README.md +83 -0
- package/ROADMAP.md +18 -20
- package/bash-mode/editor-ghost.ts +63 -0
- package/bash-mode/editor-history.ts +81 -0
- package/bash-mode/editor-input.ts +35 -0
- package/bash-mode/editor.ts +16 -134
- package/bash-mode/shell-session.ts +12 -15
- package/bash-mode/types.ts +2 -0
- package/docs/bash-mode.md +46 -0
- package/docs/commands.md +122 -0
- package/docs/configuration.md +307 -0
- package/docs/index.md +18 -0
- package/docs/segments.md +168 -0
- package/docs/skill-manager.md +7 -0
- package/docs/stash-and-shortcuts.md +64 -0
- package/docs/working-vibes.md +71 -0
- package/package.json +2 -1
- package/queue/store.ts +72 -3
- package/src/config/custom-items.ts +45 -0
- package/src/config/parse.ts +19 -3
- package/src/config/powerline-config.ts +5 -0
- package/src/config/primitives.ts +13 -1
- package/src/config/segment-options.ts +29 -24
- package/src/config/settings-patch.ts +23 -2
- package/src/config/types.ts +32 -17
- package/src/extension/commands/bash-mode-actions.ts +1 -0
- package/src/extension/commands/commands.ts +36 -18
- package/src/extension/commands/powerline-completions.ts +8 -0
- package/src/extension/commands/powerline-doctor.ts +247 -0
- package/src/extension/commands/powerline-export.ts +94 -0
- package/src/extension/commands/queue-commands.ts +29 -1
- package/src/extension/commands/vibe-command.ts +49 -1
- package/src/extension/core/constants.ts +1 -0
- package/src/extension/core/segment-context.ts +38 -2
- package/src/extension/core/state.ts +5 -1
- package/src/extension/core/status-export.ts +68 -0
- package/src/extension/core/types.ts +4 -0
- package/src/extension/hooks/repairs.ts +89 -16
- package/src/extension/session/activate.ts +1 -2
- package/src/extension/session/cost-alert.ts +36 -0
- package/src/extension/session/session-lifecycle.ts +82 -1
- package/src/extension/settings/settings-io.ts +23 -17
- package/src/extension/settings/wishcraft-config.ts +2 -1
- package/src/extension/shortcuts/shortcuts-config.ts +5 -0
- package/src/extension/ui/layout.ts +1 -1
- package/src/extension/ui/menu-items.ts +379 -0
- package/src/extension/ui/menu-views.ts +377 -191
- package/src/extension/ui/overlay-chrome.ts +163 -0
- package/src/extension/ui/powerline-menu-view.ts +2 -4
- package/src/extension/ui/token-overlays.ts +82 -0
- package/src/extension/welcome/welcome-integration.ts +5 -9
- package/src/segments/registry.ts +100 -39
- package/src/segments/system.ts +204 -64
- package/src/segments/usage.ts +10 -4
- package/src/usage/ledger.ts +6 -0
- package/src/usage/token-budget.ts +57 -0
- package/src/usage/tps-ring.ts +151 -0
- package/src/usage/usage-store.ts +351 -0
- package/src/welcome/banner.ts +2 -1
- package/src/welcome/index.ts +3 -1
- package/src/welcome/overlay.ts +2 -0
- package/src/welcome/renderer.ts +52 -10
- package/src/welcome/types.ts +1 -2
- package/src/welcome/whats-new.ts +156 -0
- package/src/welcome/widgets/index.ts +3 -0
- package/src/welcome/widgets/whats-new-widget.ts +19 -0
- package/src/working-vibes/generate.ts +144 -58
- package/src/working-vibes/index.ts +10 -2
- package/src/working-vibes/storage.ts +5 -0
|
@@ -41,7 +41,9 @@ export let config: PowerlineConfig = {
|
|
|
41
41
|
invalidPlacement: null,
|
|
42
42
|
welcome: true,
|
|
43
43
|
stashSharpSShortcut: false,
|
|
44
|
-
|
|
44
|
+
costAlert: null,
|
|
45
|
+
customItemsAuto: false,
|
|
46
|
+
queue: { captureSigil: "#", retentionHours: 24 },
|
|
45
47
|
segments: {},
|
|
46
48
|
presets: {},
|
|
47
49
|
segmentLabels: {},
|
|
@@ -109,6 +111,8 @@ export function createRuntimeState(
|
|
|
109
111
|
stashedEditorText: null,
|
|
110
112
|
stashedPromptHistory: readPersistedStashHistory(),
|
|
111
113
|
currentEditor: null,
|
|
114
|
+
costAlertNotified: false,
|
|
115
|
+
tokenBudgetNotifiedLevel: 0,
|
|
112
116
|
bashModeActive: false,
|
|
113
117
|
bashTranscript: new BashTranscriptStore(bashModeSettings),
|
|
114
118
|
bashCompletionEngine: new BashCompletionEngine(),
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
2
|
+
// ChefGroep status bridge. Powerline publishes its own state under a stable
|
|
3
|
+
// `powerline.*` key set via `ctx.ui.setStatus`, so ChefBar and other
|
|
4
|
+
// extensions can read it without depending on powerline internals. These keys
|
|
5
|
+
// are hidden from powerline's own `extension_statuses` segment.
|
|
6
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
7
|
+
|
|
8
|
+
export const POWERLINE_STATUS_KEYS = {
|
|
9
|
+
preset: "powerline.preset",
|
|
10
|
+
tps: "powerline.tps",
|
|
11
|
+
ports: "powerline.ports",
|
|
12
|
+
} as const;
|
|
13
|
+
|
|
14
|
+
/** Keys never rendered in powerline's own bar (they exist for other extensions). */
|
|
15
|
+
export const HIDDEN_POWERLINE_STATUS_KEYS: ReadonlySet<string> = new Set(
|
|
16
|
+
Object.values(POWERLINE_STATUS_KEYS),
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
export interface PowerlineStatusSnapshot {
|
|
20
|
+
/** Active preset name. */
|
|
21
|
+
preset?: string;
|
|
22
|
+
/** `POWERLINE_TPS` override; `undefined` clears it (back to live rate). */
|
|
23
|
+
tps?: string | undefined;
|
|
24
|
+
/** open_ports count as text (`?` when a fleet host probe failed). */
|
|
25
|
+
ports?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
type StatusPublisherCtx = {
|
|
29
|
+
ui?: { setStatus?: (key: string, value: string | undefined) => void };
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Pure: turn a partial snapshot into the exact `[statusKey, value]` writes to
|
|
34
|
+
* make. Only keys present in the snapshot are emitted (using `in` so an
|
|
35
|
+
* explicit `tps: undefined` still produces a clearing write).
|
|
36
|
+
*/
|
|
37
|
+
export function buildPowerlineStatusExport(
|
|
38
|
+
snapshot: PowerlineStatusSnapshot,
|
|
39
|
+
): Array<[string, string | undefined]> {
|
|
40
|
+
const entries: Array<[string, string | undefined]> = [];
|
|
41
|
+
if ("preset" in snapshot) {
|
|
42
|
+
entries.push([POWERLINE_STATUS_KEYS.preset, snapshot.preset]);
|
|
43
|
+
}
|
|
44
|
+
if ("tps" in snapshot) {
|
|
45
|
+
entries.push([POWERLINE_STATUS_KEYS.tps, snapshot.tps]);
|
|
46
|
+
}
|
|
47
|
+
if ("ports" in snapshot) {
|
|
48
|
+
entries.push([POWERLINE_STATUS_KEYS.ports, snapshot.ports]);
|
|
49
|
+
}
|
|
50
|
+
return entries;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Publish only the snapshot keys present, via `ctx.ui.setStatus`. */
|
|
54
|
+
export function publishPowerlineStatuses(
|
|
55
|
+
ctx: StatusPublisherCtx | null | undefined,
|
|
56
|
+
snapshot: PowerlineStatusSnapshot,
|
|
57
|
+
): void {
|
|
58
|
+
const setStatus = ctx?.ui?.setStatus;
|
|
59
|
+
if (!setStatus) return;
|
|
60
|
+
for (const [key, value] of buildPowerlineStatusExport(snapshot)) {
|
|
61
|
+
setStatus(key, value);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Format a listening-port count for the status export (`?` when unknown). */
|
|
66
|
+
export function formatPortsStatusValue(count: number): string {
|
|
67
|
+
return count < 0 ? "?" : String(count);
|
|
68
|
+
}
|
|
@@ -62,6 +62,10 @@ export interface RuntimeState {
|
|
|
62
62
|
stashedEditorText: string | null;
|
|
63
63
|
stashedPromptHistory: string[];
|
|
64
64
|
currentEditor: any;
|
|
65
|
+
/** True once the configured `costAlert` threshold has been notified this session. */
|
|
66
|
+
costAlertNotified: boolean;
|
|
67
|
+
/** Highest daily-budget warning already shown this session (0 / 80 / 100). */
|
|
68
|
+
tokenBudgetNotifiedLevel: 0 | 80 | 100;
|
|
65
69
|
bashModeActive: boolean;
|
|
66
70
|
bashTranscript: BashTranscriptStore;
|
|
67
71
|
bashCompletionEngine: BashCompletionEngine;
|
|
@@ -2,20 +2,74 @@
|
|
|
2
2
|
* repairs.ts
|
|
3
3
|
* ---------------------------------------------------------------------------
|
|
4
4
|
* Tool-input repairs vóór executie (pi's `tool_call` event heeft mutable
|
|
5
|
-
* input).
|
|
6
|
-
*
|
|
7
|
-
* placeholder-object) vereisen de validator-issue-lijst en blijven bewust
|
|
8
|
-
* weg tot Pi die expose't.
|
|
5
|
+
* input). Custom/extension tools only. Order is fixed: json-parse before
|
|
6
|
+
* bare-string wrap so `'["a"]'` becomes `["a"]`, not `[['["a"]']]`.
|
|
9
7
|
* ---------------------------------------------------------------------------
|
|
10
8
|
*/
|
|
11
9
|
|
|
12
10
|
/** Degenerate markdown auto-link: link-tekst == url zonder protocol. */
|
|
13
11
|
const AUTO_LINK_RE = /^\[([^\]\s]+)\]\((https?:\/\/|file:\/\/)?([^)\s]*)\)$/;
|
|
14
12
|
|
|
13
|
+
const CORE_TOOLS = new Set([
|
|
14
|
+
"bash",
|
|
15
|
+
"read",
|
|
16
|
+
"edit",
|
|
17
|
+
"write",
|
|
18
|
+
"grep",
|
|
19
|
+
"find",
|
|
20
|
+
"ls",
|
|
21
|
+
]);
|
|
22
|
+
|
|
23
|
+
const PATH_ALIASES = ["filePath", "absolutePath", "target_file", "file_path"];
|
|
24
|
+
|
|
25
|
+
const ARRAY_KEYS = new Set([
|
|
26
|
+
"files",
|
|
27
|
+
"paths",
|
|
28
|
+
"globs",
|
|
29
|
+
"args",
|
|
30
|
+
"items",
|
|
31
|
+
"patterns",
|
|
32
|
+
"include",
|
|
33
|
+
"exclude",
|
|
34
|
+
"queries",
|
|
35
|
+
"urls",
|
|
36
|
+
"commands",
|
|
37
|
+
]);
|
|
38
|
+
|
|
15
39
|
function isDegenerateAutoLink(linkText: string, url: string): boolean {
|
|
16
40
|
return linkText === url || linkText === decodeURI(url);
|
|
17
41
|
}
|
|
18
42
|
|
|
43
|
+
function isArrayKey(key: string): boolean {
|
|
44
|
+
if (ARRAY_KEYS.has(key)) return true;
|
|
45
|
+
return /_(files|paths|globs|args|items|patterns)$/.test(key);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function looksLikePath(text: string): boolean {
|
|
49
|
+
if (/\s/.test(text)) return false;
|
|
50
|
+
return text.includes("/") || text.includes(".") || /^[a-zA-Z0-9_-]+$/.test(text);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function parseJsonArray(value: string): unknown[] | null {
|
|
54
|
+
const trimmed = value.trim();
|
|
55
|
+
if (!trimmed.startsWith("[")) return null;
|
|
56
|
+
try {
|
|
57
|
+
const parsed = JSON.parse(trimmed);
|
|
58
|
+
return Array.isArray(parsed) ? parsed : null;
|
|
59
|
+
} catch {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function isEmptyObject(value: unknown): boolean {
|
|
65
|
+
return (
|
|
66
|
+
typeof value === "object" &&
|
|
67
|
+
value !== null &&
|
|
68
|
+
!Array.isArray(value) &&
|
|
69
|
+
Object.keys(value as Record<string, unknown>).length === 0
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
19
73
|
export interface RepairResult {
|
|
20
74
|
toolName: string;
|
|
21
75
|
repairs: string[];
|
|
@@ -31,39 +85,58 @@ export function repairToolInput(
|
|
|
31
85
|
input: Record<string, unknown>,
|
|
32
86
|
): RepairResult {
|
|
33
87
|
const repairs: string[] = [];
|
|
34
|
-
|
|
35
|
-
toolName,
|
|
36
|
-
);
|
|
37
|
-
if (isBuiltin || !input || typeof input !== "object") {
|
|
88
|
+
if (CORE_TOOLS.has(toolName) || !input || typeof input !== "object") {
|
|
38
89
|
return { toolName, repairs };
|
|
39
90
|
}
|
|
40
91
|
|
|
92
|
+
if (typeof input.path !== "string" || !input.path) {
|
|
93
|
+
for (const alias of PATH_ALIASES) {
|
|
94
|
+
const value = input[alias];
|
|
95
|
+
if (typeof value === "string" && looksLikePath(value)) {
|
|
96
|
+
input.path = value;
|
|
97
|
+
delete input[alias];
|
|
98
|
+
repairs.push(`path-alias:${alias}`);
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
41
104
|
for (const key of Object.keys(input)) {
|
|
42
105
|
const value = input[key];
|
|
43
|
-
// null-for-optional: model stuurde null, schema wil afwezig
|
|
44
106
|
if (value === null) {
|
|
45
107
|
delete input[key];
|
|
46
108
|
repairs.push(`null-for-optional:${key}`);
|
|
47
109
|
continue;
|
|
48
110
|
}
|
|
49
|
-
// markdown auto-link op pad-achtige velden: [x.md](http://x.md) → x.md
|
|
50
111
|
if (typeof value === "string") {
|
|
51
112
|
const m = AUTO_LINK_RE.exec(value);
|
|
52
113
|
if (m && m[1] && isDegenerateAutoLink(m[1], m[3] ?? "")) {
|
|
53
114
|
input[key] = m[1];
|
|
54
115
|
repairs.push(`auto-link-unwrap:${key}`);
|
|
55
116
|
}
|
|
117
|
+
const current = input[key];
|
|
118
|
+
if (typeof current === "string") {
|
|
119
|
+
const parsed = parseJsonArray(current);
|
|
120
|
+
if (parsed) {
|
|
121
|
+
input[key] = parsed;
|
|
122
|
+
repairs.push(`json-string-array:${key}`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
const afterParse = input[key];
|
|
127
|
+
if (isArrayKey(key) && isEmptyObject(afterParse)) {
|
|
128
|
+
input[key] = [];
|
|
129
|
+
repairs.push(`empty-object-placeholder:${key}`);
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
if (isArrayKey(key) && typeof afterParse === "string") {
|
|
133
|
+
input[key] = [afterParse];
|
|
134
|
+
repairs.push(`bare-string-wrap:${key}`);
|
|
56
135
|
}
|
|
57
136
|
}
|
|
58
137
|
return { toolName, repairs };
|
|
59
138
|
}
|
|
60
139
|
|
|
61
|
-
function looksLikePath(text: string): boolean {
|
|
62
|
-
if (/\s/.test(text)) return false;
|
|
63
|
-
return text.includes("/") || text.includes(".") || /^[a-zA-Z0-9_-]+$/.test(text);
|
|
64
|
-
}
|
|
65
|
-
void looksLikePath;
|
|
66
|
-
|
|
67
140
|
/** Repair-teller per (tool, repair) — zichtbaar via /repairs. */
|
|
68
141
|
const repairCounts = new Map<string, number>();
|
|
69
142
|
|
|
@@ -7,7 +7,6 @@ import { readSettings } from "../settings/settings-io.ts";
|
|
|
7
7
|
import { registerSessionLifecycle } from "./session-lifecycle.ts";
|
|
8
8
|
import { registerCommands } from "../commands/commands.ts";
|
|
9
9
|
import { setupInlineInvocation } from "../skills/inline-invocation.ts";
|
|
10
|
-
import { setupHooks } from "../hooks/index.ts";
|
|
11
10
|
import {
|
|
12
11
|
config,
|
|
13
12
|
createRuntimeState,
|
|
@@ -22,9 +21,9 @@ export default function powerlineFooter(pi: ExtensionAPI) {
|
|
|
22
21
|
registerCustomPresets(config.presets);
|
|
23
22
|
|
|
24
23
|
const rt = createRuntimeState(startupSettings);
|
|
24
|
+
rt.queueStore.setSentRetentionMs(config.queue.retentionHours * 60 * 60 * 1000);
|
|
25
25
|
|
|
26
26
|
registerSessionLifecycle(pi, rt);
|
|
27
27
|
registerCommands(pi, rt);
|
|
28
28
|
setupInlineInvocation(pi, rt);
|
|
29
|
-
setupHooks(pi, rt, process.cwd());
|
|
30
29
|
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { CostCurrencyCode } from "../../usage/rates.ts";
|
|
2
|
+
import { formatUsdCost } from "../../usage/rates.ts";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Decide whether the once-per-session cost alert should fire. Pure and
|
|
6
|
+
* side-effect free so it can be unit-tested without a running session.
|
|
7
|
+
*/
|
|
8
|
+
export function shouldTriggerCostAlert(options: {
|
|
9
|
+
totalCost: number;
|
|
10
|
+
threshold: number | null | undefined;
|
|
11
|
+
alreadyNotified: boolean;
|
|
12
|
+
}): boolean {
|
|
13
|
+
const { totalCost, threshold, alreadyNotified } = options;
|
|
14
|
+
if (alreadyNotified) return false;
|
|
15
|
+
if (
|
|
16
|
+
typeof threshold !== "number" ||
|
|
17
|
+
!Number.isFinite(threshold) ||
|
|
18
|
+
threshold <= 0
|
|
19
|
+
) {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
return totalCost >= threshold;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Format the one-line warning shown when the session crosses the threshold. */
|
|
26
|
+
export function formatCostAlertMessage(
|
|
27
|
+
totalCostUsd: number,
|
|
28
|
+
thresholdUsd: number,
|
|
29
|
+
currency: CostCurrencyCode = "USD",
|
|
30
|
+
): string {
|
|
31
|
+
const total =
|
|
32
|
+
formatUsdCost(totalCostUsd, currency) ?? `$${totalCostUsd.toFixed(2)}`;
|
|
33
|
+
const threshold =
|
|
34
|
+
formatUsdCost(thresholdUsd, currency) ?? `$${thresholdUsd.toFixed(2)}`;
|
|
35
|
+
return `Session cost reached ${total} (alert threshold ${threshold})`;
|
|
36
|
+
}
|
|
@@ -15,10 +15,26 @@ import {
|
|
|
15
15
|
onVibeToolCall,
|
|
16
16
|
} from "../../working-vibes/index.ts";
|
|
17
17
|
import {
|
|
18
|
+
getSessionTotalCost,
|
|
18
19
|
getUsageTokenTotal,
|
|
19
|
-
hasSessionAssistantUsage,
|
|
20
20
|
isSessionAssistantMessage,
|
|
21
21
|
} from "../../usage/ledger.ts";
|
|
22
|
+
import {
|
|
23
|
+
formatCostAlertMessage,
|
|
24
|
+
shouldTriggerCostAlert,
|
|
25
|
+
} from "./cost-alert.ts";
|
|
26
|
+
import {
|
|
27
|
+
formatTokenBudgetWarning,
|
|
28
|
+
parseTokenBudget,
|
|
29
|
+
tokenBudgetLevel,
|
|
30
|
+
} from "../../usage/token-budget.ts";
|
|
31
|
+
import {
|
|
32
|
+
recordUsageEvent,
|
|
33
|
+
loadUsageFileFromDisk,
|
|
34
|
+
tokenTotal,
|
|
35
|
+
totalsForRange,
|
|
36
|
+
dayKey,
|
|
37
|
+
} from "../../usage/usage-store.ts";
|
|
22
38
|
import {
|
|
23
39
|
detectCustomCompactionEnabled,
|
|
24
40
|
readSettings,
|
|
@@ -59,6 +75,52 @@ import {
|
|
|
59
75
|
scheduleDismissWelcome,
|
|
60
76
|
} from "../welcome/welcome-control.ts";
|
|
61
77
|
|
|
78
|
+
/**
|
|
79
|
+
* Fire the configured `powerline.costAlert` warning at most once per session.
|
|
80
|
+
* Reads the running cost from the (cached) token ledger so repeated calls are
|
|
81
|
+
* cheap; a UI-less or already-notified session short-circuits immediately.
|
|
82
|
+
*/
|
|
83
|
+
function maybeNotifyCostAlert(rt: RuntimeState, ctx: any): void {
|
|
84
|
+
if (!ctx?.hasUI || rt.costAlertNotified) return;
|
|
85
|
+
const threshold = config.costAlert;
|
|
86
|
+
const sessionEvents = rt.sessionBranchCache.get(ctx.sessionManager);
|
|
87
|
+
const totalCost = getSessionTotalCost(rt.tokenStatsCache.get(sessionEvents));
|
|
88
|
+
if (
|
|
89
|
+
!shouldTriggerCostAlert({
|
|
90
|
+
totalCost,
|
|
91
|
+
threshold,
|
|
92
|
+
alreadyNotified: rt.costAlertNotified,
|
|
93
|
+
})
|
|
94
|
+
) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
rt.costAlertNotified = true;
|
|
98
|
+
ctx.ui.notify(
|
|
99
|
+
formatCostAlertMessage(
|
|
100
|
+
totalCost,
|
|
101
|
+
threshold as number,
|
|
102
|
+
config.segmentOptions?.cost?.currency ?? "USD",
|
|
103
|
+
),
|
|
104
|
+
"warning",
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function maybeNotifyTokenBudget(rt: RuntimeState, ctx: any): void {
|
|
109
|
+
if (!ctx?.hasUI) return;
|
|
110
|
+
const daily = parseTokenBudget(readSettings(ctx.cwd ?? process.cwd()).wishcraft)
|
|
111
|
+
.daily;
|
|
112
|
+
if (!daily) return;
|
|
113
|
+
const now = Date.now();
|
|
114
|
+
const todayStart = Date.parse(`${dayKey(now)}T00:00:00`);
|
|
115
|
+
const used = tokenTotal(
|
|
116
|
+
totalsForRange(loadUsageFileFromDisk(), todayStart, now + 1),
|
|
117
|
+
);
|
|
118
|
+
const { level } = tokenBudgetLevel(used, daily);
|
|
119
|
+
if (level === 0 || level <= rt.tokenBudgetNotifiedLevel) return;
|
|
120
|
+
rt.tokenBudgetNotifiedLevel = level;
|
|
121
|
+
ctx.ui.notify(formatTokenBudgetWarning(used, daily, level), "warning");
|
|
122
|
+
}
|
|
123
|
+
|
|
62
124
|
// Helper to extract recent agent response text (skipping thinking blocks)
|
|
63
125
|
function getRecentAgentContext(ctx: any): string | undefined {
|
|
64
126
|
const sessionEvents = ctx.sessionManager?.getBranch?.() ?? [];
|
|
@@ -107,6 +169,8 @@ export function registerSessionLifecycle(
|
|
|
107
169
|
rt.lastUserPrompt = "";
|
|
108
170
|
rt.isStreaming = false;
|
|
109
171
|
rt.liveAssistantUsage = null;
|
|
172
|
+
rt.costAlertNotified = false;
|
|
173
|
+
rt.tokenBudgetNotifiedLevel = 0;
|
|
110
174
|
rt.powerlineCompacting = false;
|
|
111
175
|
rt.deliverAfterRetrySettles = false;
|
|
112
176
|
rt.stashedEditorText = null;
|
|
@@ -116,6 +180,9 @@ export function registerSessionLifecycle(
|
|
|
116
180
|
rt.bashModeSettings = parseBashModeSettings(settings, rt.resolvedShortcuts);
|
|
117
181
|
rt.showLastPrompt = settings.showLastPrompt !== false;
|
|
118
182
|
setConfig(parsePowerlineConfig(settings.powerline, PRESET_NAMES));
|
|
183
|
+
rt.queueStore.setSentRetentionMs(
|
|
184
|
+
config.queue.retentionHours * 60 * 60 * 1000,
|
|
185
|
+
);
|
|
119
186
|
registerCustomSegments(config.segments);
|
|
120
187
|
registerCustomPresets(config.presets);
|
|
121
188
|
warnInvalidSegmentSettings(ctx);
|
|
@@ -154,6 +221,7 @@ export function registerSessionLifecycle(
|
|
|
154
221
|
} else {
|
|
155
222
|
dismissWelcome(rt, ctx);
|
|
156
223
|
}
|
|
224
|
+
maybeNotifyTokenBudget(rt, ctx);
|
|
157
225
|
}
|
|
158
226
|
});
|
|
159
227
|
|
|
@@ -271,9 +339,21 @@ export function registerSessionLifecycle(
|
|
|
271
339
|
rt.liveAssistantUsage = null;
|
|
272
340
|
} else if (getUsageTokenTotal(event.message.usage) > 0) {
|
|
273
341
|
rt.liveAssistantUsage = event.message.usage;
|
|
342
|
+
const usage = event.message.usage;
|
|
343
|
+
recordUsageEvent({
|
|
344
|
+
at: Date.now(),
|
|
345
|
+
model: ctx.model?.id ?? ctx.model?.name,
|
|
346
|
+
input: usage.input,
|
|
347
|
+
output: usage.output,
|
|
348
|
+
cacheRead: usage.cacheRead,
|
|
349
|
+
cacheWrite: usage.cacheWrite,
|
|
350
|
+
cost: usage.cost.total,
|
|
351
|
+
});
|
|
274
352
|
}
|
|
275
353
|
}
|
|
276
354
|
requestImmediateStatusRender(rt, { deferDuringTyping: false });
|
|
355
|
+
maybeNotifyCostAlert(rt, ctx);
|
|
356
|
+
maybeNotifyTokenBudget(rt, ctx);
|
|
277
357
|
});
|
|
278
358
|
|
|
279
359
|
pi.on("turn_end", async (_event, ctx) => {
|
|
@@ -361,6 +441,7 @@ export function registerSessionLifecycle(
|
|
|
361
441
|
);
|
|
362
442
|
}
|
|
363
443
|
}
|
|
444
|
+
maybeNotifyCostAlert(rt, ctx);
|
|
364
445
|
}
|
|
365
446
|
} catch (error) {
|
|
366
447
|
if (!isStaleExtensionContextError(error)) throw error;
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import { readFileSync, writeFileSync, existsSync, mkdirSync } from "node:fs";
|
|
2
2
|
import { join, dirname } from "node:path";
|
|
3
3
|
|
|
4
|
-
import type {
|
|
4
|
+
import type {
|
|
5
|
+
CustomPresetConfig,
|
|
6
|
+
StatusLinePreset,
|
|
7
|
+
} from "../../config/types.ts";
|
|
5
8
|
import type { PowerlineConfig } from "../../config/powerline-config.ts";
|
|
6
9
|
import {
|
|
10
|
+
nextPowerlineSettingWithCustomPreset,
|
|
7
11
|
nextPowerlineSettingWithOptions,
|
|
8
12
|
nextPowerlineSettingWithPreset,
|
|
9
13
|
} from "../../config/powerline-config.ts";
|
|
@@ -182,21 +186,6 @@ export function writePowerlineSetting(
|
|
|
182
186
|
return writeSettingKey(cwd, "powerline", update);
|
|
183
187
|
}
|
|
184
188
|
|
|
185
|
-
export function writePowerlineDisabledSegmentSetting(
|
|
186
|
-
disabledSegments: string[],
|
|
187
|
-
cwd: string = process.cwd(),
|
|
188
|
-
): boolean {
|
|
189
|
-
return writePowerlineSetting(cwd, (existing) => {
|
|
190
|
-
// Shorthand string (bv. "chef") is een preset-naam: bewaar 'm.
|
|
191
|
-
const base = isRecord(existing)
|
|
192
|
-
? existing
|
|
193
|
-
: typeof existing === "string"
|
|
194
|
-
? { preset: existing }
|
|
195
|
-
: {};
|
|
196
|
-
return { ...base, disabledSegments };
|
|
197
|
-
});
|
|
198
|
-
}
|
|
199
|
-
|
|
200
189
|
export function writePowerlinePresetSetting(
|
|
201
190
|
preset: StatusLinePreset,
|
|
202
191
|
cwd: string = process.cwd(),
|
|
@@ -206,10 +195,27 @@ export function writePowerlinePresetSetting(
|
|
|
206
195
|
);
|
|
207
196
|
}
|
|
208
197
|
|
|
198
|
+
export function writePowerlineCustomPresetSetting(
|
|
199
|
+
cwd: string,
|
|
200
|
+
name: string,
|
|
201
|
+
presetDef: CustomPresetConfig,
|
|
202
|
+
): boolean {
|
|
203
|
+
return writePowerlineSetting(cwd, (existingPowerlineSetting) =>
|
|
204
|
+
nextPowerlineSettingWithCustomPreset(
|
|
205
|
+
existingPowerlineSetting,
|
|
206
|
+
name,
|
|
207
|
+
presetDef,
|
|
208
|
+
),
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
|
|
209
212
|
export function writePowerlineOptionSetting(
|
|
210
213
|
cwd: string,
|
|
211
214
|
updates: Partial<
|
|
212
|
-
Pick<
|
|
215
|
+
Pick<
|
|
216
|
+
PowerlineConfig,
|
|
217
|
+
"welcome" | "stashSharpSShortcut" | "placement" | "disabledSegments"
|
|
218
|
+
>
|
|
213
219
|
>,
|
|
214
220
|
currentPreset: StatusLinePreset,
|
|
215
221
|
): boolean {
|
|
@@ -145,7 +145,8 @@ export function buildConfigGroups(settings: Record<string, unknown>): ConfigGrou
|
|
|
145
145
|
title: "Hooks & repairs (harness-laag)",
|
|
146
146
|
items: [
|
|
147
147
|
{ label: "Hooks ingeschakeld", path: "wishcraft.hooksEnabled", kind: "toggle", hint: "commandcode-achtige preToolUse/postToolUse/sessionStart hooks" },
|
|
148
|
-
{ label: "Tool-input repairs", path: "wishcraft.repairsEnabled", kind: "toggle", hint: "null-for-optional, auto-link
|
|
148
|
+
{ label: "Tool-input repairs", path: "wishcraft.repairsEnabled", kind: "toggle", hint: "null-for-optional, auto-link, json-array, path aliases" },
|
|
149
|
+
{ label: "Dagelijks tokenbudget", path: "wishcraft.tokenBudget.daily", kind: "number", hint: "kleurt cost-segment; blokkeert nooit. 0 = uit" },
|
|
149
150
|
],
|
|
150
151
|
},
|
|
151
152
|
{
|
|
@@ -348,10 +348,15 @@ export function parseBashModeSettings(
|
|
|
348
348
|
Number.isFinite(raw.transcriptMaxBytes)
|
|
349
349
|
? Math.max(16 * 1024, Math.floor(raw.transcriptMaxBytes))
|
|
350
350
|
: DEFAULT_BASH_MODE_SETTINGS.transcriptMaxBytes;
|
|
351
|
+
const initScript =
|
|
352
|
+
typeof raw.initScript === "string" && raw.initScript.trim()
|
|
353
|
+
? raw.initScript
|
|
354
|
+
: null;
|
|
351
355
|
|
|
352
356
|
return {
|
|
353
357
|
toggleShortcut,
|
|
354
358
|
transcriptMaxLines,
|
|
355
359
|
transcriptMaxBytes,
|
|
360
|
+
initScript,
|
|
356
361
|
};
|
|
357
362
|
}
|
|
@@ -61,7 +61,7 @@ export function computeResponsiveLayout(
|
|
|
61
61
|
// Get all segments: primary first, then secondary
|
|
62
62
|
const mergedSegments = mergeSegmentsWithCustomItems(
|
|
63
63
|
presetDef,
|
|
64
|
-
|
|
64
|
+
ctx.effectiveCustomItems,
|
|
65
65
|
{
|
|
66
66
|
layout: config.layout,
|
|
67
67
|
disabledSegments: config.disabledSegments,
|