@oh-my-pi/pi-coding-agent 16.0.11 → 16.1.1

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 (71) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/dist/cli.js +3166 -3202
  3. package/dist/types/config/settings-schema.d.ts +40 -39
  4. package/dist/types/lsp/types.d.ts +5 -3
  5. package/dist/types/modes/components/__tests__/skill-message.test.d.ts +1 -0
  6. package/dist/types/modes/components/assistant-message.d.ts +8 -0
  7. package/dist/types/modes/components/cache-invalidation-marker.d.ts +39 -0
  8. package/dist/types/modes/components/compaction-summary-message.d.ts +14 -1
  9. package/dist/types/modes/components/index.d.ts +0 -1
  10. package/dist/types/modes/components/message-frame.d.ts +6 -4
  11. package/dist/types/modes/interactive-mode.d.ts +2 -1
  12. package/dist/types/modes/theme/theme.d.ts +7 -1
  13. package/dist/types/modes/types.d.ts +7 -1
  14. package/dist/types/sdk.d.ts +1 -1
  15. package/dist/types/session/agent-session.d.ts +20 -1
  16. package/dist/types/session/session-context.d.ts +7 -0
  17. package/dist/types/session/session-dump-format.d.ts +1 -0
  18. package/dist/types/session/tool-choice-queue.d.ts +14 -0
  19. package/dist/types/system-prompt.d.ts +3 -3
  20. package/dist/types/tools/index.d.ts +4 -0
  21. package/dist/types/tools/resolve.d.ts +15 -5
  22. package/package.json +12 -12
  23. package/src/config/settings-schema.ts +48 -39
  24. package/src/config/settings.ts +40 -0
  25. package/src/debug/log-viewer.ts +4 -4
  26. package/src/debug/raw-sse.ts +4 -4
  27. package/src/edit/renderer.ts +2 -2
  28. package/src/internal-urls/docs-index.generated.txt +1 -1
  29. package/src/lsp/client.ts +9 -9
  30. package/src/lsp/render.ts +7 -7
  31. package/src/lsp/types.ts +6 -3
  32. package/src/modes/components/__tests__/skill-message.test.ts +92 -0
  33. package/src/modes/components/agent-dashboard.ts +1 -1
  34. package/src/modes/components/assistant-message.ts +21 -0
  35. package/src/modes/components/cache-invalidation-marker.ts +94 -0
  36. package/src/modes/components/chat-transcript-builder.ts +16 -2
  37. package/src/modes/components/compaction-summary-message.ts +29 -1
  38. package/src/modes/components/custom-message.ts +4 -1
  39. package/src/modes/components/dynamic-border.ts +1 -1
  40. package/src/modes/components/extensions/extension-dashboard.ts +1 -1
  41. package/src/modes/components/extensions/inspector-panel.ts +5 -5
  42. package/src/modes/components/hook-selector.ts +2 -2
  43. package/src/modes/components/index.ts +0 -1
  44. package/src/modes/components/message-frame.ts +10 -6
  45. package/src/modes/components/model-selector.ts +2 -2
  46. package/src/modes/components/overlay-box.ts +10 -9
  47. package/src/modes/components/settings-defs.ts +7 -0
  48. package/src/modes/components/skill-message.ts +39 -19
  49. package/src/modes/components/tiny-title-download-progress.ts +1 -1
  50. package/src/modes/components/welcome.ts +1 -1
  51. package/src/modes/controllers/event-controller.ts +14 -0
  52. package/src/modes/controllers/selector-controller.ts +7 -0
  53. package/src/modes/interactive-mode.ts +9 -1
  54. package/src/modes/theme/theme.ts +14 -0
  55. package/src/modes/types.ts +7 -1
  56. package/src/modes/utils/ui-helpers.ts +20 -2
  57. package/src/prompts/steering/user-interjection.md +3 -4
  58. package/src/sdk.ts +8 -6
  59. package/src/session/agent-session.ts +96 -23
  60. package/src/session/messages.ts +7 -9
  61. package/src/session/session-context.ts +54 -7
  62. package/src/session/session-dump-format.ts +3 -1
  63. package/src/session/snapcompact-inline.ts +2 -2
  64. package/src/session/tool-choice-queue.ts +59 -0
  65. package/src/system-prompt.ts +10 -9
  66. package/src/tools/bash-interactive.ts +4 -4
  67. package/src/tools/index.ts +4 -0
  68. package/src/tools/resolve.ts +66 -41
  69. package/src/tui/output-block.ts +9 -9
  70. package/dist/types/modes/components/branch-summary-message.d.ts +0 -13
  71. package/src/modes/components/branch-summary-message.ts +0 -46
