@oh-my-pi/pi-coding-agent 15.7.3 → 15.7.5

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 (55) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/dist/types/config/settings-schema.d.ts +3 -22
  3. package/dist/types/extensibility/custom-tools/types.d.ts +2 -2
  4. package/dist/types/extensibility/shared-events.d.ts +2 -2
  5. package/dist/types/internal-urls/local-protocol.d.ts +19 -9
  6. package/dist/types/internal-urls/types.d.ts +14 -0
  7. package/dist/types/lsp/client.d.ts +3 -0
  8. package/dist/types/mcp/manager.d.ts +14 -5
  9. package/dist/types/modes/controllers/command-controller.d.ts +2 -3
  10. package/dist/types/session/agent-session.d.ts +2 -6
  11. package/dist/types/session/shake-types.d.ts +3 -3
  12. package/dist/types/task/repair-args.d.ts +52 -0
  13. package/dist/types/tiny/models.d.ts +0 -14
  14. package/dist/types/tiny/title-client.d.ts +28 -2
  15. package/dist/types/tiny/title-protocol.d.ts +8 -9
  16. package/dist/types/tools/find.d.ts +1 -1
  17. package/dist/types/tools/path-utils.d.ts +7 -0
  18. package/dist/types/tui/output-block.d.ts +7 -7
  19. package/package.json +9 -9
  20. package/scripts/build-binary.ts +0 -1
  21. package/src/cli.ts +59 -0
  22. package/src/config/settings-schema.ts +3 -24
  23. package/src/config/settings.ts +10 -0
  24. package/src/extensibility/custom-tools/types.ts +2 -2
  25. package/src/extensibility/shared-events.ts +2 -2
  26. package/src/internal-urls/docs-index.generated.ts +2 -2
  27. package/src/internal-urls/local-protocol.ts +23 -11
  28. package/src/internal-urls/types.ts +15 -0
  29. package/src/lsp/client.ts +28 -5
  30. package/src/mcp/manager.ts +87 -4
  31. package/src/modes/controllers/command-controller.ts +7 -39
  32. package/src/modes/controllers/event-controller.ts +33 -26
  33. package/src/modes/controllers/mcp-command-controller.ts +1 -1
  34. package/src/prompts/system/project-prompt.md +3 -2
  35. package/src/prompts/system/subagent-system-prompt.md +12 -8
  36. package/src/prompts/system/system-prompt.md +8 -6
  37. package/src/session/agent-session.ts +11 -93
  38. package/src/session/shake-types.ts +4 -5
  39. package/src/slash-commands/builtin-registry.ts +2 -4
  40. package/src/task/executor.ts +14 -4
  41. package/src/task/index.ts +3 -2
  42. package/src/task/repair-args.ts +117 -0
  43. package/src/tiny/models.ts +0 -28
  44. package/src/tiny/title-client.ts +133 -43
  45. package/src/tiny/title-protocol.ts +11 -16
  46. package/src/tiny/worker.ts +6 -61
  47. package/src/tools/ast-edit.ts +3 -0
  48. package/src/tools/ast-grep.ts +3 -0
  49. package/src/tools/find.ts +20 -6
  50. package/src/tools/gh.ts +1 -0
  51. package/src/tools/path-utils.ts +13 -2
  52. package/src/tools/read.ts +1 -0
  53. package/src/tools/search.ts +12 -1
  54. package/src/tui/output-block.ts +37 -75
  55. package/src/utils/git.ts +9 -3
@@ -20,10 +20,10 @@ export interface OutputBlockOptions {
20
20
  }
21
21
 
22
22
  const BORDER_SHIMMER_TICK_MS = 50;
23
- /** Duration of one full clockwise lap around the border, in ms. Fixed so a box
24
- * growing (new output line) or resizing only nudges the segment proportionally
25
- * instead of teleporting it. */
26
- const BORDER_LAP_MS = 4000;
23
+ /** Duration of one full left↔right↔left bounce of the bottom-edge segment, in
24
+ * ms. Position is derived from the wall clock against this fixed cycle so a
25
+ * resize only nudges the segment proportionally instead of teleporting it. */
26
+ const BORDER_BOUNCE_MS = 3000;
27
27
  /** Length, in border cells, of the moving segment. */
