@oh-my-pi/pi-coding-agent 15.2.3 → 15.3.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.
Files changed (72) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/dist/types/config/settings-schema.d.ts +34 -1
  3. package/dist/types/config/settings.d.ts +6 -0
  4. package/dist/types/discovery/helpers.d.ts +1 -0
  5. package/dist/types/goals/runtime.d.ts +4 -0
  6. package/dist/types/hashline/constants.d.ts +0 -2
  7. package/dist/types/hashline/hash.d.ts +13 -39
  8. package/dist/types/hashline/parser.d.ts +2 -6
  9. package/dist/types/modes/components/status-line/types.d.ts +10 -0
  10. package/dist/types/modes/components/status-line.d.ts +10 -0
  11. package/dist/types/modes/interactive-mode.d.ts +3 -1
  12. package/dist/types/modes/shared.d.ts +9 -0
  13. package/dist/types/modes/theme/shimmer.d.ts +6 -3
  14. package/dist/types/modes/types.d.ts +3 -1
  15. package/dist/types/modes/utils/context-usage.d.ts +17 -0
  16. package/dist/types/modes/utils/ui-helpers.d.ts +5 -1
  17. package/dist/types/session/agent-session.d.ts +9 -0
  18. package/dist/types/task/executor.d.ts +3 -1
  19. package/dist/types/task/types.d.ts +35 -0
  20. package/dist/types/tools/bash-command-fixup.d.ts +0 -5
  21. package/dist/types/utils/clipboard.d.ts +3 -1
  22. package/dist/types/utils/image-resize.d.ts +4 -1
  23. package/package.json +7 -7
  24. package/src/config/prompt-templates.ts +1 -8
  25. package/src/config/settings-schema.ts +29 -1
  26. package/src/config/settings.ts +19 -0
  27. package/src/discovery/helpers.ts +5 -1
  28. package/src/edit/index.ts +1 -1
  29. package/src/edit/renderer.ts +5 -7
  30. package/src/edit/streaming.ts +24 -12
  31. package/src/extensibility/plugins/legacy-pi-compat.ts +27 -5
  32. package/src/goals/runtime.ts +35 -13
  33. package/src/hashline/constants.ts +0 -3
  34. package/src/hashline/diff.ts +1 -1
  35. package/src/hashline/execute.ts +2 -2
  36. package/src/hashline/grammar.lark +7 -8
  37. package/src/hashline/hash.ts +21 -43
  38. package/src/hashline/input.ts +15 -13
  39. package/src/hashline/parser.ts +62 -161
  40. package/src/internal-urls/docs-index.generated.ts +2 -2
  41. package/src/main.ts +1 -1
  42. package/src/modes/components/model-selector.ts +53 -22
  43. package/src/modes/components/status-line/segments.ts +53 -0
  44. package/src/modes/components/status-line/types.ts +4 -0
  45. package/src/modes/components/status-line.ts +147 -12
  46. package/src/modes/controllers/command-controller.ts +9 -0
  47. package/src/modes/controllers/event-controller.ts +10 -1
  48. package/src/modes/interactive-mode.ts +74 -18
  49. package/src/modes/shared.ts +16 -0
  50. package/src/modes/theme/shimmer.ts +15 -6
  51. package/src/modes/theme/theme.ts +1 -1
  52. package/src/modes/types.ts +1 -1
  53. package/src/modes/utils/context-usage.ts +25 -2
  54. package/src/modes/utils/ui-helpers.ts +11 -1
  55. package/src/prompts/agents/frontmatter.md +1 -0
  56. package/src/prompts/tools/hashline.md +62 -81
  57. package/src/sdk.ts +24 -0
  58. package/src/session/agent-session.ts +58 -0
  59. package/src/session/session-manager.ts +54 -1
  60. package/src/slash-commands/builtin-registry.ts +10 -0
  61. package/src/task/executor.ts +50 -1
  62. package/src/task/index.ts +11 -0
  63. package/src/task/render.ts +26 -2
  64. package/src/task/types.ts +35 -0
  65. package/src/tools/bash-command-fixup.ts +0 -10
  66. package/src/tools/bash.ts +1 -9
  67. package/src/utils/clipboard.ts +68 -3
  68. package/src/utils/commit-message-generator.ts +6 -1
  69. package/src/utils/image-resize.ts +51 -26
  70. package/src/utils/title-generator.ts +45 -13
  71. package/dist/types/modes/components/status-line-segment-editor.d.ts +0 -24
  72. package/src/modes/components/status-line-segment-editor.ts +0 -359
