takomi 2.1.2 → 2.1.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/.pi/README.md +124 -124
- package/.pi/agents/architect.md +15 -15
- package/.pi/agents/coder.md +14 -14
- package/.pi/agents/designer.md +17 -17
- package/.pi/agents/orchestrator.md +22 -22
- package/.pi/agents/reviewer.md +16 -16
- package/.pi/extensions/oauth-router/README.md +125 -125
- package/.pi/extensions/oauth-router/commands.ts +380 -380
- package/.pi/extensions/oauth-router/config.ts +200 -200
- package/.pi/extensions/oauth-router/index.ts +41 -41
- package/.pi/extensions/oauth-router/oauth-flow.ts +154 -154
- package/.pi/extensions/oauth-router/oauth-store.ts +121 -121
- package/.pi/extensions/oauth-router/package.json +14 -14
- package/.pi/extensions/oauth-router/policies.ts +27 -27
- package/.pi/extensions/oauth-router/provider.ts +492 -492
- package/.pi/extensions/oauth-router/scripts/vibe-verify.py +98 -98
- package/.pi/extensions/oauth-router/state.ts +174 -174
- package/.pi/extensions/oauth-router/types.ts +153 -153
- package/.pi/extensions/takomi-runtime/command-text.ts +130 -130
- package/.pi/extensions/takomi-runtime/commands.ts +179 -179
- package/.pi/extensions/takomi-runtime/context-panel.ts +282 -282
- package/.pi/extensions/takomi-runtime/index.ts +1288 -1288
- package/.pi/extensions/takomi-runtime/profile.ts +114 -114
- package/.pi/extensions/takomi-runtime/routing-policy.ts +105 -105
- package/.pi/extensions/takomi-runtime/shared.ts +492 -492
- package/.pi/extensions/takomi-runtime/subagent-controller.ts +364 -364
- package/.pi/extensions/takomi-runtime/subagent-render.ts +501 -501
- package/.pi/extensions/takomi-runtime/subagent-types.ts +83 -83
- package/.pi/extensions/takomi-runtime/ui.ts +133 -133
- package/.pi/extensions/takomi-subagents/agent-aliases.ts +18 -18
- package/.pi/extensions/takomi-subagents/agents.ts +113 -113
- package/.pi/extensions/takomi-subagents/delegation-plan.ts +95 -95
- package/.pi/extensions/takomi-subagents/dispatch-helpers.ts +26 -26
- package/.pi/extensions/takomi-subagents/dispatch.ts +215 -215
- package/.pi/extensions/takomi-subagents/index.ts +75 -75
- package/.pi/extensions/takomi-subagents/live-updates.ts +83 -83
- package/.pi/extensions/takomi-subagents/native-render.ts +174 -174
- package/.pi/extensions/takomi-subagents/tool-runner.ts +209 -209
- package/.pi/themes/takomi-noir.json +81 -81
- package/package.json +59 -59
- package/src/doctor.js +87 -84
- package/src/pi-harness.js +355 -351
- package/src/pi-installer.js +193 -171
- package/src/pi-takomi-core/index.ts +4 -4
- package/src/pi-takomi-core/orchestration.ts +402 -402
- package/src/pi-takomi-core/routing.ts +93 -93
- package/src/pi-takomi-core/types.ts +173 -173
- package/src/pi-takomi-core/workflows.ts +299 -299
- package/src/skills-installer.js +101 -101
|
@@ -1,282 +1,282 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Takomi Context Panel - right-side overlay showing session context.
|
|
3
|
-
*
|
|
4
|
-
* Tracks file edits, tool usage, and Takomi runtime metadata.
|
|
5
|
-
* Toggled with Alt+C or /takomi-context.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import type { ExtensionAPI, ExtensionContext, Theme } from "@mariozechner/pi-coding-agent";
|
|
9
|
-
import { ellipsizeMiddle, formatDuration, truncateToWidth } from "./shared";
|
|
10
|
-
|
|
11
|
-
interface Component {
|
|
12
|
-
render(width: number): string[];
|
|
13
|
-
handleInput?(data: string): void;
|
|
14
|
-
invalidate(): void;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
interface OverlayHandle {
|
|
18
|
-
hide(): void;
|
|
19
|
-
setHidden(hidden: boolean): void;
|
|
20
|
-
isHidden(): boolean;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export type FileEdit = {
|
|
24
|
-
path: string;
|
|
25
|
-
action: "M" | "+" | "R";
|
|
26
|
-
timestamp: number;
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
export type ToolUseCount = {
|
|
30
|
-
[tool: string]: number;
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
export type ContextRuntimeState = {
|
|
34
|
-
role?: string;
|
|
35
|
-
stage?: string;
|
|
36
|
-
workflow?: string;
|
|
37
|
-
activeSessionId?: string;
|
|
38
|
-
autoOrch?: boolean;
|
|
39
|
-
launchMode?: string;
|
|
40
|
-
planMode?: boolean;
|
|
41
|
-
activeSubagent?: string;
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
export type ContextPanelState = {
|
|
45
|
-
fileEdits: FileEdit[];
|
|
46
|
-
toolUses: ToolUseCount;
|
|
47
|
-
sessionStart: number;
|
|
48
|
-
lastToolAt: number;
|
|
49
|
-
runtime: ContextRuntimeState;
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
function createEmptyState(): ContextPanelState {
|
|
53
|
-
return {
|
|
54
|
-
fileEdits: [],
|
|
55
|
-
toolUses: {},
|
|
56
|
-
sessionStart: Date.now(),
|
|
57
|
-
lastToolAt: 0,
|
|
58
|
-
runtime: {},
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
class ContextPanelComponent implements Component {
|
|
63
|
-
constructor(
|
|
64
|
-
private readonly theme: Theme,
|
|
65
|
-
private readonly getState: () => ContextPanelState,
|
|
66
|
-
private readonly getCtx: () => ExtensionContext | undefined,
|
|
67
|
-
) {}
|
|
68
|
-
|
|
69
|
-
invalidate(): void {}
|
|
70
|
-
|
|
71
|
-
render(width: number): string[] {
|
|
72
|
-
const theme = this.theme;
|
|
73
|
-
const state = this.getState();
|
|
74
|
-
const ctx = this.getCtx();
|
|
75
|
-
const panelWidth = Math.min(36, Math.max(24, width));
|
|
76
|
-
const innerWidth = panelWidth - 4;
|
|
77
|
-
const pad = " ";
|
|
78
|
-
const lines: string[] = [];
|
|
79
|
-
const hBar = theme.fg("dim", "─".repeat(panelWidth));
|
|
80
|
-
|
|
81
|
-
lines.push(hBar);
|
|
82
|
-
lines.push(`${pad}${theme.fg("accent", "◎ Context")}`);
|
|
83
|
-
lines.push("");
|
|
84
|
-
|
|
85
|
-
if (ctx) {
|
|
86
|
-
const elapsed = formatDuration(Date.now() - state.sessionStart);
|
|
87
|
-
const runtimeLabel = [
|
|
88
|
-
state.runtime.role ?? "agent",
|
|
89
|
-
state.runtime.stage ?? "-",
|
|
90
|
-
].join(" · ");
|
|
91
|
-
|
|
92
|
-
lines.push(`${pad}${theme.fg("muted", runtimeLabel)}`);
|
|
93
|
-
lines.push(`${pad}${theme.fg("dim", "Session:")} ${theme.fg("muted", elapsed)}`);
|
|
94
|
-
|
|
95
|
-
if (state.runtime.workflow) {
|
|
96
|
-
lines.push(`${pad}${theme.fg("dim", "Flow:")} ${theme.fg("muted", truncateToWidth(state.runtime.workflow, innerWidth - 10))}`);
|
|
97
|
-
}
|
|
98
|
-
if (state.runtime.activeSessionId) {
|
|
99
|
-
lines.push(`${pad}${theme.fg("dim", "ID:")} ${theme.fg("muted", ellipsizeMiddle(state.runtime.activeSessionId, innerWidth - 10))}`);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
const contextUsage = ctx.getContextUsage();
|
|
103
|
-
if (contextUsage && contextUsage.percent !== null) {
|
|
104
|
-
const pct = Math.round(contextUsage.percent);
|
|
105
|
-
const tokStr = contextUsage.tokens !== null
|
|
106
|
-
? `${Math.round(contextUsage.tokens / 1000)}k/${Math.round(contextUsage.contextWindow / 1000)}k`
|
|
107
|
-
: "?";
|
|
108
|
-
const ctxTone = pct > 80 ? "error" : pct > 60 ? "warning" : "muted";
|
|
109
|
-
lines.push(`${pad}${theme.fg("dim", "Context:")} ${theme.fg(ctxTone as never, `${pct}%`)} ${theme.fg("dim", `(${tokStr})`)}`);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
if (ctx.model) {
|
|
113
|
-
lines.push(`${pad}${theme.fg("dim", "Model:")} ${theme.fg("muted", ellipsizeMiddle(ctx.model.id, innerWidth - 10))}`);
|
|
114
|
-
}
|
|
115
|
-
if (state.runtime.activeSubagent) {
|
|
116
|
-
lines.push(`${pad}${theme.fg("dim", "Agent:")} ${theme.fg("muted", truncateToWidth(state.runtime.activeSubagent, innerWidth - 10))}`);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
const modeFlags = [
|
|
120
|
-
state.runtime.launchMode ?? (state.runtime.autoOrch ? "auto" : "manual"),
|
|
121
|
-
state.runtime.planMode ? "plan" : "direct",
|
|
122
|
-
].join(" | ");
|
|
123
|
-
lines.push(`${pad}${theme.fg("dim", "Mode:")} ${theme.fg("muted", modeFlags)}`);
|
|
124
|
-
lines.push("");
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
lines.push(`${pad}${theme.fg("accent", "-- Files Modified --")}`);
|
|
128
|
-
if (state.fileEdits.length === 0) {
|
|
129
|
-
lines.push(`${pad}${theme.fg("dim", " (none yet)")}`);
|
|
130
|
-
} else {
|
|
131
|
-
const seen = new Map<string, FileEdit>();
|
|
132
|
-
for (const edit of state.fileEdits) seen.set(edit.path, edit);
|
|
133
|
-
const deduped = [...seen.values()].slice(-12);
|
|
134
|
-
|
|
135
|
-
for (const edit of deduped) {
|
|
136
|
-
const icon = edit.action === "+" ? theme.fg("success", "+") : edit.action === "R" ? theme.fg("muted", "R") : theme.fg("warning", "M");
|
|
137
|
-
const displayPath = ellipsizeMiddle(edit.path.replace(/\\/g, "/"), innerWidth - 4);
|
|
138
|
-
lines.push(`${pad} ${icon} ${truncateToWidth(displayPath, innerWidth - 4)}`);
|
|
139
|
-
}
|
|
140
|
-
if (seen.size > 12) {
|
|
141
|
-
lines.push(`${pad} ${theme.fg("dim", `... +${seen.size - 12} more`)}`);
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
lines.push("");
|
|
145
|
-
|
|
146
|
-
lines.push(`${pad}${theme.fg("accent", "-- Tool Activity --")}`);
|
|
147
|
-
const toolEntries = Object.entries(state.toolUses).sort((a, b) => b[1] - a[1]);
|
|
148
|
-
if (toolEntries.length === 0) {
|
|
149
|
-
lines.push(`${pad}${theme.fg("dim", " (no tools used)")}`);
|
|
150
|
-
} else {
|
|
151
|
-
for (const [tool, count] of toolEntries.slice(0, 8)) {
|
|
152
|
-
lines.push(`${pad} ${theme.fg("success", "✓")} ${tool} ${theme.fg("dim", `(${count})`)}`);
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
lines.push("");
|
|
156
|
-
|
|
157
|
-
lines.push(`${pad}${theme.fg("dim", "(Alt+C to close)")}`);
|
|
158
|
-
lines.push(hBar);
|
|
159
|
-
return lines;
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
export class TakomiContextPanel {
|
|
164
|
-
private state = createEmptyState();
|
|
165
|
-
private visible = false;
|
|
166
|
-
private overlayHandle?: OverlayHandle;
|
|
167
|
-
private requestRender?: () => void;
|
|
168
|
-
private lastCtx?: ExtensionContext;
|
|
169
|
-
|
|
170
|
-
getState(): ContextPanelState {
|
|
171
|
-
return this.state;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
isVisible(): boolean {
|
|
175
|
-
return this.visible;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
setRuntimeState(runtime: ContextRuntimeState): void {
|
|
179
|
-
this.state.runtime = { ...runtime };
|
|
180
|
-
this.requestRender?.();
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
trackFileEdit(filePath: string, action: "M" | "+" | "R"): void {
|
|
184
|
-
this.state.fileEdits.push({ path: filePath, action, timestamp: Date.now() });
|
|
185
|
-
this.state.lastToolAt = Date.now();
|
|
186
|
-
this.requestRender?.();
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
trackToolUse(toolName: string): void {
|
|
190
|
-
this.state.toolUses[toolName] = (this.state.toolUses[toolName] ?? 0) + 1;
|
|
191
|
-
this.state.lastToolAt = Date.now();
|
|
192
|
-
this.requestRender?.();
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
resetSession(): void {
|
|
196
|
-
this.state = createEmptyState();
|
|
197
|
-
this.requestRender?.();
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
refresh(): void {
|
|
201
|
-
this.requestRender?.();
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
toggle(ctx: ExtensionContext): void {
|
|
205
|
-
this.lastCtx = ctx;
|
|
206
|
-
if (this.visible) this.hide();
|
|
207
|
-
else this.show(ctx);
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
show(ctx: ExtensionContext): void {
|
|
211
|
-
this.lastCtx = ctx;
|
|
212
|
-
if (!ctx.hasUI) return;
|
|
213
|
-
if (this.visible) {
|
|
214
|
-
this.requestRender?.();
|
|
215
|
-
return;
|
|
216
|
-
}
|
|
217
|
-
this.visible = true;
|
|
218
|
-
|
|
219
|
-
void ctx.ui.custom<void>(
|
|
220
|
-
(tui, theme, _keybindings, _done) => {
|
|
221
|
-
this.requestRender = () => tui.requestRender();
|
|
222
|
-
return new ContextPanelComponent(theme, () => this.state, () => this.lastCtx);
|
|
223
|
-
},
|
|
224
|
-
{
|
|
225
|
-
overlay: true,
|
|
226
|
-
overlayOptions: {
|
|
227
|
-
width: 36,
|
|
228
|
-
maxHeight: "70%",
|
|
229
|
-
anchor: "right-center",
|
|
230
|
-
margin: { right: 1, top: 2, bottom: 3 },
|
|
231
|
-
nonCapturing: true,
|
|
232
|
-
visible: (termWidth) => termWidth >= 100,
|
|
233
|
-
},
|
|
234
|
-
onHandle: (handle) => {
|
|
235
|
-
this.overlayHandle = handle;
|
|
236
|
-
},
|
|
237
|
-
},
|
|
238
|
-
).then(() => {
|
|
239
|
-
this.visible = false;
|
|
240
|
-
this.overlayHandle = undefined;
|
|
241
|
-
this.requestRender = undefined;
|
|
242
|
-
});
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
hide(): void {
|
|
246
|
-
this.visible = false;
|
|
247
|
-
this.overlayHandle?.hide();
|
|
248
|
-
this.overlayHandle = undefined;
|
|
249
|
-
this.requestRender = undefined;
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
export function wireContextPanel(pi: ExtensionAPI, panel: TakomiContextPanel): void {
|
|
254
|
-
pi.on("tool_result", (event) => {
|
|
255
|
-
const toolName = event.toolName;
|
|
256
|
-
panel.trackToolUse(toolName);
|
|
257
|
-
|
|
258
|
-
if (toolName === "edit" || toolName === "write" || toolName === "read") {
|
|
259
|
-
const input = event.input as { file_path?: string; filePath?: string };
|
|
260
|
-
const filePath = input.file_path ?? input.filePath;
|
|
261
|
-
if (filePath) {
|
|
262
|
-
const action = toolName === "write" ? "+" : toolName === "read" ? "R" : "M";
|
|
263
|
-
panel.trackFileEdit(filePath, action);
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
});
|
|
267
|
-
|
|
268
|
-
pi.on("turn_end", () => {
|
|
269
|
-
panel.refresh();
|
|
270
|
-
});
|
|
271
|
-
|
|
272
|
-
pi.on("session_start", () => {
|
|
273
|
-
panel.resetSession();
|
|
274
|
-
});
|
|
275
|
-
|
|
276
|
-
pi.registerShortcut("alt+c", {
|
|
277
|
-
description: "Toggle Takomi context panel",
|
|
278
|
-
handler: async (ctx) => {
|
|
279
|
-
panel.toggle(ctx);
|
|
280
|
-
},
|
|
281
|
-
});
|
|
282
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Takomi Context Panel - right-side overlay showing session context.
|
|
3
|
+
*
|
|
4
|
+
* Tracks file edits, tool usage, and Takomi runtime metadata.
|
|
5
|
+
* Toggled with Alt+C or /takomi-context.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { ExtensionAPI, ExtensionContext, Theme } from "@mariozechner/pi-coding-agent";
|
|
9
|
+
import { ellipsizeMiddle, formatDuration, truncateToWidth } from "./shared";
|
|
10
|
+
|
|
11
|
+
interface Component {
|
|
12
|
+
render(width: number): string[];
|
|
13
|
+
handleInput?(data: string): void;
|
|
14
|
+
invalidate(): void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface OverlayHandle {
|
|
18
|
+
hide(): void;
|
|
19
|
+
setHidden(hidden: boolean): void;
|
|
20
|
+
isHidden(): boolean;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export type FileEdit = {
|
|
24
|
+
path: string;
|
|
25
|
+
action: "M" | "+" | "R";
|
|
26
|
+
timestamp: number;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export type ToolUseCount = {
|
|
30
|
+
[tool: string]: number;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export type ContextRuntimeState = {
|
|
34
|
+
role?: string;
|
|
35
|
+
stage?: string;
|
|
36
|
+
workflow?: string;
|
|
37
|
+
activeSessionId?: string;
|
|
38
|
+
autoOrch?: boolean;
|
|
39
|
+
launchMode?: string;
|
|
40
|
+
planMode?: boolean;
|
|
41
|
+
activeSubagent?: string;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export type ContextPanelState = {
|
|
45
|
+
fileEdits: FileEdit[];
|
|
46
|
+
toolUses: ToolUseCount;
|
|
47
|
+
sessionStart: number;
|
|
48
|
+
lastToolAt: number;
|
|
49
|
+
runtime: ContextRuntimeState;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
function createEmptyState(): ContextPanelState {
|
|
53
|
+
return {
|
|
54
|
+
fileEdits: [],
|
|
55
|
+
toolUses: {},
|
|
56
|
+
sessionStart: Date.now(),
|
|
57
|
+
lastToolAt: 0,
|
|
58
|
+
runtime: {},
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
class ContextPanelComponent implements Component {
|
|
63
|
+
constructor(
|
|
64
|
+
private readonly theme: Theme,
|
|
65
|
+
private readonly getState: () => ContextPanelState,
|
|
66
|
+
private readonly getCtx: () => ExtensionContext | undefined,
|
|
67
|
+
) {}
|
|
68
|
+
|
|
69
|
+
invalidate(): void {}
|
|
70
|
+
|
|
71
|
+
render(width: number): string[] {
|
|
72
|
+
const theme = this.theme;
|
|
73
|
+
const state = this.getState();
|
|
74
|
+
const ctx = this.getCtx();
|
|
75
|
+
const panelWidth = Math.min(36, Math.max(24, width));
|
|
76
|
+
const innerWidth = panelWidth - 4;
|
|
77
|
+
const pad = " ";
|
|
78
|
+
const lines: string[] = [];
|
|
79
|
+
const hBar = theme.fg("dim", "─".repeat(panelWidth));
|
|
80
|
+
|
|
81
|
+
lines.push(hBar);
|
|
82
|
+
lines.push(`${pad}${theme.fg("accent", "◎ Context")}`);
|
|
83
|
+
lines.push("");
|
|
84
|
+
|
|
85
|
+
if (ctx) {
|
|
86
|
+
const elapsed = formatDuration(Date.now() - state.sessionStart);
|
|
87
|
+
const runtimeLabel = [
|
|
88
|
+
state.runtime.role ?? "agent",
|
|
89
|
+
state.runtime.stage ?? "-",
|
|
90
|
+
].join(" · ");
|
|
91
|
+
|
|
92
|
+
lines.push(`${pad}${theme.fg("muted", runtimeLabel)}`);
|
|
93
|
+
lines.push(`${pad}${theme.fg("dim", "Session:")} ${theme.fg("muted", elapsed)}`);
|
|
94
|
+
|
|
95
|
+
if (state.runtime.workflow) {
|
|
96
|
+
lines.push(`${pad}${theme.fg("dim", "Flow:")} ${theme.fg("muted", truncateToWidth(state.runtime.workflow, innerWidth - 10))}`);
|
|
97
|
+
}
|
|
98
|
+
if (state.runtime.activeSessionId) {
|
|
99
|
+
lines.push(`${pad}${theme.fg("dim", "ID:")} ${theme.fg("muted", ellipsizeMiddle(state.runtime.activeSessionId, innerWidth - 10))}`);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const contextUsage = ctx.getContextUsage();
|
|
103
|
+
if (contextUsage && contextUsage.percent !== null) {
|
|
104
|
+
const pct = Math.round(contextUsage.percent);
|
|
105
|
+
const tokStr = contextUsage.tokens !== null
|
|
106
|
+
? `${Math.round(contextUsage.tokens / 1000)}k/${Math.round(contextUsage.contextWindow / 1000)}k`
|
|
107
|
+
: "?";
|
|
108
|
+
const ctxTone = pct > 80 ? "error" : pct > 60 ? "warning" : "muted";
|
|
109
|
+
lines.push(`${pad}${theme.fg("dim", "Context:")} ${theme.fg(ctxTone as never, `${pct}%`)} ${theme.fg("dim", `(${tokStr})`)}`);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (ctx.model) {
|
|
113
|
+
lines.push(`${pad}${theme.fg("dim", "Model:")} ${theme.fg("muted", ellipsizeMiddle(ctx.model.id, innerWidth - 10))}`);
|
|
114
|
+
}
|
|
115
|
+
if (state.runtime.activeSubagent) {
|
|
116
|
+
lines.push(`${pad}${theme.fg("dim", "Agent:")} ${theme.fg("muted", truncateToWidth(state.runtime.activeSubagent, innerWidth - 10))}`);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const modeFlags = [
|
|
120
|
+
state.runtime.launchMode ?? (state.runtime.autoOrch ? "auto" : "manual"),
|
|
121
|
+
state.runtime.planMode ? "plan" : "direct",
|
|
122
|
+
].join(" | ");
|
|
123
|
+
lines.push(`${pad}${theme.fg("dim", "Mode:")} ${theme.fg("muted", modeFlags)}`);
|
|
124
|
+
lines.push("");
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
lines.push(`${pad}${theme.fg("accent", "-- Files Modified --")}`);
|
|
128
|
+
if (state.fileEdits.length === 0) {
|
|
129
|
+
lines.push(`${pad}${theme.fg("dim", " (none yet)")}`);
|
|
130
|
+
} else {
|
|
131
|
+
const seen = new Map<string, FileEdit>();
|
|
132
|
+
for (const edit of state.fileEdits) seen.set(edit.path, edit);
|
|
133
|
+
const deduped = [...seen.values()].slice(-12);
|
|
134
|
+
|
|
135
|
+
for (const edit of deduped) {
|
|
136
|
+
const icon = edit.action === "+" ? theme.fg("success", "+") : edit.action === "R" ? theme.fg("muted", "R") : theme.fg("warning", "M");
|
|
137
|
+
const displayPath = ellipsizeMiddle(edit.path.replace(/\\/g, "/"), innerWidth - 4);
|
|
138
|
+
lines.push(`${pad} ${icon} ${truncateToWidth(displayPath, innerWidth - 4)}`);
|
|
139
|
+
}
|
|
140
|
+
if (seen.size > 12) {
|
|
141
|
+
lines.push(`${pad} ${theme.fg("dim", `... +${seen.size - 12} more`)}`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
lines.push("");
|
|
145
|
+
|
|
146
|
+
lines.push(`${pad}${theme.fg("accent", "-- Tool Activity --")}`);
|
|
147
|
+
const toolEntries = Object.entries(state.toolUses).sort((a, b) => b[1] - a[1]);
|
|
148
|
+
if (toolEntries.length === 0) {
|
|
149
|
+
lines.push(`${pad}${theme.fg("dim", " (no tools used)")}`);
|
|
150
|
+
} else {
|
|
151
|
+
for (const [tool, count] of toolEntries.slice(0, 8)) {
|
|
152
|
+
lines.push(`${pad} ${theme.fg("success", "✓")} ${tool} ${theme.fg("dim", `(${count})`)}`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
lines.push("");
|
|
156
|
+
|
|
157
|
+
lines.push(`${pad}${theme.fg("dim", "(Alt+C to close)")}`);
|
|
158
|
+
lines.push(hBar);
|
|
159
|
+
return lines;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export class TakomiContextPanel {
|
|
164
|
+
private state = createEmptyState();
|
|
165
|
+
private visible = false;
|
|
166
|
+
private overlayHandle?: OverlayHandle;
|
|
167
|
+
private requestRender?: () => void;
|
|
168
|
+
private lastCtx?: ExtensionContext;
|
|
169
|
+
|
|
170
|
+
getState(): ContextPanelState {
|
|
171
|
+
return this.state;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
isVisible(): boolean {
|
|
175
|
+
return this.visible;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
setRuntimeState(runtime: ContextRuntimeState): void {
|
|
179
|
+
this.state.runtime = { ...runtime };
|
|
180
|
+
this.requestRender?.();
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
trackFileEdit(filePath: string, action: "M" | "+" | "R"): void {
|
|
184
|
+
this.state.fileEdits.push({ path: filePath, action, timestamp: Date.now() });
|
|
185
|
+
this.state.lastToolAt = Date.now();
|
|
186
|
+
this.requestRender?.();
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
trackToolUse(toolName: string): void {
|
|
190
|
+
this.state.toolUses[toolName] = (this.state.toolUses[toolName] ?? 0) + 1;
|
|
191
|
+
this.state.lastToolAt = Date.now();
|
|
192
|
+
this.requestRender?.();
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
resetSession(): void {
|
|
196
|
+
this.state = createEmptyState();
|
|
197
|
+
this.requestRender?.();
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
refresh(): void {
|
|
201
|
+
this.requestRender?.();
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
toggle(ctx: ExtensionContext): void {
|
|
205
|
+
this.lastCtx = ctx;
|
|
206
|
+
if (this.visible) this.hide();
|
|
207
|
+
else this.show(ctx);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
show(ctx: ExtensionContext): void {
|
|
211
|
+
this.lastCtx = ctx;
|
|
212
|
+
if (!ctx.hasUI) return;
|
|
213
|
+
if (this.visible) {
|
|
214
|
+
this.requestRender?.();
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
this.visible = true;
|
|
218
|
+
|
|
219
|
+
void ctx.ui.custom<void>(
|
|
220
|
+
(tui, theme, _keybindings, _done) => {
|
|
221
|
+
this.requestRender = () => tui.requestRender();
|
|
222
|
+
return new ContextPanelComponent(theme, () => this.state, () => this.lastCtx);
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
overlay: true,
|
|
226
|
+
overlayOptions: {
|
|
227
|
+
width: 36,
|
|
228
|
+
maxHeight: "70%",
|
|
229
|
+
anchor: "right-center",
|
|
230
|
+
margin: { right: 1, top: 2, bottom: 3 },
|
|
231
|
+
nonCapturing: true,
|
|
232
|
+
visible: (termWidth) => termWidth >= 100,
|
|
233
|
+
},
|
|
234
|
+
onHandle: (handle) => {
|
|
235
|
+
this.overlayHandle = handle;
|
|
236
|
+
},
|
|
237
|
+
},
|
|
238
|
+
).then(() => {
|
|
239
|
+
this.visible = false;
|
|
240
|
+
this.overlayHandle = undefined;
|
|
241
|
+
this.requestRender = undefined;
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
hide(): void {
|
|
246
|
+
this.visible = false;
|
|
247
|
+
this.overlayHandle?.hide();
|
|
248
|
+
this.overlayHandle = undefined;
|
|
249
|
+
this.requestRender = undefined;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
export function wireContextPanel(pi: ExtensionAPI, panel: TakomiContextPanel): void {
|
|
254
|
+
pi.on("tool_result", (event) => {
|
|
255
|
+
const toolName = event.toolName;
|
|
256
|
+
panel.trackToolUse(toolName);
|
|
257
|
+
|
|
258
|
+
if (toolName === "edit" || toolName === "write" || toolName === "read") {
|
|
259
|
+
const input = event.input as { file_path?: string; filePath?: string };
|
|
260
|
+
const filePath = input.file_path ?? input.filePath;
|
|
261
|
+
if (filePath) {
|
|
262
|
+
const action = toolName === "write" ? "+" : toolName === "read" ? "R" : "M";
|
|
263
|
+
panel.trackFileEdit(filePath, action);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
pi.on("turn_end", () => {
|
|
269
|
+
panel.refresh();
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
pi.on("session_start", () => {
|
|
273
|
+
panel.resetSession();
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
pi.registerShortcut("alt+c", {
|
|
277
|
+
description: "Toggle Takomi context panel",
|
|
278
|
+
handler: async (ctx) => {
|
|
279
|
+
panel.toggle(ctx);
|
|
280
|
+
},
|
|
281
|
+
});
|
|
282
|
+
}
|