@oh-my-pi/pi-coding-agent 17.1.1 → 17.1.2

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 (96) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/dist/cli.js +6216 -3984
  3. package/dist/types/cli/bench-cli.d.ts +1 -0
  4. package/dist/types/config/model-resolver.d.ts +6 -3
  5. package/dist/types/config/settings-schema.d.ts +4 -0
  6. package/dist/types/launch/broker-list-order.test.d.ts +1 -0
  7. package/dist/types/modes/interactive-mode.d.ts +2 -6
  8. package/dist/types/modes/types.d.ts +8 -5
  9. package/dist/types/modes/utils/ui-helpers.d.ts +1 -6
  10. package/dist/types/task/executor.d.ts +3 -1
  11. package/dist/types/task/structured-subagent.d.ts +3 -0
  12. package/dist/types/task/types.d.ts +11 -11
  13. package/dist/types/thinking.d.ts +13 -0
  14. package/dist/types/tools/eval-format/index.d.ts +7 -0
  15. package/dist/types/tools/eval-format/javascript.d.ts +2 -0
  16. package/dist/types/tools/eval-format/julia.d.ts +2 -0
  17. package/dist/types/tools/eval-format/python.d.ts +2 -0
  18. package/dist/types/tools/eval-format/ruby.d.ts +2 -0
  19. package/dist/types/tools/resolve.d.ts +7 -0
  20. package/dist/types/tools/todo.d.ts +4 -1
  21. package/dist/types/web/search/providers/base.d.ts +16 -0
  22. package/dist/types/web/search/providers/brave.d.ts +2 -0
  23. package/dist/types/web/search/providers/firecrawl.d.ts +2 -0
  24. package/dist/types/web/search/providers/gemini.d.ts +3 -0
  25. package/dist/types/web/search/providers/jina.d.ts +2 -0
  26. package/dist/types/web/search/providers/kagi.d.ts +2 -0
  27. package/dist/types/web/search/providers/kimi.d.ts +2 -0
  28. package/dist/types/web/search/providers/parallel.d.ts +2 -0
  29. package/dist/types/web/search/providers/perplexity.d.ts +3 -0
  30. package/dist/types/web/search/providers/searxng.d.ts +10 -0
  31. package/dist/types/web/search/providers/tavily.d.ts +8 -0
  32. package/dist/types/web/search/query.d.ts +190 -0
  33. package/package.json +12 -12
  34. package/src/cli/bench-cli.ts +12 -1
  35. package/src/cli/web-search-cli.ts +7 -0
  36. package/src/config/model-resolver.ts +17 -6
  37. package/src/config/settings-schema.ts +5 -0
  38. package/src/debug/raw-sse-buffer.ts +157 -30
  39. package/src/export/share.ts +4 -3
  40. package/src/launch/broker-list-order.test.ts +89 -0
  41. package/src/launch/broker.ts +49 -8
  42. package/src/modes/controllers/input-controller.ts +8 -8
  43. package/src/modes/interactive-mode.ts +60 -5
  44. package/src/modes/types.ts +9 -4
  45. package/src/modes/utils/ui-helpers.ts +7 -8
  46. package/src/prompts/system/resolve-device-reminder.md +1 -1
  47. package/src/prompts/system/workflow-notice.md +1 -1
  48. package/src/prompts/tools/ast-edit.md +1 -0
  49. package/src/prompts/tools/bash.md +1 -0
  50. package/src/prompts/tools/eval.md +2 -2
  51. package/src/prompts/tools/task.md +5 -2
  52. package/src/prompts/tools/web-search.md +2 -0
  53. package/src/sdk.ts +8 -4
  54. package/src/session/agent-session.ts +14 -0
  55. package/src/session/queued-messages.ts +7 -1
  56. package/src/task/executor.ts +17 -9
  57. package/src/task/index.ts +14 -34
  58. package/src/task/structured-subagent.ts +4 -0
  59. package/src/task/types.ts +15 -13
  60. package/src/thinking.ts +27 -0
  61. package/src/tools/ast-edit.ts +4 -1
  62. package/src/tools/bash.ts +13 -0
  63. package/src/tools/browser/render.ts +2 -1
  64. package/src/tools/eval-format/index.ts +24 -0
  65. package/src/tools/eval-format/javascript.ts +952 -0
  66. package/src/tools/eval-format/julia.ts +446 -0
  67. package/src/tools/eval-format/python.ts +544 -0
  68. package/src/tools/eval-format/ruby.ts +380 -0
  69. package/src/tools/eval-render.ts +12 -6
  70. package/src/tools/resolve.ts +10 -1
  71. package/src/tools/todo.ts +58 -6
  72. package/src/web/search/index.ts +28 -5
  73. package/src/web/search/providers/anthropic.ts +62 -4
  74. package/src/web/search/providers/base.ts +16 -0
  75. package/src/web/search/providers/brave.ts +30 -3
  76. package/src/web/search/providers/codex.ts +14 -1
  77. package/src/web/search/providers/duckduckgo.ts +23 -1
  78. package/src/web/search/providers/ecosia.ts +5 -1
  79. package/src/web/search/providers/exa.ts +28 -1
  80. package/src/web/search/providers/firecrawl.ts +37 -3
  81. package/src/web/search/providers/gemini.ts +12 -2
  82. package/src/web/search/providers/google.ts +2 -1
  83. package/src/web/search/providers/jina.ts +31 -8
  84. package/src/web/search/providers/kagi.ts +10 -1
  85. package/src/web/search/providers/kimi.ts +16 -1
  86. package/src/web/search/providers/mojeek.ts +14 -1
  87. package/src/web/search/providers/parallel.ts +48 -2
  88. package/src/web/search/providers/perplexity.ts +94 -6
  89. package/src/web/search/providers/searxng.ts +145 -9
  90. package/src/web/search/providers/startpage.ts +8 -2
  91. package/src/web/search/providers/synthetic.ts +7 -1
  92. package/src/web/search/providers/tavily.ts +52 -3
  93. package/src/web/search/providers/tinyfish.ts +6 -1
  94. package/src/web/search/providers/xai.ts +49 -2
  95. package/src/web/search/providers/zai.ts +19 -1
  96. package/src/web/search/query.ts +850 -0
