@oh-my-pi/pi-coding-agent 16.0.2 → 16.0.3

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 (88) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/README.md +0 -1
  3. package/dist/cli.js +217 -276
  4. package/dist/types/advisor/advise-tool.d.ts +30 -1
  5. package/dist/types/commands/install.d.ts +1 -1
  6. package/dist/types/config/model-resolver.d.ts +8 -0
  7. package/dist/types/config/settings-schema.d.ts +0 -10
  8. package/dist/types/eval/js/shared/runtime.d.ts +1 -0
  9. package/dist/types/eval/js/worker-core.d.ts +1 -0
  10. package/dist/types/extensibility/extensions/loader.d.ts +2 -2
  11. package/dist/types/goals/runtime.d.ts +0 -1
  12. package/dist/types/mcp/tool-bridge.d.ts +3 -0
  13. package/dist/types/modes/components/custom-editor.d.ts +14 -4
  14. package/dist/types/modes/controllers/command-controller.d.ts +1 -1
  15. package/dist/types/modes/interactive-mode.d.ts +1 -1
  16. package/dist/types/modes/setup-wizard/wizard-overlay.d.ts +3 -2
  17. package/dist/types/modes/theme/mermaid-cache.d.ts +18 -1
  18. package/dist/types/modes/types.d.ts +1 -1
  19. package/dist/types/registry/agent-lifecycle.d.ts +16 -1
  20. package/dist/types/sdk.d.ts +8 -0
  21. package/dist/types/session/agent-session.d.ts +20 -8
  22. package/dist/types/session/session-dump-format.d.ts +8 -2
  23. package/dist/types/session/session-entries.d.ts +4 -0
  24. package/dist/types/session/session-history-format.d.ts +2 -0
  25. package/dist/types/session/session-manager.d.ts +22 -0
  26. package/dist/types/stt/downloader.d.ts +5 -5
  27. package/dist/types/task/executor.d.ts +6 -0
  28. package/dist/types/task/persisted-revive.d.ts +36 -0
  29. package/dist/types/tiny/models.d.ts +8 -0
  30. package/dist/types/tools/builtin-names.d.ts +1 -1
  31. package/dist/types/tools/index.d.ts +0 -1
  32. package/package.json +12 -12
  33. package/src/advisor/__tests__/advisor.test.ts +150 -50
  34. package/src/advisor/advise-tool.ts +48 -6
  35. package/src/advisor/runtime.ts +10 -3
  36. package/src/auto-thinking/classifier.ts +12 -3
  37. package/src/cli.ts +2 -2
  38. package/src/commands/install.ts +3 -3
  39. package/src/config/model-resolver.ts +28 -11
  40. package/src/config/settings-schema.ts +0 -11
  41. package/src/eval/agent-bridge.ts +2 -0
  42. package/src/eval/js/context-manager.ts +2 -1
  43. package/src/eval/js/shared/runtime.ts +189 -15
  44. package/src/eval/js/worker-core.ts +19 -0
  45. package/src/export/html/index.ts +1 -1
  46. package/src/export/html/tool-views.generated.js +34 -35
  47. package/src/extensibility/extensions/loader.ts +21 -9
  48. package/src/goals/runtime.ts +1 -23
  49. package/src/internal-urls/docs-index.generated.ts +4 -6
  50. package/src/main.ts +20 -0
  51. package/src/mcp/render.ts +11 -1
  52. package/src/mcp/tool-bridge.ts +3 -0
  53. package/src/modes/components/custom-editor.test.ts +63 -18
  54. package/src/modes/components/custom-editor.ts +63 -15
  55. package/src/modes/controllers/command-controller.ts +2 -2
  56. package/src/modes/controllers/input-controller.ts +15 -9
  57. package/src/modes/controllers/selector-controller.ts +13 -8
  58. package/src/modes/controllers/tan-command-controller.ts +1 -0
  59. package/src/modes/interactive-mode.ts +4 -2
  60. package/src/modes/setup-wizard/wizard-overlay.ts +26 -4
  61. package/src/modes/theme/mermaid-cache.ts +74 -11
  62. package/src/modes/theme/theme.ts +14 -1
  63. package/src/modes/types.ts +1 -1
  64. package/src/prompts/system/system-prompt.md +2 -1
  65. package/src/registry/agent-lifecycle.ts +60 -8
  66. package/src/sdk.ts +20 -26
  67. package/src/session/agent-session.ts +246 -78
  68. package/src/session/artifacts.ts +19 -1
  69. package/src/session/session-dump-format.ts +167 -23
  70. package/src/session/session-entries.ts +4 -0
  71. package/src/session/session-history-format.ts +37 -3
  72. package/src/session/session-manager.ts +94 -4
  73. package/src/slash-commands/builtin-registry.ts +4 -7
  74. package/src/stt/asr-client.ts +6 -0
  75. package/src/stt/downloader.ts +13 -6
  76. package/src/stt/stt-controller.ts +52 -11
  77. package/src/task/executor.ts +18 -2
  78. package/src/task/index.ts +2 -2
  79. package/src/task/persisted-revive.ts +128 -0
  80. package/src/tiny/models.ts +10 -0
  81. package/src/tiny/worker.ts +4 -3
  82. package/src/tools/builtin-names.ts +0 -1
  83. package/src/tools/index.ts +0 -4
  84. package/src/tools/output-meta.ts +17 -3
  85. package/src/utils/title-generator.ts +4 -4
  86. package/dist/types/tools/render-mermaid.d.ts +0 -38
  87. package/src/prompts/tools/render-mermaid.md +0 -9
  88. package/src/tools/render-mermaid.ts +0 -69