@@ -1,359 +0,0 @@
1
- /**
2
- * Status Line Segment Editor
3
- *
4
- * Interactive component for configuring status line segments.
5
- * - Three-column layout: Left | Right | Disabled
6
- * - Space: Toggle segment visibility (disabled ↔ left)
7
- * - Tab: Cycle segment between columns (left → right → disabled → left)
8
- * - Shift+J/K: Reorder segment within column
9
- * - Live preview shown in the actual status line above
10
- */
11
- import { Container, matchesKey, padding } from "@oh-my-pi/pi-tui";
12
- import type { StatusLineSegmentId } from "../../config/settings-schema";
13
- import { theme } from "../../modes/theme/theme";
14
- import { matchesAppInterrupt } from "../../modes/utils/keybinding-matchers";
15
- import { ALL_SEGMENT_IDS } from "./status-line/segments";
16
-
17
- // Segment display names and short descriptions
18
- const SEGMENT_INFO: Record<StatusLineSegmentId, { label: string; short: string }> = {
19
- pi: { label: "Pi", short: "π icon" },
20
- model: { label: "Model", short: "model name" },
21
- mode: { label: "Mode", short: "plan/loop status" },
22
- path: { label: "Path", short: "working dir" },
23
- git: { label: "Git", short: "branch/status" },
24
- pr: { label: "PR", short: "pull request" },
25
- subagents: { label: "Agents", short: "subagent count" },
26
- token_in: { label: "Tokens In", short: "input tokens" },
27
- token_out: { label: "Tokens Out", short: "output tokens" },
28
- token_total: { label: "Tokens", short: "total tokens" },
29
- token_rate: { label: "Tokens/s", short: "output throughput" },
30
- cost: { label: "Cost", short: "session cost" },
31
- context_pct: { label: "Context %", short: "context usage" },
32
- context_total: { label: "Context", short: "context window" },
33
- time_spent: { label: "Elapsed", short: "session time" },
34
- time: { label: "Clock", short: "current time" },
35
- session: { label: "Session", short: "session ID" },
36
- hostname: { label: "Host", short: "hostname" },
37
- cache_read: { label: "Cache ↑", short: "cache read" },
38
- cache_write: { label: "Cache ↓", short: "cache write" },
39
- session_name: { label: "Session Name", short: "named session" },
40
- };
41
-
42
- type Column = "left" | "right" | "disabled";
43
-
44
- interface SegmentState {
45
- id: StatusLineSegmentId;
46
- column: Column;
47
- order: number;
48
- }
49
-
50
- export interface SegmentEditorCallbacks {
51
- onSave: (leftSegments: StatusLineSegmentId[], rightSegments: StatusLineSegmentId[]) => void;
52
- onCancel: () => void;
53
- onPreview?: (leftSegments: StatusLineSegmentId[], rightSegments: StatusLineSegmentId[]) => void;
54
- }
55
-
56
- export class StatusLineSegmentEditorComponent extends Container {
57
- #segments: SegmentState[];
58
- #selectedIndex: number = 0;
59
- #focusColumn: "left" | "right" | "disabled" = "left";
60
-
61
- constructor(
62
- currentLeft: StatusLineSegmentId[],
63
- currentRight: StatusLineSegmentId[],
64
- private readonly callbacks: SegmentEditorCallbacks,
65
- ) {
66
- super();
67
-
68
- // Initialize segment states
69
- this.#segments = [];
70
- const usedIds = new Set<StatusLineSegmentId>();
71
-
72
- // Add left segments in order
73
- for (let i = 0; i < currentLeft.length; i++) {
74
- const id = currentLeft[i];
75
- this.#segments.push({ id, column: "left", order: i });
76
- usedIds.add(id);
77
- }
78
-
79
- // Add right segments in order
80
- for (let i = 0; i < currentRight.length; i++) {
81
- const id = currentRight[i];
82
- this.#segments.push({ id, column: "right", order: i });
83
- usedIds.add(id);
84
- }
85
-
86
- // Add remaining segments as disabled
87
- for (const id of ALL_SEGMENT_IDS) {
88
- if (!usedIds.has(id)) {
89
- this.#segments.push({ id, column: "disabled", order: 999 });
90
- }
91
- }
92
-
93
- // Trigger initial preview
94
- this.#triggerPreview();
95
- }
96
-
97
- #getSegmentsForColumn(column: Column): SegmentState[] {
98
- return this.#segments.filter(s => s.column === column).sort((a, b) => a.order - b.order);
99
- }
100
-
101
- #getCurrentColumnSegments(): SegmentState[] {
102
- return this.#getSegmentsForColumn(this.#focusColumn);
103
- }
104
-
105
- #triggerPreview(): void {
106
- const left = this.#getSegmentsForColumn("left").map(s => s.id);
107
- const right = this.#getSegmentsForColumn("right").map(s => s.id);
108
- this.callbacks.onPreview?.(left, right);
109
- }
110
-
111
- handleInput(data: string): void {
112
- const columnSegments = this.#getCurrentColumnSegments();
113
-
114
- if (matchesKey(data, "up") || data === "k") {
115
- // Move selection up within column, or jump to previous column
116
- if (this.#selectedIndex > 0) {
117
- this.#selectedIndex--;
118
- } else {
119
- // Jump to previous column
120
- if (this.#focusColumn === "disabled") {
121
- const rightSegs = this.#getSegmentsForColumn("right");
122
- if (rightSegs.length > 0) {
123
- this.#focusColumn = "right";
124
- this.#selectedIndex = rightSegs.length - 1;
125
- } else {
126
- const leftSegs = this.#getSegmentsForColumn("left");
127
- if (leftSegs.length > 0) {
128
- this.#focusColumn = "left";
129
- this.#selectedIndex = leftSegs.length - 1;
130
- }
131
- }
132
- } else if (this.#focusColumn === "right") {
133
- const leftSegs = this.#getSegmentsForColumn("left");
134
- if (leftSegs.length > 0) {
135
- this.#focusColumn = "left";
136
- this.#selectedIndex = leftSegs.length - 1;
137
- }
138
- }
139
- }
140
- } else if (matchesKey(data, "down") || data === "j") {
141
- // Move selection down within column, or jump to next column
142
- if (this.#selectedIndex < columnSegments.length - 1) {
143
- this.#selectedIndex++;
144
- } else {
145
- // Jump to next column
146
- if (this.#focusColumn === "left") {
147
- const rightSegs = this.#getSegmentsForColumn("right");
148
- if (rightSegs.length > 0) {
149
- this.#focusColumn = "right";
150
- this.#selectedIndex = 0;
151
- } else {
152
- const disabledSegs = this.#getSegmentsForColumn("disabled");
153
- if (disabledSegs.length > 0) {
154
- this.#focusColumn = "disabled";
155
- this.#selectedIndex = 0;
156
- }
157
- }
158
- } else if (this.#focusColumn === "right") {
159
- const disabledSegs = this.#getSegmentsForColumn("disabled");
160
- if (disabledSegs.length > 0) {
161
- this.#focusColumn = "disabled";
162
- this.#selectedIndex = 0;
163
- }
164
- }
165
- }
166
- } else if (matchesKey(data, "tab")) {
167
- // Cycle segment: left → right → disabled → left
168
- const seg = columnSegments[this.#selectedIndex];
169
- if (seg) {
170
- const oldColumn = seg.column;
171
- if (seg.column === "left") {
172
- seg.column = "right";
173
- seg.order = this.#getSegmentsForColumn("right").length;
174
- } else if (seg.column === "right") {
175
- seg.column = "disabled";
176
- seg.order = 999;
177
- } else {
178
- seg.column = "left";
179
- seg.order = this.#getSegmentsForColumn("left").length;
180
- }
181
- // Recompact orders in old column
182
- this.#recompactColumn(oldColumn);
183
- this.#triggerPreview();
184
- }
185
- } else if (matchesKey(data, "shift+tab")) {
186
- // Reverse cycle: left ← right ← disabled ← left
187
- const seg = columnSegments[this.#selectedIndex];
188
- if (seg) {
189
- const oldColumn = seg.column;
190
- if (seg.column === "left") {
191
- seg.column = "disabled";
192
- seg.order = 999;
193
- } else if (seg.column === "right") {
194
- seg.column = "left";
195
- seg.order = this.#getSegmentsForColumn("left").length;
196
- } else {
197
- seg.column = "right";
198
- seg.order = this.#getSegmentsForColumn("right").length;
199
- }
200
- this.#recompactColumn(oldColumn);
201
- this.#triggerPreview();
202
- }
203
- } else if (data === " ") {
204
- // Quick toggle: disabled ↔ left
205
- const seg = columnSegments[this.#selectedIndex];
206
- if (seg) {
207
- const oldColumn = seg.column;
208
- if (seg.column === "disabled") {
209
- seg.column = "left";
210
- seg.order = this.#getSegmentsForColumn("left").length;
211
- } else {
212
- seg.column = "disabled";
213
- seg.order = 999;
214
- }
215
- this.#recompactColumn(oldColumn);
216
- this.#triggerPreview();
217
- }
218
- } else if (data === "K") {
219
- // Move segment up in order (Shift+K)
220
- const seg = columnSegments[this.#selectedIndex];
221
- if (seg && seg.column !== "disabled" && this.#selectedIndex > 0) {
222
- const prevSeg = columnSegments[this.#selectedIndex - 1];
223
- const tempOrder = seg.order;
224
- seg.order = prevSeg.order;
225
- prevSeg.order = tempOrder;
226
- this.#selectedIndex--;
227
- this.#triggerPreview();
228
- }
229
- } else if (data === "J") {
230
- // Move segment down in order (Shift+J)
231
- const seg = columnSegments[this.#selectedIndex];
232
- if (seg && seg.column !== "disabled" && this.#selectedIndex < columnSegments.length - 1) {
233
- const nextSeg = columnSegments[this.#selectedIndex + 1];
234
- const tempOrder = seg.order;
235
- seg.order = nextSeg.order;
236
- nextSeg.order = tempOrder;
237
- this.#selectedIndex++;
238
- this.#triggerPreview();
239
- }
240
- } else if (matchesKey(data, "enter") || matchesKey(data, "return") || data === "\n") {
241
- const left = this.#getSegmentsForColumn("left").map(s => s.id);
242
- const right = this.#getSegmentsForColumn("right").map(s => s.id);
243
- this.callbacks.onSave(left, right);
244
- } else if (matchesAppInterrupt(data)) {
245
- this.callbacks.onCancel();
246
- }
247
- }
248
-
249
- #recompactColumn(column: Column): void {
250
- if (column === "disabled") return;
251
- const segs = this.#getSegmentsForColumn(column);
252
- for (let i = 0; i < segs.length; i++) {
253
- segs[i].order = i;
254
- }
255
- }
256
-
257
- render(width: number): string[] {
258
- const lines: string[] = [];
259
-
260
- // Title with live preview indicator
261
- lines.push(theme.bold(theme.fg("accent", "Configure Status Line Segments")));
262
- lines.push(theme.fg("dim", "Live preview shown in status line above"));
263
- lines.push("");
264
-
265
- // Key bindings
266
- lines.push(
267
- theme.fg("muted", "Space") +
268
- " toggle " +
269
- theme.fg("muted", "Tab/S-Tab") +
270
- " cycle column " +
271
- theme.fg("muted", "J/K") +
272
- " reorder " +
273
- theme.fg("muted", "Enter") +
274
- " save " +
275
- theme.fg("muted", "Esc") +
276
- " cancel",
277
- );
278
- lines.push("");
279
-
280
- // Get segments for each column
281
- const leftSegs = this.#getSegmentsForColumn("left");
282
- const rightSegs = this.#getSegmentsForColumn("right");
283
- const disabledSegs = this.#getSegmentsForColumn("disabled");
284
-
285
- // Calculate column widths
286
- const colWidth = Math.max(18, Math.floor((width - 6) / 3));
287
-
288
- // Column headers
289
- const activeMarker = theme.nav.back;
290
- const leftHeader =
291
- this.#focusColumn === "left"
292
- ? theme.bold(theme.fg("accent", `${activeMarker} LEFT`))
293
- : theme.fg("muted", " LEFT");
294
- const rightHeader =
295
- this.#focusColumn === "right"
296
- ? theme.bold(theme.fg("accent", `${activeMarker} RIGHT`))
297
- : theme.fg("muted", " RIGHT");
298
- const disabledHeader =
299
- this.#focusColumn === "disabled"
300
- ? theme.bold(theme.fg("accent", `${activeMarker} AVAILABLE`))
301
- : theme.fg("muted", " AVAILABLE");
302
-
303
- lines.push(`${leftHeader.padEnd(colWidth + 8)}${rightHeader.padEnd(colWidth + 8)}${disabledHeader}`);
304
- lines.push(theme.fg("dim", theme.boxRound.horizontal.repeat(Math.min(width - 2, colWidth * 3 + 6))));
305
-
306
- // Render rows
307
- const maxRows = Math.max(leftSegs.length, rightSegs.length, disabledSegs.length, 1);
308
-
309
- for (let row = 0; row < maxRows; row++) {
310
- let line = "";
311
-
312
- // Left column
313
- line += this.#renderSegmentCell(leftSegs[row], "left", row, colWidth);
314
-
315
- // Right column
316
- line += this.#renderSegmentCell(rightSegs[row], "right", row, colWidth);
317
-
318
- // Disabled column
319
- line += this.#renderSegmentCell(disabledSegs[row], "disabled", row, colWidth);
320
-
321
- lines.push(line);
322
- }
323
-
324
- // Summary line
325
- lines.push("");
326
- const leftCount = leftSegs.length;
327
- const rightCount = rightSegs.length;
328
- const summary = theme.fg(
329
- "dim",
330
- `${leftCount} left ${theme.sep.dot} ${rightCount} right ${theme.sep.dot} ${disabledSegs.length} available`,
331
- );
332
- lines.push(summary);
333
-
334
- return lines;
335
- }
336
-
337
- #renderSegmentCell(seg: SegmentState | undefined, column: Column, row: number, colWidth: number): string {
338
- if (!seg) {
339
- return "".padEnd(colWidth + 2);
340
- }
341
-
342
- const isSelected = this.#focusColumn === column && this.#selectedIndex === row;
343
- const info = SEGMENT_INFO[seg.id];
344
- const label = info?.label ?? seg.id;
345
-
346
- let text: string;
347
- if (isSelected) {
348
- text = theme.bg("selectedBg", theme.fg("text", ` ${label} `));
349
- } else if (column === "disabled") {
350
- text = theme.fg("dim", ` ${label}`);
351
- } else {
352
- text = theme.fg("text", ` ${label}`);
353
- }
354
-
355
- // Pad to column width (accounting for ANSI codes)
356
- const padSize = colWidth - label.length - 1;
357
- return text + padding(Math.max(0, padSize));
358
- }
359
- }