@@ -1,4 +1,10 @@
1
- import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
1
+ import type {
2
+ AgentTool,
3
+ AgentToolContext,
4
+ AgentToolResult,
5
+ AgentToolUpdateCallback,
6
+ CustomMessage,
7
+ } from "@oh-my-pi/pi-agent-core";
2
8
  import type { Component } from "@oh-my-pi/pi-tui";
3
9
  import { Text } from "@oh-my-pi/pi-tui";
4
10
  import { prompt, untilAborted } from "@oh-my-pi/pi-utils";
@@ -28,11 +34,18 @@ export interface ResolveToolDetails {
28
34
  sourceResultDetails?: unknown;
29
35
  }
30
36
 
37
+ /** Monotonic suffix making each staged preview's pending-invoker id UNIQUE, so
38
+ * stacked previews never clobber one another by label. */
39
+ let pendingPreviewSeq = 0;
40
+
31
41
  /**
32
- * Queue a resolve-protocol handler on the tool-choice queue. Forces the next
33
- * LLM call to invoke the hidden `resolve` tool, wraps the caller's apply/reject
34
- * callbacks into an onInvoked closure that matches the resolve schema, and
35
- * steers a preview reminder so the model understands why.
42
+ * Register a non-forcing resolve-protocol handler for a staged preview. Wraps the
43
+ * caller's apply/reject into an onInvoked closure (matching the resolve schema) and
44
+ * stores it on the tool-choice queue's pending-invoker registry under a UNIQUE id.
45
+ * The `resolve` tool dispatches to it; the agent-loop's SoftToolRequirement
46
+ * lifecycle injects the preview reminder and escalates to a forced `resolve` only
47
+ * if the model declines — so a compliant turn pays ZERO tool_choice change (no
48
+ * prompt-cache messages-cache invalidation).
36
49
  *
37
50
  * This is the canonical entry point for any tool that wants preview/apply
38
51
  * semantics. No session-level abstraction is needed: callers pass their
@@ -48,46 +61,55 @@ export function queueResolveHandler(
48
61
  },
49
62
  ): void {
50
63
  const queue = session.getToolChoiceQueue?.();
51
- const forced = session.buildToolChoice?.("resolve");
52
- if (!queue || !forced || typeof forced === "string") return;
64
+ if (!queue) return;
53
65
 
54
- const steerReminder = (): void => {
55
- session.steer?.({
56
- customType: "resolve-reminder",
57
- content: [
58
- "<system-reminder>",
59
- "This is a preview. Call the `resolve` tool to apply or discard these changes.",
60
- "</system-reminder>",
61
- ].join("\n"),
62
- details: { toolName: options.sourceToolName },
63
- });
64
- };
66
+ // Unique per preview: stacked/sequential previews each get their own entry.
67
+ const id = `pending-action:${options.sourceToolName}:${pendingPreviewSeq++}`;
65
68
 
66
- const pushDirective = (): void => {
67
- queue.pushOnce(forced, {
68
- label: `pending-action:${options.sourceToolName}`,
69
- now: true,
70
- onRejected: () => "requeue",
71
- onInvoked: async (input: unknown) =>
72
- runResolveInvocation(input as ResolveParams, {
73
- sourceToolName: options.sourceToolName,
74
- label: options.label,
75
- apply: options.apply,
76
- reject: options.reject,
77
- onApplyError: () => {
78
- // Apply threw (e.g. ast_edit overlapping replacements). Re-push the
79
- // same directive so the preview remains pending and the model can
80
- // `discard` or fix-and-retry on the next turn instead of being
81
- // stranded with no pending action to address.
82
- pushDirective();
83
- steerReminder();
84
- },
85
- }),
69
+ const onInvoked = async (input: unknown): Promise<AgentToolResult<unknown>> => {
70
+ const result = await runResolveInvocation(input as ResolveParams, {
71
+ sourceToolName: options.sourceToolName,
72
+ label: options.label,
73
+ apply: options.apply,
74
+ reject: options.reject,
75
+ onApplyError: () => {
76
+ // Apply threw (e.g. ast_edit overlapping replacements). Keep the preview
77
+ // pending under the SAME id so the model can `discard` or fix-and-retry;
78
+ // runResolveInvocation rethrows, so the success-path removal below is skipped.
79
+ queue.registerPendingInvoker(id, options.sourceToolName, onInvoked);
80
+ },
86
81
  });
82
+ // Resolved (apply succeeded, or discard): consume the staged action exactly once.
83
+ queue.removePendingInvoker(id);
84
+ return result;
87
85
  };
88
86
 
89
- pushDirective();
90
- steerReminder();
87
+ // NON-FORCING: register so `resolve` can dispatch here WITHOUT changing
88
+ // tool_choice. The agent-loop injects the reminder (from the SoftToolRequirement
89
+ // the session builds) and forces a resolve turn only on non-compliance.
90
+ queue.registerPendingInvoker(id, options.sourceToolName, onInvoked);
91
+ }
92
+
93
+ /**
94
+ * The canonical preview reminder. The resolve mechanism owns the wording; the
95
+ * agent-loop delivers it via the session's `SoftToolRequirement.reminder` (injected
96
+ * once per pending-preview head) instead of a host-side steer, so it lands as a
97
+ * stable mid-history append and never churns the cached prefix.
98
+ */
99
+ export function buildResolveReminderMessage(sourceToolName: string): CustomMessage {
100
+ return {
101
+ role: "custom",
102
+ customType: "resolve-reminder",
103
+ content: [
104
+ "<system-reminder>",
105
+ "This is a preview. Call the `resolve` tool to apply or discard these changes.",
106
+ "</system-reminder>",
107
+ ].join("\n"),
108
+ display: false,
109
+ details: { toolName: sourceToolName },
110
+ attribution: "agent",
111
+ timestamp: Date.now(),
112
+ };
91
113
  }