package/src/task/index.ts CHANGED
@@ -23,6 +23,7 @@ import subagentUserPromptTemplate from "../prompts/system/subagent-user-prompt.m
23
23
  import taskDescriptionTemplate from "../prompts/tools/task.md" with { type: "text" };
24
24
  import taskAsyncContractTemplate from "../prompts/tools/task-async-contract.md" with { type: "text" };
25
25
  import taskSummaryTemplate from "../prompts/tools/task-summary.md" with { type: "text" };
26
+ import { TASK_EFFORTS, type TaskEffort } from "../thinking";
26
27
  import { truncateForPrompt } from "../tools/approval";
27
28
  import { isIrcEnabled } from "../tools/hub";
28
29
  import { formatBytes, formatDuration } from "../tools/render-utils";
@@ -231,17 +232,11 @@ function validateShapeParams(batchEnabled: boolean, params: TaskParams): string
231
232
  * policy later, in `spawnParamsFor`. Returns a problem description, or
232
233
  * undefined when valid.
233
234
  */
234
- function hasInvalidModelSelector(model: unknown): boolean {
235
- if (model === undefined) return false;
236
- const selectors = typeof model === "string" ? [model] : Array.isArray(model) ? model : undefined;
237
- const materializedSelectors = selectors ? Array.from(selectors) : [];
238
- return (
239
- !selectors ||
240
- materializedSelectors.length === 0 ||
241
- materializedSelectors.some(
242
- selector => typeof selector !== "string" || !selector.split(",").some(pattern => pattern.trim()),
243
- )
244
- );
235
+
236
+ /** Reject an out-of-range `effort` selector on internal/stale-transcript calls that bypass the wire schema. */
237
+ function validateEffort(effort: TaskEffort | undefined, label: string): string | undefined {
238
+ if (effort === undefined || TASK_EFFORTS.includes(effort)) return undefined;
239
+ return `${label} has an invalid \`effort\` value ${JSON.stringify(effort)}. Use "lo", "med", or "hi".`;
245
240
  }
246
241
 
247
242
  function validateSpawnParams(params: TaskParams, batchEnabled: boolean): string | undefined {
@@ -259,9 +254,8 @@ function validateSpawnParams(params: TaskParams, batchEnabled: boolean): string
259
254
  if (!item || typeof item.task !== "string" || item.task.trim() === "") {
260
255
  return `Task ${i + 1}${item?.name ? ` (\`${item.name}\`)` : ""} is missing \`task\`. Every task needs complete, self-contained instructions.`;
261
256
  }
262
- if (hasInvalidModelSelector(item.model)) {
263
- return `Task ${i + 1}${item.name ? ` (\`${item.name}\`)` : ""} has an invalid \`model\`. Provide a non-empty selector or a non-empty array of non-empty selectors.`;
264
- }
257
+ const effortError = validateEffort(item.effort, `Task ${i + 1}${item.name ? ` (\`${item.name}\`)` : ""}`);
258
+ if (effortError) return effortError;
265
259
  }
266
260
  const seen = new Map<string, string>();
267
261
  for (const item of tasks) {
@@ -284,10 +278,7 @@ function validateSpawnParams(params: TaskParams, batchEnabled: boolean): string
284
278
  ? "Missing `tasks`. Provide a `tasks` array (one subagent per item) with a shared `context`."
285
279
  : "Missing `task`. Provide complete, self-contained instructions for the agent.";
286
280
  }
287
- if (hasInvalidModelSelector(params.model)) {
288
- return "Invalid `model`. Provide a non-empty selector or a non-empty array of non-empty selectors.";
289
- }
290
- return undefined;
281
+ return validateEffort(params.effort, "The call");
291
282
  }
292
283
 
293
284
  /**
@@ -300,9 +291,10 @@ function resolveSpawnItems(params: TaskParams): TaskItem[] {
300
291
  if (Array.isArray(params.tasks) && params.tasks.length > 0) {
301
292
  return params.tasks;
302
293
  }
303
- const item: TaskItem = { name: params.name, agent: params.agent, task: params.task, model: params.model };
294
+ const item: TaskItem = { name: params.name, agent: params.agent, task: params.task };
304
295
  if ("outputSchema" in params) item.outputSchema = params.outputSchema;
305
296
  if ("schemaMode" in params) item.schemaMode = params.schemaMode;
297
+ if ("effort" in params) item.effort = params.effort;
306
298
  if ("isolated" in params) item.isolated = params.isolated;
307
299
  return [item];
308
300
  }
@@ -320,10 +312,10 @@ function spawnParamsFor(params: TaskParams, item: TaskItem, defaultAgent: string
320
312
  const spawn: TaskParams = { agent: item.agent?.trim() || defaultAgent };
321
313
  if (item.name !== undefined) spawn.name = item.name;
322
314
  if (item.task !== undefined) spawn.task = item.task;
323
- if (item.model !== undefined) spawn.model = item.model;
324
315
  if (params.context !== undefined) spawn.context = params.context;
325
316
  if ("outputSchema" in item) spawn.outputSchema = item.outputSchema;
326
317
  if ("schemaMode" in item) spawn.schemaMode = item.schemaMode;
318
+ if ("effort" in item) spawn.effort = item.effort;
327
319
  if (item.isolated !== undefined) {
328
320
  spawn.isolated = item.isolated;
329
321
  } else if ("isolated" in params) {
@@ -491,14 +483,6 @@ function discoverAgentsForCreate(cwd: string): Promise<DiscoveryResult> {
491
483
  return pending;
492
484
  }
493
485
 
494
- function formatModelForApproval(model: unknown): string | undefined {
495
- const selectors = typeof model === "string" ? [model] : Array.isArray(model) ? model : [];
496
- const normalized = selectors.filter(
497
- (selector): selector is string => typeof selector === "string" && !!selector.trim(),
498
- );
499
- return normalized.length > 0 ? truncateForPrompt(normalized.join(" → ")) : undefined;
500
- }
501
-
502
486
  // ═══════════════════════════════════════════════════════════════════════════
503
487
  // Tool Class
504
488
  // ═══════════════════════════════════════════════════════════════════════════
@@ -522,8 +506,6 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
522
506
  if (typeof params.name === "string" && params.name.trim()) {
523
507
  lines.push(`Name: ${truncateForPrompt(params.name)}`);
524
508
  }
525
- const model = formatModelForApproval(params.model);
526
- if (model) lines.push(`Model: ${model}`);
527
509
  if (typeof params.task === "string") {
528
510
  lines.push(`Task:\n${truncateForPrompt(params.task)}`);
529
511
  }
@@ -554,8 +536,6 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
554
536
  lines.push(`Name: ${truncateForPrompt(firstTask.name)}`);
555
537
  }
556
538
  lines.push(`Agent: ${truncateForPrompt(effectiveAgent(firstTask))}`);
557
- const itemModel = formatModelForApproval("model" in firstTask ? firstTask.model : undefined);
558
- if (itemModel) lines.push(`Model: ${itemModel}`);
559
539
  if ("task" in firstTask && typeof firstTask.task === "string") {
560
540
  lines.push(`Task:\n${truncateForPrompt(firstTask.task)}`);
561
541
  }
@@ -663,9 +643,9 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
663
643
  assignment: (params.task ?? "").trim(),
664
644
  context: this.#isBatchEnabled() ? params.context?.trim() || undefined : undefined,
665
645
  agent: params.agent,
666
- model: params.model,
667
646
  ...(Object.hasOwn(params, "outputSchema") ? { outputSchema: params.outputSchema } : {}),
668
647
  ...(Object.hasOwn(params, "schemaMode") ? { schemaMode: params.schemaMode } : {}),
648
+ ...(params.effort !== undefined ? { effort: params.effort } : {}),
669
649
  ...("isolated" in params ? { isolation: { requested: params.isolated } } : {}),
670
650
  blockedAgent: this.#blockedAgent,
671
651
  enableLsp: (this.session.enableLsp ?? true) && this.session.settings.get("task.enableLsp"),
@@ -1419,9 +1399,9 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
1419
1399
  assignment,
1420
1400
  context,
1421
1401
  agent: params.agent,
1422
- model: params.model,
1423
1402
  ...(Object.hasOwn(params, "outputSchema") ? { outputSchema: params.outputSchema } : {}),
1424
1403
  ...(Object.hasOwn(params, "schemaMode") ? { schemaMode: params.schemaMode } : {}),
1404
+ ...(params.effort !== undefined ? { effort: params.effort } : {}),
1425
1405
  identity: { id: preAllocatedId, label: params.name },
1426
1406
  index: spawnIndex,
1427
1407
  parentToolCallId: toolCallId,
@@ -16,6 +16,7 @@ import { loadOverallPlanReference } from "../plan-mode/plan-handoff";
16
16
  import planModeSubagentPrompt from "../prompts/system/plan-mode-subagent.md" with { type: "text" };
17
17
  import subagentUserPromptTemplate from "../prompts/system/subagent-user-prompt.md" with { type: "text" };
18
18
  import { MAIN_AGENT_ID } from "../registry/agent-registry";
19
+ import type { TaskEffort } from "../thinking";
19
20
  import type { ToolSession } from "../tools";
20
21
  import { isIrcEnabled } from "../tools/hub";
21
22
  import { buildOutputValidator } from "../tools/output-schema-validator";
@@ -87,6 +88,8 @@ export interface StructuredSubagentRequest {
87
88
  /** Presence, rather than truthiness, makes this the highest-priority schema. */
88
89
  outputSchema?: unknown;
89
90
  schemaMode?: StructuredSubagentSchemaMode;
91
+ /** Per-spawn thinking effort mapped onto the resolved model's supported range; overrides the agent's default selector. */
92
+ effort?: TaskEffort;
90
93
  identity?: StructuredSubagentIdentity;
91
94
  index?: number;
92
95
  parentToolCallId?: string;
@@ -390,6 +393,7 @@ function buildExecutorOptions(
390
393
  modelOverride: policy.modelOverride,
391
394
  parentActiveModelPattern: policy.parentActiveModelPattern,
392
395
  thinkingLevel: policy.effectiveAgent.thinkingLevel,
396
+ effort: request.effort,
393
397
  ...(policy.schema.source === "none"
394
398
  ? {}
395
399
  : {
package/src/task/types.ts CHANGED
@@ -2,7 +2,7 @@ import type { Usage } from "@oh-my-pi/pi-ai";
2
2
  import { $env } from "@oh-my-pi/pi-utils";
3
3
  import { type BaseType, type } from "arktype";
4
4
  import type { AgentSessionEvent } from "../session/agent-session";
5
- import type { ConfiguredThinkingLevel } from "../thinking";
5
+ import type { ConfiguredThinkingLevel, TaskEffort } from "../thinking";
6
6
  import type { NestedRepoPatch } from "./worktree";
7
7
 
8
8
  /** Source of an agent definition */
@@ -108,12 +108,14 @@ export const LABEL_MAX = 80;
108
108
 
109
109
  // Keep this explicit: ArkType serializes `unknown` as a boolean subschema, which llama.cpp grammars reject.
110
110
  const outputSchemaInputSchema = type("object | boolean | string | null");
111
+ // Coarse per-spawn thinking effort; must stay in sync with TASK_EFFORTS in ../thinking.
112
+ const effortRule = '"lo" | "med" | "hi"' as const;
111
113
 
112
114
  export const taskItemSchema = type({
113
115
  "name?": "string",
114
116
  agent: "string = 'task'",
115
117
  task: "string",
116
- "model?": "string | string[]",
118
+ "effort?": effortRule,
117
119
  "outputSchema?": outputSchemaInputSchema,
118
120
  "schemaMode?": '"permissive" | "strict"',
119
121
  "+": "delete",
@@ -122,7 +124,7 @@ const taskItemSchemaIsolated = type({
122
124
  "name?": "string",
123
125
  agent: "string = 'task'",
124
126
  task: "string",
125
- "model?": "string | string[]",
127
+ "effort?": effortRule,
126
128
  "outputSchema?": outputSchemaInputSchema,
127
129
  "schemaMode?": '"permissive" | "strict"',
128
130
  "isolated?": "boolean",
@@ -137,8 +139,8 @@ export interface TaskItem {
137
139
  agent?: string;
138
140
  /** The work; required by the schema. */
139
141
  task?: string;
140
- /** Explicit model selector or fallback chain for this spawn, including optional reasoning suffixes. */
141
- model?: string | string[];
142
+ /** Per-spawn thinking effort: lowest/middle/highest level the resolved model supports. Overrides the agent's default selector (e.g. `auto`). */
143
+ effort?: TaskEffort;
142
144
  /** Caller-provided output schema; its presence overrides the selected agent's schema. */
143
145
  outputSchema?: unknown;
144
146
  /** Validation behavior for a caller-provided or inherited output schema. */
@@ -151,7 +153,7 @@ export const taskSchema = type({
151
153
  "name?": "string",
152
154
  agent: "string = 'task'",
153
155
  task: "string",
154
- "model?": "string | string[]",
156
+ "effort?": effortRule,
155
157
  "outputSchema?": outputSchemaInputSchema,
156
158
  "schemaMode?": '"permissive" | "strict"',
157
159
  "isolated?": "boolean",
@@ -161,7 +163,7 @@ const taskSchemaNoIsolation = type({
161
163
  "name?": "string",
162
164
  agent: "string = 'task'",
163
165
  task: "string",
164
- "model?": "string | string[]",
166
+ "effort?": effortRule,
165
167
  "outputSchema?": outputSchemaInputSchema,
166
168
  "schemaMode?": '"permissive" | "strict"',
167
169
  "+": "delete",
@@ -206,7 +208,7 @@ function createTaskSchema(options: {
206
208
  "name?": "string",
207
209
  agent,
208
210
  task: "string",
209
- "model?": "string | string[]",
211
+ "effort?": effortRule,
210
212
  "outputSchema?": outputSchemaInputSchema,
211
213
  "schemaMode?": '"permissive" | "strict"',
212
214
  "isolated?": "boolean",
@@ -222,7 +224,7 @@ function createTaskSchema(options: {
222
224
  "name?": "string",
223
225
  agent,
224
226
  task: "string",
225
- "model?": "string | string[]",
227
+ "effort?": effortRule,
226
228
  "outputSchema?": outputSchemaInputSchema,
227
229
  "schemaMode?": '"permissive" | "strict"',
228
230
  "+": "delete",
@@ -238,7 +240,7 @@ function createTaskSchema(options: {
238
240
  "name?": "string",
239
241
  agent,
240
242
  task: "string",
241
- "model?": "string | string[]",
243
+ "effort?": effortRule,
242
244
  "outputSchema?": outputSchemaInputSchema,
243
245
  "schemaMode?": '"permissive" | "strict"',
244
246
  "isolated?": "boolean",
@@ -249,7 +251,7 @@ function createTaskSchema(options: {
249
251
  "name?": "string",
250
252
  agent,
251
253
  task: "string",
252
- "model?": "string | string[]",
254
+ "effort?": effortRule,
253
255
  "outputSchema?": outputSchemaInputSchema,
254
256
  "schemaMode?": '"permissive" | "strict"',
255
257
  "+": "delete",
@@ -293,8 +295,8 @@ export interface TaskParams {
293
295
  agent?: string;
294
296
  /** The work (flat form). */
295
297
  task?: string;
296
- /** Explicit model selector or fallback chain for the spawn, including optional reasoning suffixes. */
297
- model?: string | string[];
298
+ /** Per-spawn thinking effort (flat form): lowest/middle/highest level the resolved model supports. */
299
+ effort?: TaskEffort;
298
300
  /** Caller-provided output schema; its presence overrides the selected agent's schema. */
299
301
  outputSchema?: unknown;
300
302
  /** Validation behavior for a caller-provided or inherited output schema. */
package/src/thinking.ts CHANGED
@@ -220,6 +220,33 @@ export function clampAutoThinkingEffort(model: Model | undefined, effort: Effort
220
220
  return chosen;
221
221
  }
222
222
 
223
+ /** Coarse per-spawn effort selectors accepted by the task tool. */
224
+ export const TASK_EFFORTS = ["lo", "med", "hi"] as const;
225
+
226
+ /** Coarse task-spawn effort: the lowest, middle, or highest thinking level the target model supports. */
227
+ export type TaskEffort = (typeof TASK_EFFORTS)[number];
228
+
229
+ /**
230
+ * Maps a coarse task effort onto the model's supported thinking range:
231
+ * `lo` = lowest supported level, `hi` = highest (whatever the model tops out
232
+ * at — high, xhigh, or max), `med` = the middle (lower of the two middles for
233
+ * an even-sized range). Without a model, maps over the full canonical range.
234
+ * Returns `undefined` when the model has no controllable effort surface, so
235
+ * callers fall back to their default selector (e.g. `auto`).
236
+ */
237
+ export function resolveTaskEffortLevel(model: Model | undefined, effort: TaskEffort): Effort | undefined {
238
+ const supported = model ? getSupportedEfforts(model) : THINKING_EFFORTS;
239
+ if (supported.length === 0) return undefined;
240
+ switch (effort) {
241
+ case "lo":
242
+ return supported[0];
243
+ case "med":
244
+ return supported[(supported.length - 1) >> 1];
245
+ case "hi":
246
+ return supported[supported.length - 1];
247
+ }
248
+ }
249
+
223
250
  /**
224
251
  * The provisional concrete level shown while `auto` is configured but before a
225
252
  * turn has been classified. Prefers the model's `defaultLevel`, otherwise High,
@@ -32,7 +32,7 @@ import {
32
32
  formatParseErrorsCountLabel,
33
33
  PREVIEW_LIMITS,
34
34
  } from "./render-utils";
35
- import { queueResolveHandler } from "./resolve";
35
+ import { PREVIEW_PENDING_NOTICE, queueResolveHandler } from "./resolve";
36
36
  import { ToolError } from "./tool-errors";
37
37
  import { toolResult } from "./tool-result";
38
38
 
@@ -520,6 +520,9 @@ export class AstEditTool implements AgentTool<typeof astEditSchema, AstEditToolD
520
520
  return toolResult(appliedDetails).text(text).done();
521
521
  },
522
522
  });
523
+ // The renderer's ⟨proposed⟩ badge is TUI-only; this line is the model's
524
+ // in-result signal that the diff above is staged, not applied.
525
+ outputLines.unshift(PREVIEW_PENDING_NOTICE, "");
523
526
  }
524
527
 
525
528
  const details: AstEditToolDetails = {
package/src/tools/bash.ts CHANGED
@@ -10,6 +10,7 @@ import type { Component } from "@oh-my-pi/pi-tui";
10
10
  import { ImageProtocol, TERMINAL } from "@oh-my-pi/pi-tui";
11
11
  import { getProjectDir, isEnoent, logger, prompt } from "@oh-my-pi/pi-utils";
12
12
  import { type } from "arktype";
13
+ import type { Settings } from "../config/settings";
13
14
  import { applyDirenvPreflight, type BashResult, executeBash } from "../exec/bash-executor";
14
15
  import type { RenderResultOptions } from "../extensibility/custom-tools/types";
15
16
  import { InternalUrlRouter } from "../internal-urls";
@@ -85,6 +86,16 @@ export function wrapShellLineForClientTerminal(
85
86
  return { command: shellConfig.shell, args: [...shellConfig.args, finalLine] };
86
87
  }
87
88
 
89
+ /**
90
+ * Mirrors pi-shell's `uutils_env_disabled` gate for `PI_DISABLE_UUTILS_BUILTINS`:
91
+ * session shell env first, then process env; truthy = present and not "", "0",
92
+ * or "false". Controls whether the prompt advertises the in-process builtins.
93
+ */
94
+ function shellBuiltinsDisabled(settings: Settings): boolean {
95
+ const raw = settings.getShellConfig().env?.PI_DISABLE_UUTILS_BUILTINS ?? Bun.env.PI_DISABLE_UUTILS_BUILTINS;
96
+ return !!raw && raw !== "0" && raw.toLowerCase() !== "false";
97
+ }
98
+
88
99
  /**
89
100
  * Bash patterns flagged as safety critical for approval policy.
90
101
  *
@@ -496,6 +507,8 @@ export class BashTool implements AgentTool<typeof bashSchemaBase | typeof bashSc
496
507
  "eval",
497
508
  evalBackends.python || evalBackends.js || evalBackends.ruby || evalBackends.julia,
498
509
  ),
510
+ hasShellBuiltins: !shellBuiltinsDisabled(this.session.settings),
511
+ isWindows: process.platform === "win32",
499
512
  });
500
513
  }
501
514
  readonly parameters: BashToolSchema;
@@ -11,6 +11,7 @@ import type { RenderResultOptions } from "../../extensibility/custom-tools/types
11
11
  import type { Theme } from "../../modes/theme/theme";
12
12
  import { Hasher, isFramedBlockComponent, markFramedBlockComponent, renderCodeCell, renderStatusLine } from "../../tui";
13
13
  import type { BrowserToolDetails } from "../browser";
14
+ import { formatJavaScriptForDisplay } from "../eval-format/javascript";
14
15
  import { formatStyledTruncationWarning, stripOutputNotice } from "../output-meta";
15
16
  import { replaceTabs, shortenPath } from "../render-utils";
16
17
 
@@ -90,7 +91,7 @@ function renderRunCell(
90
91
  isError: boolean,
91
92
  theme: Theme,
92
93
  ): Component {
93
- const code = dropTrailingBlankLines(args.code ?? "");
94
+ const code = formatJavaScriptForDisplay(dropTrailingBlankLines(args.code ?? ""));
94
95
  const status = cellStatus(options.isPartial, isError);
95
96
 
96
97
  const titleParts: string[] = [tabLabel(args, details)];
@@ -0,0 +1,24 @@
1
+ import type { EvalLanguage } from "../../eval/types";
2
+ import { formatJavaScriptForDisplay } from "./javascript";
3
+ import { formatJuliaForDisplay } from "./julia";
4
+ import { formatPythonForDisplay } from "./python";
5
+ import { formatRubyForDisplay } from "./ruby";
6
+
7
+ export * from "./javascript";
8
+ export * from "./julia";
9
+ export * from "./python";
10
+ export * from "./ruby";
11
+
12
+ /** Formats an arbitrary eval-code prefix for display without changing the executed source. */
13
+ export function formatEvalCodeForDisplay(source: string, language: EvalLanguage): string {
14
+ switch (language) {
15
+ case "js":
16
+ return formatJavaScriptForDisplay(source);
17
+ case "ruby":
18
+ return formatRubyForDisplay(source);
19
+ case "julia":
20
+ return formatJuliaForDisplay(source);
21
+ case "python":
22
+ return formatPythonForDisplay(source);
23
+ }
24
+ }