28
28
  const BORDER_SEGMENT_LEN = 8;
29
29
 
@@ -36,38 +36,25 @@ export function borderShimmerTick(): number {
36
36
  return Math.floor(Date.now() / BORDER_SHIMMER_TICK_MS);
37
37
  }
38
38
 
39
- /** Ease-in-out so the segment decelerates into and accelerates out of corners. */
39
+ /** Ease-in-out so the segment decelerates into and accelerates out of each wall. */
40
40
  function easeInOutQuad(t: number): number {
41
41
  return t < 0.5 ? 2 * t * t : 1 - (-2 * t + 2) ** 2 / 2;
42
42
  }
43
43
 
44
44
  /**
45
- * Perimeter index of the moving segment's head for a box of inner width `W` and
46
- * height `H` at time `now`. The lap is split across the four edges in proportion
47
- * to their length (so the average speed is uniform) and each edge is eased, for a
48
- * deliberate, non-linear glide that slows at every corner. Position is derived
49
- * from the wall clock against a fixed lap duration, so a perimeter change (new
50
- * row / resize) shifts the head by at most a cell or two — no reset.
45
+ * Column of the travelling segment's center on the bottom edge for a box of
46
+ * inner width `W` at time `now`. The segment bounces left right left across
47
+ * the bottom border: a triangle wave over one full there-and-back cycle, eased
48
+ * per leg so it slows as it nears each wall before reversing. Position is
49
+ * derived from the wall clock against a fixed cycle, so a resize shifts the
50
+ * center proportionally — no reset.
51
51
  */