92
114
 
93
115
  /**
@@ -185,7 +207,10 @@ export class ResolveTool implements AgentTool<typeof resolveSchema, ResolveToolD
185
207
  _context?: AgentToolContext,
186
208
  ): Promise<AgentToolResult<ResolveToolDetails>> {
187
209
  return untilAborted(signal, async () => {
188
- const invoker = this.session.peekQueueInvoker?.() ?? this.session.peekStandingResolveHandler?.();
210
+ const invoker =
211
+ this.session.peekQueueInvoker?.() ??
212
+ this.session.peekPendingInvoker?.() ??
213
+ this.session.peekStandingResolveHandler?.();
189
214
  if (!invoker) {
190
215
  // `discard` is a request to cancel/abort a staged action. When nothing is
191
216
  // pending, the desired end-state (no staged change) already holds, so honor
@@ -48,8 +48,8 @@ function normalizeContentPaddingLeft(value: number | undefined): number {
48
48
 
49
49
  export function renderOutputBlock(options: OutputBlockOptions, theme: Theme): string[] {
50
50
  const { header, headerMeta, state, sections = [], width, applyBg = true } = options;
51
- const h = theme.boxSharp.horizontal;
52
- const v = theme.boxSharp.vertical;
51
+ const h = theme.boxRound.horizontal;
52
+ const v = theme.boxRound.vertical;
53
53
  const cap = h.repeat(3);
54
54
  const lineWidth = Math.max(0, width);
55
55
  // Border colors: running/pending use accent, success uses dim (gray), error/warning keep their colors
@@ -84,8 +84,8 @@ export function renderOutputBlock(options: OutputBlockOptions, theme: Theme): st
84
84
  const rows: BlockRow[] = [];
85
85
  rows.push({
86
86
  kind: "bar",
87
- leftChar: theme.boxSharp.topLeft,
88
- rightChar: theme.boxSharp.topRight,
87
+ leftChar: theme.boxRound.topLeft,
88
+ rightChar: theme.boxRound.topRight,
89
89
  label: header,
90
90
  meta: headerMeta,
91
91
  });
@@ -99,15 +99,15 @@ export function renderOutputBlock(options: OutputBlockOptions, theme: Theme): st
99
99
  if (section.label) {
100
100
  rows.push({
101
101
  kind: "bar",
102
- leftChar: theme.boxSharp.teeRight,
103
- rightChar: theme.boxSharp.teeLeft,
102
+ leftChar: theme.boxRound.teeRight,
103
+ rightChar: theme.boxRound.teeLeft,
104
104
  label: section.label,
105
105
  });
106
106
  } else if (section.separator && sectionIndex > 0) {
107
107
  rows.push({
108
108
  kind: "bar",
109
- leftChar: theme.boxSharp.teeRight,
110
- rightChar: theme.boxSharp.teeLeft,
109
+ leftChar: theme.boxRound.teeRight,
110
+ rightChar: theme.boxRound.teeLeft,
111
111
  });
112
112
  }
113
113
  const allLines = section.lines.flatMap(l => l.split("\n"));
@@ -126,7 +126,7 @@ export function renderOutputBlock(options: OutputBlockOptions, theme: Theme): st
126
126
  }
127
127
  }
128
128
 
129
- rows.push({ kind: "bottom", leftChar: theme.boxSharp.bottomLeft, rightChar: theme.boxSharp.bottomRight });
129
+ rows.push({ kind: "bottom", leftChar: theme.boxRound.bottomLeft, rightChar: theme.boxRound.bottomRight });
130
130
 
131
131
  const H = rows.length;
132
132
 
@@ -1,13 +0,0 @@
1
- import { Box } from "@oh-my-pi/pi-tui";
2
- import type { BranchSummaryMessage } from "../../session/messages";
3
- /**
4
- * Component that renders a branch summary message with collapsed/expanded state.
5
- * Uses same background color as hook messages for visual consistency.
6
- */
7
- export declare class BranchSummaryMessageComponent extends Box {
8
- #private;
9
- private readonly message;
10
- constructor(message: BranchSummaryMessage);
11
- setExpanded(expanded: boolean): void;
12
- invalidate(): void;
13
- }
@@ -1,46 +0,0 @@
1
- import { Box, Markdown, Spacer, Text } from "@oh-my-pi/pi-tui";
2
- import { getMarkdownTheme, theme } from "../../modes/theme/theme";
3
- import type { BranchSummaryMessage } from "../../session/messages";
4
-
5
- /**
6
- * Component that renders a branch summary message with collapsed/expanded state.
7
- * Uses same background color as hook messages for visual consistency.
8
- */
9
- export class BranchSummaryMessageComponent extends Box {
10
- #expanded = false;
11
-
12
- constructor(private readonly message: BranchSummaryMessage) {
13
- super(1, 1, t => theme.bg("customMessageBg", t));
14
- this.setIgnoreTight(true);
15
- this.#updateDisplay();
16
- }
17
-
18
- setExpanded(expanded: boolean): void {
19
- this.#expanded = expanded;
20
- this.#updateDisplay();
21
- }
22
-
23
- override invalidate(): void {
24
- super.invalidate();
25
- this.#updateDisplay();
26
- }
27
-
28
- #updateDisplay(): void {
29
- this.clear();
30
-
31
- const label = theme.fg("customMessageLabel", theme.bold("[branch]"));
32
- this.addChild(new Text(label, 0, 0));
33
- this.addChild(new Spacer(1));
34
-
35
- if (this.#expanded) {
36
- const header = "**Branch Summary**\n\n";
37
- this.addChild(
38
- new Markdown(header + this.message.summary, 0, 0, getMarkdownTheme(), {
39
- color: (text: string) => theme.fg("customMessageText", text),
40
- }),
41
- );
42
- } else {
43
- this.addChild(new Text(theme.fg("customMessageText", "Branch summary (ctrl+o to expand)"), 0, 0));
44
- }
45
- }
46
- }