@oh-my-pi/pi-coding-agent 16.0.8 → 16.0.9

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.
@@ -42,6 +42,7 @@ export interface Args {
42
42
  noExtensions?: boolean;
43
43
  pluginDirs?: string[];
44
44
  print?: boolean;
45
+ printThoughts?: boolean;
45
46
  export?: string;
46
47
  noSkills?: boolean;
47
48
  skills?: string[];
@@ -127,6 +127,9 @@ export default class Index extends Command {
127
127
  "no-title": import("@oh-my-pi/pi-utils/cli").FlagDescriptor<"boolean"> & {
128
128
  description: string;
129
129
  };
130
+ "print-thoughts": import("@oh-my-pi/pi-utils/cli").FlagDescriptor<"boolean"> & {
131
+ description: string;
132
+ };
130
133
  "max-time": import("@oh-my-pi/pi-utils/cli").FlagDescriptor<"string"> & {
131
134
  description: string;
132
135
  };
@@ -36,6 +36,7 @@ export declare class GoalRuntime {
36
36
  #private;
37
37
  constructor(host: GoalRuntimeHost);
38
38
  get snapshot(): GoalRuntimeSnapshot;
39
+ clearAccounting(): void;
39
40
  onTurnStart(turnId: string, baselineUsage: GoalTokenUsage): void;
40
41
  onToolCompleted(toolName: string): Promise<void>;
41
42
  onGoalToolCompleted(): Promise<void>;
@@ -46,7 +47,9 @@ export declare class GoalRuntime {
46
47
  onTaskAborted(options?: {
47
48
  reason?: "interrupted" | "internal";
48
49
  }): Promise<void>;
49
- onThreadResumed(): Promise<GoalModeState | undefined>;
50
+ onThreadResumed(options?: {
51
+ preserveActiveGoal?: boolean;
52
+ }): Promise<GoalModeState | undefined>;
50
53
  onBudgetMutated(newBudget: number | undefined): Promise<GoalModeState | undefined>;
51
54
  flushUsage(steering: GoalBudgetSteering, currentUsage?: GoalTokenUsage): Promise<void>;
52
55
  createGoal(input: {
@@ -19,6 +19,8 @@ export interface PrintModeOptions {
19
19
  initialMessage?: string;
20
20
  /** Images to attach to the initial message */
21
21
  initialImages?: ImageContent[];
22
+ /** If true, include thinking blocks in text output */
23
+ printThoughts?: boolean;
22
24
  }
23
25
  /**
24
26
  * Run in print (single-shot) mode.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@oh-my-pi/pi-coding-agent",
4
- "version": "16.0.8",
4
+ "version": "16.0.9",
5
5
  "description": "Coding agent CLI with read, bash, edit, write tools and session management",
6
6
  "homepage": "https://omp.sh",
7
7
  "author": "Can Boluk",
@@ -48,17 +48,17 @@
48
48
  "@agentclientprotocol/sdk": "0.25.0",
49
49
  "@babel/parser": "^7.29.7",
50
50
  "@mozilla/readability": "^0.6.0",
51
- "@oh-my-pi/hashline": "16.0.8",
52
- "@oh-my-pi/omp-stats": "16.0.8",
53
- "@oh-my-pi/pi-agent-core": "16.0.8",
54
- "@oh-my-pi/pi-ai": "16.0.8",
55
- "@oh-my-pi/pi-catalog": "16.0.8",
56
- "@oh-my-pi/pi-mnemopi": "16.0.8",
57
- "@oh-my-pi/pi-natives": "16.0.8",
58
- "@oh-my-pi/pi-tui": "16.0.8",
59
- "@oh-my-pi/pi-utils": "16.0.8",
60
- "@oh-my-pi/pi-wire": "16.0.8",
61
- "@oh-my-pi/snapcompact": "16.0.8",
51
+ "@oh-my-pi/hashline": "16.0.9",
52
+ "@oh-my-pi/omp-stats": "16.0.9",
53
+ "@oh-my-pi/pi-agent-core": "16.0.9",
54
+ "@oh-my-pi/pi-ai": "16.0.9",
55
+ "@oh-my-pi/pi-catalog": "16.0.9",
56
+ "@oh-my-pi/pi-mnemopi": "16.0.9",
57
+ "@oh-my-pi/pi-natives": "16.0.9",
58
+ "@oh-my-pi/pi-tui": "16.0.9",
59
+ "@oh-my-pi/pi-utils": "16.0.9",
60
+ "@oh-my-pi/pi-wire": "16.0.9",
61
+ "@oh-my-pi/snapcompact": "16.0.9",
62
62
  "@opentelemetry/api": "^1.9.1",
63
63
  "@opentelemetry/context-async-hooks": "^2.7.1",
64
64
  "@opentelemetry/exporter-trace-otlp-proto": "^0.218.0",
package/src/cli/args.ts CHANGED
@@ -56,6 +56,7 @@ export interface Args {
56
56
  noExtensions?: boolean;
57
57
  pluginDirs?: string[];
58
58
  print?: boolean;
59
+ printThoughts?: boolean;
59
60
  export?: string;
60
61
  noSkills?: boolean;
61
62
  skills?: string[];
@@ -200,6 +201,8 @@ export function parseArgs(inputArgs: string[], extensionFlags?: Map<string, { ty
200
201
  result.advisor = true;
201
202
  } else if (arg === "--print" || arg === "-p") {
202
203
  result.print = true;
204
+ } else if (arg === "--print-thoughts") {
205
+ result.printThoughts = true;
203
206
  } else if (arg === "--no-extensions") {
204
207
  result.noExtensions = true;
205
208
  } else if (arg === "--no-skills") {
@@ -270,6 +270,7 @@ export const VALUELESS_FLAGS: ReadonlySet<string> = new Set([
270
270
  "--hide-thinking",
271
271
  "--advisor",
272
272
  "--print",
273
+ "--print-thoughts",
273
274
  "--no-extensions",
274
275
  "--no-skills",
275
276
  "--no-rules",
@@ -136,6 +136,9 @@ export default class Index extends Command {
136
136
  "no-title": Flags.boolean({
137
137
  description: "Disable title auto-generation",
138
138
  }),
139
+ "print-thoughts": Flags.boolean({
140
+ description: "Include thinking blocks in print mode text output",
141
+ }),
139
142
  "max-time": Flags.string({
140
143
  description: "Stop the session after this many seconds",
141
144
  }),
@@ -178,8 +178,8 @@ export class GoalRuntime {
178
178
  }
179
179
  }
180
180
 
181
- #markActiveAccounting(goal: Goal): void {
182
- if (this.#wallClock.activeGoalId !== goal.id) {
181
+ #markActiveAccounting(goal: Goal, resetWallClock = false): void {
182
+ if (resetWallClock || this.#wallClock.activeGoalId !== goal.id) {
183
183
  this.#wallClock = { lastAccountedAt: this.#now(), activeGoalId: goal.id };
184
184
  }
185
185
  if (this.#turnSnapshot) {
@@ -195,6 +195,12 @@ export class GoalRuntime {
195
195
  }
196
196
  }
197
197
 
198
+ clearAccounting(): void {
199
+ this.#turnSnapshot = undefined;
200
+ this.#clearActiveAccounting();
201
+ this.#budgetReportedFor = undefined;
202
+ }
203
+
198
204
  onTurnStart(turnId: string, baselineUsage: GoalTokenUsage): void {
199
205
  this.#turnSnapshot = { turnId, baselineUsage: { ...baselineUsage } };
200
206
  const state = this.#host.getState();
@@ -235,7 +241,7 @@ export class GoalRuntime {
235
241
  return;
236
242
  }
237
243
  await this.#withAccounting(async () => {
238
- await this.#flushUsageLocked("suppressed");
244
+ await this.#flushUsageLocked("suppressed", undefined, options?.reason === "internal");
239
245
  this.#turnSnapshot = undefined;
240
246
  if (options?.reason !== "interrupted") return;
241
247
  const cloned = this.#getStateClone();
@@ -249,9 +255,14 @@ export class GoalRuntime {
249
255
  });
250
256
  }
251
257
 
252
- async onThreadResumed(): Promise<GoalModeState | undefined> {
258
+ async onThreadResumed(options?: { preserveActiveGoal?: boolean }): Promise<GoalModeState | undefined> {
253
259
  const state = this.#getStateClone();
254
260
  if (!state) return undefined;
261
+ if (options?.preserveActiveGoal && state.enabled && state.goal.status === "active") {
262
+ this.#markActiveAccounting(state.goal, true);
263
+ await this.#commitState(state, { emit: true });
264
+ return state;
265
+ }
255
266
  if (state.goal.status === "active") {
256
267
  state.enabled = false;
257
268
  state.goal.status = "paused";
@@ -301,6 +312,7 @@ export class GoalRuntime {
301
312
  async #flushUsageLocked(
302
313
  steering: GoalBudgetSteering,
303
314
  currentUsage: GoalTokenUsage = this.#host.getCurrentUsage(),
315
+ persistWallClock = false,
304
316
  ): Promise<void> {
305
317
  const state = this.#getStateClone();
306
318
  if (!state?.enabled || !isAccountingStatus(state.goal)) return;
@@ -333,10 +345,10 @@ export class GoalRuntime {
333
345
  if (this.#wallClock.activeGoalId === state.goal.id && wallSeconds > 0) {
334
346
  this.#wallClock.lastAccountedAt += wallSeconds * 1000;
335
347
  }
336
-
337
348
  // Persisting wall-clock-only accounting on every tool event bloats /goal sessions with full
338
- // objective snapshots. Keep the in-memory/UI state fresh, but persist only token/budget changes.
339
- const shouldPersistUsage = tokenDelta > 0 || flippedToBudgetLimited;
349
+ // objective snapshots. Keep normal tool flushes in memory/UI only, but make wall-clock
350
+ // usage durable before internal session switches because the active runtime is leaving.
351
+ const shouldPersistUsage = tokenDelta > 0 || flippedToBudgetLimited || (persistWallClock && wallSeconds > 0);
340
352
  await this.#commitState(state, { persist: shouldPersistUsage ? "goal" : undefined });
341
353
 
342
354
  if (state.goal.status !== "budget-limited") {
package/src/main.ts CHANGED
@@ -1039,6 +1039,13 @@ export async function runRootCommand(
1039
1039
  });
1040
1040
  }
1041
1041
 
1042
+ // --print-thoughts (single-shot print mode) must surface reasoning, so un-hide
1043
+ // thinking before the session is built — otherwise a passive hideThinkingBlock
1044
+ // setting makes the provider omit summaries and the flag prints nothing. An
1045
+ // explicit --hide-thinking below still wins.
1046
+ if (parsedArgs.printThoughts && !isProtocolMode && !isInteractive) {
1047
+ settingsInstance.override("hideThinkingBlock", false);
1048
+ }
1042
1049
  // Apply --hide-thinking CLI flag (ephemeral, not persisted)
1043
1050
  if (parsedArgs.hideThinking) {
1044
1051
  settingsInstance.override("hideThinkingBlock", true);
@@ -1373,6 +1380,7 @@ export async function runRootCommand(
1373
1380
  messages: initialArgs.messages,
1374
1381
  initialMessage,
1375
1382
  initialImages,
1383
+ printThoughts: initialArgs.printThoughts,
1376
1384
  });
1377
1385
  if ($env.PI_TIMING) {
1378
1386
  logger.printTimings();
@@ -797,7 +797,7 @@ export class InteractiveMode implements InteractiveModeContext {
797
797
  await this.initHooksAndCustomTools();
798
798
 
799
799
  // Restore mode from session (e.g. plan mode on resume)
800
- this.session.setSessionSwitchReconciler?.(() => this.#reconcileModeFromSession());
800
+ this.session.setSessionSwitchReconciler?.(() => this.#reconcileModeFromSession({ preserveActiveGoal: true }));
801
801
  await this.#reconcileModeFromSession();
802
802
 
803
803
  // Brand-new sessions optionally start in plan mode when the user has made it
@@ -1783,11 +1783,12 @@ export class InteractiveMode implements InteractiveModeContext {
1783
1783
  }
1784
1784
 
1785
1785
  /** Reconcile mode state from session entries on resume/switch. */
1786
- async #reconcileModeFromSession(): Promise<void> {
1786
+ async #reconcileModeFromSession(options?: { preserveActiveGoal?: boolean }): Promise<void> {
1787
1787
  await this.#clearTransientModeState();
1788
1788
  const sessionContext = this.sessionManager.buildSessionContext();
1789
1789
  const goalEnabled = this.session.settings.get("goal.enabled");
1790
1790
  if (!goalEnabled && (sessionContext.mode === "goal" || sessionContext.mode === "goal_paused")) {
1791
+ this.session.goalRuntime.clearAccounting();
1791
1792
  this.sessionManager.appendModeChange("none");
1792
1793
  return;
1793
1794
  }
@@ -1802,7 +1803,9 @@ export class InteractiveMode implements InteractiveModeContext {
1802
1803
  mode: "active",
1803
1804
  goal,
1804
1805
  });
1805
- const restored = await this.session.goalRuntime.onThreadResumed();
1806
+ const restored = await this.session.goalRuntime.onThreadResumed({
1807
+ preserveActiveGoal: options?.preserveActiveGoal,
1808
+ });
1806
1809
  this.goalModeEnabled = restored?.enabled === true;
1807
1810
  this.goalModePaused = restored?.enabled !== true && restored?.goal.status === "paused";
1808
1811
  // sdk.ts excludes "goal" from the initial active tool set unconditionally.
@@ -1815,6 +1818,7 @@ export class InteractiveMode implements InteractiveModeContext {
1815
1818
  this.#updateGoalModeStatus();
1816
1819
  return;
1817
1820
  }
1821
+ this.session.goalRuntime.clearAccounting();
1818
1822
  if (!this.session.settings.get("plan.enabled")) {
1819
1823
  // Clear stale plan/plan_paused mode so re-enabling the setting
1820
1824
  // later doesn't unexpectedly restore an old plan session.
@@ -24,6 +24,8 @@ export interface PrintModeOptions {
24
24
  initialMessage?: string;
25
25
  /** Images to attach to the initial message */
26
26
  initialImages?: ImageContent[];
27
+ /** If true, include thinking blocks in text output */
28
+ printThoughts?: boolean;
27
29
  }
28
30
 
29
31
  /**
@@ -31,7 +33,7 @@ export interface PrintModeOptions {
31
33
  * Sends prompts to the agent and outputs the result.
32
34
  */
33
35
  export async function runPrintMode(session: AgentSession, options: PrintModeOptions): Promise<void> {
34
- const { mode, messages = [], initialMessage, initialImages } = options;
36
+ const { mode, messages = [], initialMessage, initialImages, printThoughts } = options;
35
37
 
36
38
  // Emit session header for JSON mode
37
39
  if (mode === "json") {
@@ -108,6 +110,8 @@ export async function runPrintMode(session: AgentSession, options: PrintModeOpti
108
110
  for (const content of assistantMsg.content) {
109
111
  if (content.type === "text") {
110
112
  process.stdout.write(`${sanitizeText(content.text)}\n`);
113
+ } else if (printThoughts && content.type === "thinking" && content.thinking.trim().length > 0) {
114
+ process.stdout.write(`${sanitizeText(content.thinking)}\n`);
111
115
  }
112
116
  }
113
117
  }
@@ -1 +1,3 @@
1
- Send one concrete, terse piece of advice to the agent you are watching. Use sparingly; stay silent when nothing matters.
1
+ Send one concrete, terse piece of advice to the agent you are watching.
2
+ - Use sparingly; stay silent when nothing matters.
3
+ - Call it to head off likely-wrong or materially wasteful work.
@@ -1,32 +1,75 @@
1
1
  <system-conventions>
2
2
  RFC 2119 applies to MUST, REQUIRED, SHOULD, RECOMMENDED, MAY, OPTIONAL. `NEVER` and `AVOID` are aliases for `MUST NOT` and `SHOULD NOT`.
3
- You can explore the workspace; budget is 2–3 tool calls per advise (exception: critical bugs warrant deeper verification before raising a blocker).
4
3
  </system-conventions>
5
4
 
6
- You bring a different angle.
7
- The agent might not have thought about an edge case, spotted a hallucinated API, or realized a simpler approach exists.
5
+ You bring a different angle, and advocate for the user and the code-quality & robustness.
6
+ You're watching over the main agent as a peer-programmer:
7
+ - They might not have thought about an edge case, or realized a more elegant approach exists.
8
+ - They might be sinking deeper into a hole that will not get the user's request accomplished.
9
+
8
10
  Your job is to offer that view before they sink work into the wrong direction.
9
11
 
10
12
  <workflow>
11
- You receive the agent's transcript incrementally, including private thinking.
13
+ You receive the agent's transcript incrementally, including their thoughts.
12
14
  You have read-only access through `read`, `search`, `find` to verify your suspicions.
13
- Keep exploration lean — 2–3 calls per advise unless you've spotted a critical bug and need to be absolutely certain before raising a blocker.
15
+ Keep exploration lean:
16
+ - 2–3 tool calls per advise.
17
+ - Exception: critical bugs may need deeper verification before raising a blocker.
14
18
  </workflow>
15
19
 
16
20
  <communication>
17
- At most one `advise` per update. Prefer silence when the agent is on track. Address the agent directly. Offer alternatives, not lectures. Never restate what they know; never explain how to use the advisor.
18
- Do not comment merely to add insight, context, or a second opinion. NEVER restate information the agent already has, including tool or CLI errors returned directly to it. NEVER flag a problem that will surface on its own — type errors, LSP diagnostics, failed builds, failing tests, lint — the agent's own tooling catches those. NEVER repeat advice you already gave.
21
+ - You call `advise` to surface your commentary to the driving agent; at most one `advise` per update.
22
+ - Prefer silence when the agent is on track.
23
+ - Address the agent directly.
24
+ - Offer alternatives, not lectures.
25
+ - NEVER restate information the agent already has, including errors they have seen.
26
+ - Examples: type errors, LSP diagnostics, failed builds, failing tests, lint.
27
+ - NEVER repeat advice you already gave.
28
+ - NEVER nitpick about things user stated they are okay with. You are the advocate for the user.
19
29
  </communication>
20
30
 
21
31
  <critical>
22
- You SHOULD call `advise` when: agent might be heading the wrong way, missed an edge case, about to call a hallucinated API, going in circles, picking brittle approach over better one. Low confidence bar "this might be wrong" is worth noting if they didn't think about it.
32
+ A low-confidence bar applies ONLY to concrete technical risk:
33
+ - Generic uncertainty, vague unease, or user-intent ambiguity → stay SILENT.
34
+
23
35
  NEVER advise just to second-guess decisions the agent understands and is committed to, if you are not certain.
36
+
37
+ NEVER advise on intent or process:
38
+ - Do not push the agent to ask for clarification, confirm scope, or summarize input before acting.
39
+ - Do not question whether the user's ask is clear enough.
40
+ - Intent is the agent's domain; it defaults to informed action.
41
+ - Your lane: correctness, edge cases, design, process.
42
+
43
+ Cite the exact instruction or risk.
24
44
  </critical>
25
45
 
26
46
  <completeness>
27
- **`nit`** — Non-urgent cleanup, refactor, style, missed opportunity. Folded at next step boundary; agent keeps working. Examples: edge cases that don't break correctness, simplifications, better approach the agent can consider.
28
- **`concern`** Agent might be heading wrong or missed something material. Offers your view; agent decides. Use when: exploring wrong code path, picking fragile approach when better exists, missing constraint, hallucinated API, going in circles, edge case about to be baked in.
29
- **`blocker`** Stop and reconsider. Use ONLY when: continuing will clearly waste the turn, produce broken output, or the path is fundamentally unsound. Verify thoroughly before raising.
47
+ **`nit`**
48
+ - Non-urgent cleanup, refactor, style, missed opportunity.
49
+ - Folded at next step boundary; agent keeps working.
50
+ - Examples:
51
+ - Edge cases that don't break correctness.
52
+ - Simplifications.
53
+ - Better approach the agent can consider.
54
+
55
+ **`concern`**
56
+ - Agent might be heading wrong or missed something material.
57
+ - Offers your view; agent decides.
58
+ - Use when:
59
+ - Exploring wrong code path.
60
+ - Picking fragile approach when better exists.
61
+ - Not parallelizing when user request is obviously parallelizable.
62
+ - Missing constraint.
63
+ - Edge case about to be baked in.
64
+
65
+ **`blocker`**
66
+ - Stop and reconsider.
67
+ - Use ONLY when the agent making progress will clearly:
68
+ - Waste the users time with a larger refactor.
69
+ - Will require the user to interrupt the agent later on, due to them going in circles without a solution.
70
+ - Be fundamentally unsound.
71
+ - Verify thoroughly before raising.
30
72
  </completeness>
31
73
 
32
- You MAY suggest an approach or fix if you've explored enough to be confident. Your job is pair programming, not just bugs — offer the better designs, not just the warning.
74
+ You MAY suggest an approach or fix if you've explored enough to be confident.
75
+ Offer the better designs, not just the warning.
@@ -7375,7 +7375,7 @@ export class AgentSession {
7375
7375
  throw new Error("Compaction already in progress");
7376
7376
  }
7377
7377
  this.#disconnectFromAgent();
7378
- await this.abort();
7378
+ await this.abort({ goalReason: "internal" });
7379
7379
  const compactionAbortController = new AbortController();
7380
7380
  this.#compactionAbortController = compactionAbortController;
7381
7381
 
@@ -7543,6 +7543,10 @@ export class AgentSession {
7543
7543
  const newEntries = this.sessionManager.getEntries();
7544
7544
  const sessionContext = this.buildDisplaySessionContext();
7545
7545
  this.agent.replaceMessages(sessionContext.messages);
7546
+ // Compaction discarded the conversation history that carried the approved
7547
+ // plan reference. Clear the sent-flag so #buildPlanReferenceMessage re-reads
7548
+ // the plan from disk and re-injects it on the next turn (issue #1246).
7549
+ this.#planReferenceSent = false;
7546
7550
  this.#advisorRuntime?.reset();
7547
7551
  this.#syncTodoPhasesFromBranch();
7548
7552
  this.#closeCodexProviderSessionsForHistoryRewrite();
@@ -9409,6 +9413,10 @@ export class AgentSession {
9409
9413
  const newEntries = this.sessionManager.getEntries();
9410
9414
  const sessionContext = this.buildDisplaySessionContext();
9411
9415
  this.agent.replaceMessages(sessionContext.messages);
9416
+ // Compaction discarded the conversation history that carried the approved
9417
+ // plan reference. Clear the sent-flag so #buildPlanReferenceMessage re-reads
9418
+ // the plan from disk and re-injects it on the next turn (issue #1246).
9419
+ this.#planReferenceSent = false;
9412
9420
  this.#advisorRuntime?.reset();
9413
9421
  this.#syncTodoPhasesFromBranch();
9414
9422
  this.#closeCodexProviderSessionsForHistoryRewrite();
@@ -10960,7 +10968,7 @@ export class AgentSession {
10960
10968
  }
10961
10969
 
10962
10970
  this.#disconnectFromAgent();
10963
- await this.abort();
10971
+ await this.abort({ goalReason: "internal" });
10964
10972
 
10965
10973
  // Flush pending writes before switching so restore snapshots reflect committed state.
10966
10974
  await this.sessionManager.flush();