52
- export function borderSegmentHead(W: number, H: number, now: number): number {
53
- const P = 2 * W + 2 * H - 4;
54
- if (P <= 0) return 0;
55
- // Edge cell counts, clockwise from top-left: top, right, bottom, left.
56
- const edgeLens = [W, H - 1, W - 1, H - 2];
57
- const t = (((now % BORDER_LAP_MS) + BORDER_LAP_MS) % BORDER_LAP_MS) / BORDER_LAP_MS;
58
- let acc = 0;
59
- let start = 0;
60
- for (let i = 0; i < 4; i++) {
61
- const len = edgeLens[i]!;
62
- const frac = len / P;
63
- if (len > 0 && t < acc + frac) {
64
- const lf = (t - acc) / frac;
65
- return (start + Math.floor(easeInOutQuad(lf) * len)) % P;
66
- }
67
- acc += frac;
68
- start += len;
69
- }
70
- return P - 1;
52
+ export function borderSegmentHeadCol(W: number, now: number): number {
53
+ if (W <= 1) return 0;
54
+ const phase = (((now % BORDER_BOUNCE_MS) + BORDER_BOUNCE_MS) % BORDER_BOUNCE_MS) / BORDER_BOUNCE_MS;
55
+ // Triangle: 0→1 rightward over the first half, 1→0 leftward over the second.
56
+ const leg = phase < 0.5 ? phase * 2 : 2 - phase * 2;
57
+ return easeInOutQuad(leg) * (W - 1);
71
58
  }
72
59
 
73
60
  /**
@@ -164,30 +151,26 @@ export function renderOutputBlock(options: OutputBlockOptions, theme: Theme): st
164
151
  const W = lineWidth;
165
152
  const animate = (options.animate ?? false) && (state === "running" || state === "pending") && W >= 2 && H >= 2;
166
153
 
167
- // ── Segment geometry: one dark run travels the outer edge clockwise,
168
- // top right bottom left top. ──
169
- const P = animate ? 2 * W + 2 * H - 4 : 0;
170
- const segLen = Math.min(BORDER_SEGMENT_LEN, P);
171
- const head = animate ? borderSegmentHead(W, H, Date.now()) : 0;
154
+ // ── Segment geometry: one dark run bounces left right along the bottom
155
+ // edge only. The top, interior separators, and side borders stay the flat
156
+ // accent color. ──
157
+ const segLen = animate ? Math.min(BORDER_SEGMENT_LEN, W) : 0;
158
+ const head = animate ? borderSegmentHeadCol(W, Date.now()) : 0;
159
+ const segHalf = segLen / 2;
172
160
  const segAnsi = animate ? (darkenFgAnsi(theme.getFgAnsi(borderColor), 0.4) ?? theme.getFgAnsi("borderMuted")) : "";
173
161
  const seg = (text: string) => `${segAnsi}${text}\x1b[39m`;
174
162
 
175
- // Perimeter index of border cell (row r, col c), clockwise from top-left.
176
- const perimIndex = (r: number, c: number): number => {
177
- if (r === 0) return c;
178
- if (c === W - 1) return W - 1 + r;
179
- if (r === H - 1) return W - 1 + (H - 1) + (W - 1 - c);
180
- return W - 1 + (H - 1) + (W - 1) + (H - 1 - r);
181
- };
182
- const isLit = (idx: number): boolean => (((idx - head) % P) + P) % P < segLen;
183
- // Color a run of border glyphs starting at (row r, col startCol), grouping
163
+ // A bottom-edge column is lit when it lies within half a segment of the
164
+ // travelling center.
165
+ const isLit = (col: number): boolean => Math.abs(col - head) < segHalf;
166
+ // Color a run of bottom-edge glyphs starting at column `startCol`, grouping
184
167
  // consecutive same-state cells so each run emits a single escape pair.
185
- const colorEdge = (glyphs: string, r: number, startCol: number): string => {
168
+ const colorEdge = (glyphs: string, startCol: number): string => {
186
169
  let out = "";
187
170
  let runLit: boolean | null = null;
188
171
  let buf = "";
189
172
  for (let i = 0; i < glyphs.length; i++) {
190
- const lit = isLit(perimIndex(r, startCol + i));
173
+ const lit = isLit(startCol + i);
191
174
  if (lit !== runLit) {
192
175
  if (runLit !== null) out += (runLit ? seg : border)(buf);
193
176
  buf = "";
@@ -199,10 +182,7 @@ export function renderOutputBlock(options: OutputBlockOptions, theme: Theme): st
199
182
  return out;
200
183
  };
201
184
 
202
- const renderBar = (
203
- row: { leftChar: string; rightChar: string; label?: string; meta?: string },
204
- r: number,
205
- ): string => {
185
+ const renderBar = (row: { leftChar: string; rightChar: string; label?: string; meta?: string }): string => {
206
186
  const leftGlyphs = `${row.leftChar}${cap}`;
207
187
  const rightGlyph = row.rightChar;
208
188
  if (lineWidth <= 0) return border(leftGlyphs) + border(rightGlyph);
@@ -215,36 +195,22 @@ export function renderOutputBlock(options: OutputBlockOptions, theme: Theme): st
215
195
  const labelWidth = visibleWidth(trimmedLabel);
216
196
  const fillCount = Math.max(0, lineWidth - leftWidth - labelWidth - rightWidth);
217
197
  const fillGlyphs = h.repeat(fillCount);
218
- if (!animate) {
219
- return `${border(leftGlyphs)}${trimmedLabel}${border(fillGlyphs)}${border(rightGlyph)}`;
220
- }
221
- if (r === 0 || r === H - 1) {
222
- // Top/bottom edge: the whole horizontal run lies on the perimeter.
223
- const leftStr = colorEdge(leftGlyphs, r, 0);
224
- const fillStr = colorEdge(fillGlyphs, r, leftWidth + labelWidth);
225
- const rightStr = colorEdge(rightGlyph, r, lineWidth - rightWidth);
226
- return `${leftStr}${trimmedLabel}${fillStr}${rightStr}`;
227
- }
228
- // Interior separator: only the first/last cell sit on the outer edge.
229
- return `${colorEdge(row.leftChar, r, 0)}${border(cap)}${trimmedLabel}${border(fillGlyphs)}${colorEdge(rightGlyph, r, lineWidth - rightWidth)}`;
198
+ return `${border(leftGlyphs)}${trimmedLabel}${border(fillGlyphs)}${border(rightGlyph)}`;
230
199
  };
231
200
 
232
- const renderBottom = (row: { leftChar: string; rightChar: string }, r: number): string => {
201
+ const renderBottom = (row: { leftChar: string; rightChar: string }): string => {
233
202
  const leftGlyphs = `${row.leftChar}${cap}`;
234
203
  const rightGlyph = row.rightChar;
235
204
  const fillCount = Math.max(0, lineWidth - visibleWidth(leftGlyphs) - visibleWidth(rightGlyph));
236
205
  const fillGlyphs = h.repeat(fillCount);
237
206
  if (!animate) return `${border(leftGlyphs)}${border(fillGlyphs)}${border(rightGlyph)}`;
238
- const leftStr = colorEdge(leftGlyphs, r, 0);
239
- const fillStr = colorEdge(fillGlyphs, r, visibleWidth(leftGlyphs));
240
- const rightStr = colorEdge(rightGlyph, r, lineWidth - visibleWidth(rightGlyph));
207
+ const leftStr = colorEdge(leftGlyphs, 0);
208
+ const fillStr = colorEdge(fillGlyphs, visibleWidth(leftGlyphs));
209
+ const rightStr = colorEdge(rightGlyph, lineWidth - visibleWidth(rightGlyph));
241
210
  return `${leftStr}${fillStr}${rightStr}`;
242
211
  };
243
212
 
244
- const renderContent = (inner: string, r: number): string => {
245
- if (!animate) return `${border(`${v} `)}${inner}${border(v)}`;
246
- return `${colorEdge(v, r, 0)} ${inner}${colorEdge(v, r, lineWidth - 1)}`;
247
- };
213
+ const renderContent = (inner: string): string => `${border(`${v} `)}${inner}${border(v)}`;
248
214
 
249
215
  const lines: string[] = [];
250
216
  for (let r = 0; r < H; r++) {
@@ -254,11 +220,7 @@ export function renderOutputBlock(options: OutputBlockOptions, theme: Theme): st
254
220
  continue;
255
221
  }
256
222
  const line =
257
- row.kind === "bar"
258
- ? renderBar(row, r)
259
- : row.kind === "bottom"
260
- ? renderBottom(row, r)
261
- : renderContent(row.inner, r);
223
+ row.kind === "bar" ? renderBar(row) : row.kind === "bottom" ? renderBottom(row) : renderContent(row.inner);
262
224
  lines.push(padToWidth(line, lineWidth, bgFn));
263
225
  }
264
226
 
package/src/utils/git.ts CHANGED
@@ -1261,9 +1261,15 @@ export async function clone(url: string, targetDir: string, options: CloneOption
1261
1261
  const absoluteTarget = path.resolve(targetDir);
1262
1262
  await fs.promises.mkdir(path.dirname(absoluteTarget), { recursive: true });
1263
1263
 
1264
- const args = ["clone", "--depth", "1"];
1264
+ // `git clone --depth 1 --single-branch` only fetches the tip of the target
1265
+ // branch, so any subsequent `git checkout <sha>` for a non-tip commit fails
1266
+ // with "reference is not a tree". When the caller pinned a specific SHA we
1267
+ // fall back to a full clone so the object is guaranteed to be present.
1268
+ const shallow = !options.sha;
1269
+ const args = ["clone"];
1270
+ if (shallow) args.push("--depth", "1");
1265
1271
  if (options.ref) args.push("--branch", options.ref, "--single-branch");
1266
- else args.push("--single-branch");
1272
+ else if (shallow) args.push("--single-branch");
1267
1273
  args.push(url, absoluteTarget);
1268
1274
 
1269
1275
  try {
@@ -1273,7 +1279,7 @@ export async function clone(url: string, targetDir: string, options: CloneOption
1273
1279
  await checkout(absoluteTarget, options.sha, options.signal);
1274
1280
  } catch {
1275
1281
  await fs.promises.rm(absoluteTarget, { force: true, recursive: true });
1276
- throw new Error(`Failed to checkout SHA ${options.sha} - shallow clone may not contain this commit`);
1282
+ throw new Error(`Failed to checkout SHA ${options.sha} in cloned repository ${url}`);
1277
1283
  }
1278
1284
  }
1279
1285
  } catch (err) {