@@ -351,6 +351,12 @@ export interface ExecutorOptions {
351
351
  parentTelemetry?: AgentTelemetryConfig;
352
352
  /** Skills to autoload via sendCustomMessage before the first prompt */
353
353
  autoloadSkills?: Skill[];
354
+ /**
355
+ * Registry id of the spawning agent, recorded as this subagent's parent.
356
+ * Forwarded verbatim to the SDK; the executor never derives it (the spawner
357
+ * passes its own `getAgentId()`).
358
+ */
359
+ parentAgentId?: string;
354
360
  }
355
361
 
356
362
  function parseStringifiedJson(value: unknown): unknown {
@@ -1927,7 +1933,12 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
1927
1933
 
1928
1934
  const effectiveCwd = worktree ?? cwd;
1929
1935
  const sessionManager = sessionFile
1930
- ? await awaitAbortable(SessionManager.open(sessionFile, undefined, undefined, { initialCwd: effectiveCwd }))
1936
+ ? await awaitAbortable(
1937
+ SessionManager.open(sessionFile, undefined, undefined, {
1938
+ initialCwd: effectiveCwd,
1939
+ suppressBreadcrumb: true,
1940
+ }),
1941
+ )
1931
1942
  : SessionManager.inMemory(effectiveCwd);
1932
1943
  if (options.parentArtifactManager) {
1933
1944
  sessionManager.adoptArtifactManager(options.parentArtifactManager);
@@ -2016,6 +2027,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
2016
2027
  parentHindsightSessionState: options.parentHindsightSessionState,
2017
2028
  parentMnemopiSessionState: options.parentMnemopiSessionState,
2018
2029
  parentTaskPrefix: id,
2030
+ parentAgentId: options.parentAgentId,
2019
2031
  agentId: id,
2020
2032
  agentDisplayName: subagentDisplayName,
2021
2033
  enableLsp: lspEnabled,
@@ -2048,7 +2060,9 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
2048
2060
  // (createAgentSession → agent.replaceMessages). Isolated runs are not
2049
2061
  // resumable (worktree is merged + cleaned) and never get a reviver.
2050
2062
  reviveSession = async () => {
2051
- const reopened = await SessionManager.open(sessionFile);
2063
+ const reopened = await SessionManager.open(sessionFile, undefined, undefined, {
2064
+ suppressBreadcrumb: true,
2065
+ });
2052
2066
  if (options.parentArtifactManager) {
2053
2067
  reopened.adoptArtifactManager(options.parentArtifactManager);
2054
2068
  }
@@ -2084,6 +2098,8 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
2084
2098
  systemPrompt: session.agent.state.systemPrompt.join("\n\n"),
2085
2099
  task,
2086
2100
  tools: session.getActiveToolNames(),
2101
+ spawns: spawnsEnv,
2102
+ readSummarize: agent.readSummarize,
2087
2103
  outputSchema,
2088
2104
  });
2089
2105
 
package/src/task/index.ts CHANGED
@@ -46,7 +46,7 @@ import "../tools/review";
46
46
  import type { AsyncJobManager } from "../async";
47
47
  import type { LocalProtocolOptions } from "../internal-urls";
48
48
  import { loadOverallPlanReference } from "../plan-mode/plan-handoff";
49
- import { AgentRegistry } from "../registry/agent-registry";
49
+ import { AgentRegistry, MAIN_AGENT_ID } from "../registry/agent-registry";
50
50
  import { generateCommitMessage } from "../utils/commit-message-generator";
51
51
  import * as git from "../utils/git";
52
52
  import { type DiscoveryResult, discoverAgents, getAgent } from "./discovery";
@@ -156,7 +156,6 @@ export const READ_ONLY_TOOL_NAMES: ReadonlySet<string> = new Set([
156
156
  "reflect",
157
157
  "retain",
158
158
  "memory_edit",
159
- "render_mermaid",
160
159
  "inspect_image",
161
160
  "checkpoint",
162
161
  "rewind",
@@ -1301,6 +1300,7 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
1301
1300
  parentMnemopiSessionState: this.session.getMnemopiSessionState?.(),
1302
1301
  parentTelemetry: this.session.getTelemetry?.(),
1303
1302
  parentEvalSessionId,
1303
+ parentAgentId: this.session.getAgentId?.() ?? MAIN_AGENT_ID,
1304
1304
  };
1305
1305
 
1306
1306
  const runTask = async (): Promise<SingleResult> => {
@@ -0,0 +1,128 @@
1
+ import * as fs from "node:fs/promises";
2
+
3
+ import type { ModelRegistry } from "../config/model-registry";
4
+ import type { Settings } from "../config/settings";
5
+ import { MCPManager } from "../mcp/manager";
6
+ import type { PersistedSubagentReviverFactory } from "../registry/agent-lifecycle";
7
+ import { AgentRegistry, MAIN_AGENT_ID } from "../registry/agent-registry";
8
+ import { createAgentSession } from "../sdk";
9
+ import type { AgentSession } from "../session/agent-session";
10
+ import type { AuthStorage } from "../session/auth-storage";
11
+ import { SessionManager } from "../session/session-manager";
12
+ import { createMCPProxyTools, createSubagentSettings } from "./executor";
13
+
14
+ /**
15
+ * Ambient context the reviver needs at revive time. The top-level session is
16
+ * kept LIVE (cwd / artifact manager read on demand) so a later `/new` or cwd
17
+ * move is followed rather than snapshotted; auth/models/settings are
18
+ * process-stable and captured by reference.
19
+ */
20
+ export interface PersistedSubagentReviveContext {
21
+ session: AgentSession;
22
+ authStorage: AuthStorage;
23
+ modelRegistry: ModelRegistry;
24
+ settings: Settings;
25
+ /** LSP policy of the top-level session; revived subagents inherit it rather than defaulting on. */
26
+ enableLsp: boolean;
27
+ }
28
+
29
+ /**
30
+ * Build the factory the {@link AgentLifecycleManager} uses to cold-revive a
31
+ * `parked` subagent ref restored from disk (Agent Hub scan, collab mirror, or a
32
+ * resumed process). Such a ref carries a sessionFile but no in-memory adoption —
33
+ * the executor's live reviver closure died with the process/turn that spawned
34
+ * it — so `ensureLive` (IRC sends, hub focus) would otherwise refuse it.
35
+ *
36
+ * This rebuilds the subagent the same way `--resume` rebuilds a session: reopen
37
+ * the JSONL and replay it through {@link createAgentSession}. The catch is that
38
+ * resume restores only conversation/model from the file — the runtime contract
39
+ * (tools / system prompt / output schema / kind) is built from options, so a
40
+ * bare reopen would resurrect a wrong (top-level) session. We source that
41
+ * contract from the persisted `session_init` entry instead, and mirror the
42
+ * executor's subagent wiring (MCP proxy tools, depth-derived gating,
43
+ * yield-required, active-tool clamp, registry status sync).
44
+ */
45
+ export function createPersistedSubagentReviverFactory(
46
+ ctx: PersistedSubagentReviveContext,
47
+ ): PersistedSubagentReviverFactory {
48
+ const registry = AgentRegistry.global();
49
+ return async ref => {
50
+ const sessionFile = ref.sessionFile;
51
+ if (!sessionFile) return undefined;
52
+ const peek = await SessionManager.peekSessionInit(sessionFile);
53
+ // No persisted contract (pre-session_init file) or the recorded workspace
54
+ // is gone (isolated/merged worktree, moved dir): leave it transcript-only
55
+ // (history://) rather than resurrect a wrong or broken session.
56
+ if (!peek?.init) return undefined;
57
+ try {
58
+ await fs.stat(peek.cwd);
59
+ } catch {
60
+ return undefined;
61
+ }
62
+ const init = peek.init;
63
+ // taskDepth drives real capability gating (task-spawn allowance, memory
64
+ // startup, …); derive it from the persisted parent chain rather than
65
+ // assuming a fixed level.
66
+ let taskDepth = 1;
67
+ let parentId = ref.parentId;
68
+ const seen = new Set<string>();
69
+ while (parentId && parentId !== MAIN_AGENT_ID && !seen.has(parentId)) {
70
+ seen.add(parentId);
71
+ taskDepth++;
72
+ parentId = registry.get(parentId)?.parentId;
73
+ }
74
+ return async () => {
75
+ // Re-open fresh on every revive: park closes the writer, so this takes
76
+ // the single-writer lock cleanly and restores the full message history.
77
+ const reopened = await SessionManager.open(sessionFile, undefined, undefined, {
78
+ suppressBreadcrumb: true,
79
+ });
80
+ const artifactManager = ctx.session.sessionManager.getArtifactManager();
81
+ if (artifactManager) reopened.adoptArtifactManager(artifactManager);
82
+ // Reuse the parent's live MCP connections via proxy tools (no
83
+ // re-discovery), exactly as the executor does for live subagents.
84
+ const mcpManager = MCPManager.instance();
85
+ const mcpProxyTools = mcpManager ? createMCPProxyTools(mcpManager) : [];
86
+ const { session } = await createAgentSession({
87
+ cwd: ctx.session.sessionManager.getCwd(),
88
+ authStorage: ctx.authStorage,
89
+ modelRegistry: ctx.modelRegistry,
90
+ settings: createSubagentSettings(
91
+ ctx.settings,
92
+ init.readSummarize === false ? { "read.summarize.enabled": false } : undefined,
93
+ ),
94
+ sessionManager: reopened,
95
+ agentId: ref.id,
96
+ agentDisplayName: ref.displayName,
97
+ parentTaskPrefix: ref.id,
98
+ parentAgentId: ref.parentId,
99
+ taskDepth,
100
+ toolNames: init.tools,
101
+ outputSchema: init.outputSchema,
102
+ requireYieldTool: true,
103
+ systemPrompt: () => [init.systemPrompt],
104
+ // Old files predate persisted spawns: deny re-spawning rather than let
105
+ // createAgentSession default to wildcard ("*").
106
+ spawns: init.spawns ?? "",
107
+ hasUI: false,
108
+ enableLsp: ctx.enableLsp,
109
+ enableMCP: !mcpManager,
110
+ mcpManager,
111
+ customTools: mcpProxyTools.length > 0 ? mcpProxyTools : undefined,
112
+ });
113
+ // Clamp the active set to the persisted list: createAgentSession's
114
+ // `alwaysInclude` can re-add non-defaultInactive extension/custom tools
115
+ // the original run didn't carry. Unknown/missing names are ignored.
116
+ await session.setActiveToolsByName(init.tools);
117
+ // Cold revives must drive registry status themselves — createAgentSession
118
+ // doesn't wire this generically (the live path does it in the executor).
119
+ // Without it the idle-TTL timer never clears on a turn and the lifecycle
120
+ // could park the agent mid-run.
121
+ session.subscribe(event => {
122
+ if (event.type === "agent_start") registry.setStatus(ref.id, "running");
123
+ else if (event.type === "agent_end") registry.setStatus(ref.id, "idle");
124
+ });
125
+ return session;
126
+ };
127
+ };
128
+ }
@@ -10,6 +10,8 @@ export interface TinyTitleLocalModelSpec {
10
10
  label: string;
11
11
  description: string;
12
12
  contextNote: string;
13
+ /** Model family emits hidden reasoning unless the chat template disables it. */
14
+ reasoning?: boolean;
13
15
  }
14
16
 
15
17
  export const TINY_TITLE_LOCAL_MODELS = [
@@ -28,6 +30,7 @@ export const TINY_TITLE_LOCAL_MODELS = [
28
30
  label: "Qwen3 0.6B",
29
31
  description: "Most robust local option; slower first load, about 500 MB cached.",
30
32
  contextNote: "Use when title quality matters more than local startup cost.",
33
+ reasoning: true,
31
34
  },
32
35
  {
33
36
  key: "gemma-270m",
@@ -122,6 +125,7 @@ export const TINY_MEMORY_LOCAL_MODELS = [
122
125
  description:
123
126
  "Recommended; most disciplined extraction (ignores chit-chat), good consolidation, about 1.1 GB cached.",
124
127
  contextNote: "Best single-model pick for memory from the local experiment.",
128
+ reasoning: true,
125
129
  },
126
130
  {
127
131
  key: "gemma-3-1b",
@@ -196,6 +200,12 @@ export function getTinyMemoryModelSpec(key: TinyMemoryLocalModelKey): (typeof TI
196
200
  return spec;
197
201
  }
198
202
 
203
+ /** Return whether a memory local model may emit reasoning tokens before answers. */
204
+ export function isTinyMemoryReasoningModelKey(key: TinyMemoryLocalModelKey): boolean {
205
+ const spec = getTinyMemoryModelSpec(key);
206
+ return "reasoning" in spec && spec.reasoning === true;
207
+ }
208
+
199
209
  /** Any local model key (title or memory), used by the shared inference worker. */
200
210
  export type TinyLocalModelKey = TinyTitleLocalModelKey | TinyMemoryLocalModelKey;
201
211
 
@@ -31,7 +31,8 @@ const TITLE_PREFILL = "<title>";
31
31
  const TITLE_CLOSE = "</title>";
32
32
  const TITLE_MAX_NEW_TOKENS = 20;
33
33
  const STOP_DECODE_WINDOW_TOKENS = 32;
34
- const MEMORY_COMPLETION_MAX_NEW_TOKENS = 256;
34
+ const MEMORY_COMPLETION_DEFAULT_MAX_NEW_TOKENS = 256;
35
+ const COMPLETION_MAX_NEW_TOKENS = 1024;
35
36
  const TINY_TITLE_SYSTEM_PROMPT = prompt.render(tinyTitleSystemPrompt);
36
37
  const TRANSFORMERS_PACKAGE = "@huggingface/transformers";
37
38
  const COMPILED_TRANSFORMERS_VERSION = process.env.PI_TINY_TRANSFORMERS_VERSION;
@@ -426,8 +427,8 @@ async function generateCompletion(
426
427
  ): Promise<string | null> {
427
428
  const generator = await loadPipeline(modelKey, transport, requestId);
428
429
  const text = buildCompletionPrompt(generator, promptText);
429
- const requested = maxTokens ?? MEMORY_COMPLETION_MAX_NEW_TOKENS;
430
- const maxNewTokens = Math.min(Math.max(1, requested), MEMORY_COMPLETION_MAX_NEW_TOKENS);
430
+ const requested = maxTokens ?? MEMORY_COMPLETION_DEFAULT_MAX_NEW_TOKENS;
431
+ const maxNewTokens = Math.min(Math.max(1, requested), COMPLETION_MAX_NEW_TOKENS);
431
432
  const output = (await generator(text, {
432
433
  max_new_tokens: maxNewTokens,
433
434
  do_sample: false,
@@ -4,7 +4,6 @@ export const BUILTIN_TOOL_NAMES = [
4
4
  "edit",
5
5
  "ast_grep",
6
6
  "ast_edit",
7
- "render_mermaid",
8
7
  "ask",
9
8
  "debug",
10
9
  "eval",
@@ -55,7 +55,6 @@ import { MemoryReflectTool } from "./memory-reflect";
55
55
  import { MemoryRetainTool } from "./memory-retain";
56
56
  import { wrapToolWithMetaNotice } from "./output-meta";
57
57
  import { ReadTool } from "./read";
58
- import { RenderMermaidTool } from "./render-mermaid";
59
58
  import { createReportToolIssueTool, isAutoQaEnabled } from "./report-tool-issue";
60
59
  import { ResolveTool } from "./resolve";
61
60
  import { reportFindingTool } from "./review";
@@ -94,7 +93,6 @@ export * from "./memory-recall";
94
93
  export * from "./memory-reflect";
95
94
  export * from "./memory-retain";
96
95
  export * from "./read";
97
- export * from "./render-mermaid";
98
96
  export * from "./report-tool-issue";
99
97
  export * from "./resolve";
100
98
  export * from "./review";
@@ -420,7 +418,6 @@ export const BUILTIN_TOOLS: Record<BuiltinToolName, ToolFactory> = {
420
418
  edit: s => new EditTool(s),
421
419
  ast_grep: s => new AstGrepTool(s),
422
420
  ast_edit: s => new AstEditTool(s),
423
- render_mermaid: s => new RenderMermaidTool(s),
424
421
  ask: AskTool.createIf,
425
422
  debug: DebugTool.createIf,
426
423
  eval: s => new EvalTool(s),
@@ -563,7 +560,6 @@ export async function createTools(session: ToolSession, toolNames?: string[]): P
563
560
  if (name === "github") return session.settings.get("github.enabled");
564
561
  if (name === "ast_grep") return session.settings.get("astGrep.enabled");
565
562
  if (name === "ast_edit") return session.settings.get("astEdit.enabled");
566
- if (name === "render_mermaid") return session.settings.get("renderMermaid.enabled");
567
563
  if (name === "inspect_image") return session.settings.get("inspect_image.enabled");
568
564
  if (name === "web_search") return session.settings.get("web_search.enabled");
569
565
  // search_tool_bm25 is allowed when either legacy mcp.discoveryMode or new tools.discoveryMode is active.
@@ -12,6 +12,7 @@ import type {
12
12
  AgentToolUpdateCallback,
13
13
  } from "@oh-my-pi/pi-agent-core";
14
14
  import type { ImageContent, TextContent } from "@oh-my-pi/pi-ai";
15
+ import { logger } from "@oh-my-pi/pi-utils";
15
16
  import { getDefault, type Settings } from "../config/settings";
16
17
  import { formatGroupedDiagnosticMessages } from "../lsp/utils";
17
18
  import type { Theme } from "../modes/theme/theme";
@@ -616,9 +617,22 @@ async function spillLargeResultToArtifact(
616
617
  const totalBytes = Buffer.byteLength(fullText, "utf-8");
617
618
  if (totalBytes <= threshold) return result;
618
619
 
619
- // Save full output as artifact
620
- const artifactId = await sessionManager.saveArtifact(fullText, toolName);
621
- if (!artifactId) return result;
620
+ // Save the full output as an artifact so the elided bytes stay recoverable.
621
+ // In a persistent session this hits `Bun.write`, which can throw (disk full,
622
+ // permissions). The spill wraps arbitrary tools (built-in, MCP, extension,
623
+ // RPC-host); a save failure must never convert a successful call into an
624
+ // error, nor re-expose the full (possibly context-blowing) output. Mirror
625
+ // `enforceInlineByteCap`: always truncate past the threshold, and only
626
+ // attach the `artifact://` recovery link when the save actually succeeded.
627
+ let artifactId: string | undefined;
628
+ try {
629
+ artifactId = await sessionManager.saveArtifact(fullText, toolName);
630
+ } catch (error) {
631
+ logger.warn("Failed to spill large tool result to artifact", {
632
+ tool: toolName,
633
+ error: error instanceof Error ? error.message : String(error),
634
+ });
635
+ }
622
636
 
623
637
  // Truncate: middle elision when a head budget is configured, otherwise tail-only.
624
638
  const useMiddle = headBytes > 0;
@@ -4,7 +4,7 @@
4
4
  import * as path from "node:path";
5
5
 
6
6
  import { type Api, type AssistantMessage, completeSimple, type Model, type Tool } from "@oh-my-pi/pi-ai";
7
- import { logger, prompt } from "@oh-my-pi/pi-utils";
7
+ import { isTerminalHeadless, logger, prompt } from "@oh-my-pi/pi-utils";
8
8
  import type { ModelRegistry } from "../config/model-registry";
9
9
 
10
10
  import { resolveRoleSelection } from "../config/model-resolver";
@@ -391,7 +391,7 @@ export function formatSessionTerminalTitle(sessionName: string | undefined, cwd?
391
391
  * Set the terminal title using OSC 0 (sets both tab and window title). Unsupported terminals ignore it.
392
392
  */
393
393
  export function setTerminalTitle(title: string): void {
394
- if (!process.stdout.isTTY) return;
394
+ if (!process.stdout.isTTY || isTerminalHeadless()) return;
395
395
  process.stdout.write(`\x1b]0;${sanitizeTerminalTitlePart(title) ?? DEFAULT_TERMINAL_TITLE}\x07`);
396
396
  }
397
397
 
@@ -403,7 +403,7 @@ export function setSessionTerminalTitle(sessionName: string | undefined, cwd?: s
403
403
  * Save the current terminal title on terminals that support xterm window ops.
404
404
  */
405
405
  export function pushTerminalTitle(): void {
406
- if (!process.stdout.isTTY) return;
406
+ if (!process.stdout.isTTY || isTerminalHeadless()) return;
407
407
  process.stdout.write("\x1b[22;2t");
408
408
  }
409
409
 
@@ -411,6 +411,6 @@ export function pushTerminalTitle(): void {
411
411
  * Restore the previously saved terminal title on terminals that support xterm window ops.
412
412
  */
413
413
  export function popTerminalTitle(): void {
414
- if (!process.stdout.isTTY) return;
414
+ if (!process.stdout.isTTY || isTerminalHeadless()) return;
415
415
  process.stdout.write("\x1b[23;2t");
416
416
  }
@@ -1,38 +0,0 @@
1
- import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
2
- import { z } from "zod/v4";
3
- import type { ToolSession } from "./index";
4
- declare const renderMermaidSchema: z.ZodObject<{
5
- mermaid: z.ZodString;
6
- config: z.ZodOptional<z.ZodObject<{
7
- useAscii: z.ZodOptional<z.ZodBoolean>;
8
- paddingX: z.ZodOptional<z.ZodNumber>;
9
- paddingY: z.ZodOptional<z.ZodNumber>;
10
- boxBorderPadding: z.ZodOptional<z.ZodNumber>;
11
- }, z.core.$strip>>;
12
- }, z.core.$strip>;
13
- type RenderMermaidParams = z.infer<typeof renderMermaidSchema>;
14
- export interface RenderMermaidToolDetails {
15
- artifactId?: string;
16
- }
17
- export declare class RenderMermaidTool implements AgentTool<typeof renderMermaidSchema, RenderMermaidToolDetails> {
18
- private readonly session;
19
- readonly name = "render_mermaid";
20
- readonly approval: "read";
21
- readonly label = "RenderMermaid";
22
- readonly summary = "Render a Mermaid diagram to an image";
23
- readonly description: string;
24
- readonly parameters: z.ZodObject<{
25
- mermaid: z.ZodString;
26
- config: z.ZodOptional<z.ZodObject<{
27
- useAscii: z.ZodOptional<z.ZodBoolean>;
28
- paddingX: z.ZodOptional<z.ZodNumber>;
29
- paddingY: z.ZodOptional<z.ZodNumber>;
30
- boxBorderPadding: z.ZodOptional<z.ZodNumber>;
31
- }, z.core.$strip>>;
32
- }, z.core.$strip>;
33
- readonly strict = true;
34
- readonly loadMode = "discoverable";
35
- constructor(session: ToolSession);
36
- execute(_toolCallId: string, params: RenderMermaidParams, _signal?: AbortSignal, _onUpdate?: AgentToolUpdateCallback<RenderMermaidToolDetails>, _context?: AgentToolContext): Promise<AgentToolResult<RenderMermaidToolDetails>>;
37
- }
38
- export {};
@@ -1,9 +0,0 @@
1
- Convert Mermaid graph source into ASCII diagram output.
2
-
3
- Parameters:
4
- - `mermaid` (required): Mermaid graph text to render.
5
- - `config` (optional): JSON render configuration (spacing and layout options).
6
-
7
- Behavior:
8
- - Returns ASCII diagram text.
9
- - Saves full output to `artifact://<id>`.
@@ -1,69 +0,0 @@
1
- import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
2
- import { type MermaidAsciiRenderOptions, prompt, renderMermaidAscii } from "@oh-my-pi/pi-utils";
3
- import { z } from "zod/v4";
4
- import renderMermaidDescription from "../prompts/tools/render-mermaid.md" with { type: "text" };
5
- import type { ToolSession } from "./index";
6
-
7
- const renderMermaidSchema = z.object({
8
- mermaid: z.string().describe("mermaid source"),
9
- config: z
10
- .object({
11
- useAscii: z.boolean().optional(),
12
- paddingX: z.number().optional(),
13
- paddingY: z.number().optional(),
14
- boxBorderPadding: z.number().optional(),
15
- })
16
- .optional(),
17
- });
18
-
19
- type RenderMermaidParams = z.infer<typeof renderMermaidSchema>;
20
-
21
- function sanitizeRenderConfig(config: MermaidAsciiRenderOptions | undefined): MermaidAsciiRenderOptions | undefined {
22
- if (!config) return undefined;
23
- return {
24
- useAscii: config.useAscii,
25
- boxBorderPadding:
26
- config.boxBorderPadding === undefined ? undefined : Math.max(0, Math.floor(config.boxBorderPadding)),
27
- paddingX: config.paddingX === undefined ? undefined : Math.max(0, Math.floor(config.paddingX)),
28
- paddingY: config.paddingY === undefined ? undefined : Math.max(0, Math.floor(config.paddingY)),
29
- };
30
- }
31
- export interface RenderMermaidToolDetails {
32
- artifactId?: string;
33
- }
34
-
35
- export class RenderMermaidTool implements AgentTool<typeof renderMermaidSchema, RenderMermaidToolDetails> {
36
- readonly name = "render_mermaid";
37
- readonly approval = "read" as const;
38
- readonly label = "RenderMermaid";
39
- readonly summary = "Render a Mermaid diagram to an image";
40
- readonly description: string;
41
- readonly parameters = renderMermaidSchema;
42
- readonly strict = true;
43
- readonly loadMode = "discoverable";
44
-
45
- constructor(private readonly session: ToolSession) {
46
- this.description = prompt.render(renderMermaidDescription);
47
- }
48
-
49
- async execute(
50
- _toolCallId: string,
51
- params: RenderMermaidParams,
52
- _signal?: AbortSignal,
53
- _onUpdate?: AgentToolUpdateCallback<RenderMermaidToolDetails>,
54
- _context?: AgentToolContext,
55
- ): Promise<AgentToolResult<RenderMermaidToolDetails>> {
56
- const ascii = renderMermaidAscii(params.mermaid, sanitizeRenderConfig(params.config));
57
- const { path: artifactPath, id: artifactId } =
58
- (await this.session.allocateOutputArtifact?.("render_mermaid")) ?? {};
59
- if (artifactPath) {
60
- await Bun.write(artifactPath, ascii);
61
- }
62
-
63
- const artifactLine = artifactId ? `\n\nSaved artifact: artifact://${artifactId}` : "";
64
- return {
65
- content: [{ type: "text", text: `${ascii}${artifactLine}` }],
66
- details: { artifactId },
67
- };
68
- }
69
- }