@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
@@ -2761,6 +2761,18 @@ export function getMarkdownTheme(): MarkdownTheme {
2761
2761
  if (cachedMarkdownTheme !== undefined && cachedMarkdownThemeRef === theme) {
2762
2762
  return cachedMarkdownTheme;
2763
2763
  }
2764
+ // Mermaid ASCII diagrams render with the active palette so they read as
2765
+ // content rather than raw monochrome. Roles mirror the SVG renderer's
2766
+ // mapping; `text`/`muted`/`border`/`borderMuted`/`accent` exist in every theme.
2767
+ const mermaidColorMode = theme.getColorMode() === "truecolor" ? "truecolor" : "ansi256";
2768
+ const mermaidTheme = {
2769
+ fg: theme.getColorHex("text"),
2770
+ border: theme.getColorHex("border"),
2771
+ line: theme.getColorHex("muted"),
2772
+ arrow: theme.getColorHex("accent"),
2773
+ corner: theme.getColorHex("muted"),
2774
+ junction: theme.getColorHex("borderMuted"),
2775
+ };
2764
2776
  const markdownTheme: MarkdownTheme = {
2765
2777
  heading: (text: string) => theme.fg("mdHeading", text),
2766
2778
  link: (text: string) => theme.fg("mdLink", text),
@@ -2777,7 +2789,8 @@ export function getMarkdownTheme(): MarkdownTheme {
2777
2789
  underline: (text: string) => theme.underline(text),
2778
2790
  strikethrough: (text: string) => chalk.strikethrough(text),
2779
2791
  symbols: getSymbolTheme(),
2780
- resolveMermaidAscii,
2792
+ resolveMermaidAscii: (source, maxWidth) =>
2793
+ resolveMermaidAscii(source, { maxWidth, theme: mermaidTheme, colorMode: mermaidColorMode }),
2781
2794
  highlightCode: (code: string, lang?: string): string[] => {
2782
2795
  const validLang = lang && nativeSupportsLanguage(lang) ? lang : undefined;
2783
2796
  const highlighted = highlightCached(code, validLang, theme);
@@ -282,7 +282,7 @@ export interface InteractiveModeContext {
282
282
  handleHotkeysCommand(): void;
283
283
  handleToolsCommand(): void;
284
284
  handleContextCommand(): void;
285
- handleDumpCommand(isRaw?: boolean): void;
285
+ handleDumpCommand(): void;
286
286
  handleAdvisorDumpCommand(isRaw?: boolean): void;
287
287
  handleDebugTranscriptCommand(): Promise<void>;
288
288
  handleClearCommand(): Promise<void>;
@@ -13,6 +13,8 @@ You are a helpful assistant the team trusts with load-bearing changes, operating
13
13
  - You have agency and taste: you delete code that isn't pulling its weight, refuse abstractions that are unnecessary, and prefer boring when it's called for; but when you design thoroughly, you do so elegantly and efficiently.
14
14
  - Consider what code compiles to. NEVER allocate even a simple string when avoidable. No copies, no expensive computations unless absolutely necessary.
15
15
  - You are not alone in this repository. You SHOULD treat unexpected changes as the user's work and adapt.
16
+ - In user-visible terminal prose and final chat, you MAY use LaTeX math delimiters (such as $ or $$) and LaTeX math commands (such as \text, \times) to format equations, as well as (`\textcolor`, `\colorbox`, `\fcolorbox`) to colorize the output.
17
+ - To show the user a diagram (flowchart, sequence, state, ER, etc.), you MAY emit a fenced ` ```mermaid ` code block in your final chat — the terminal renders Mermaid source as an ASCII diagram. Keep it for genuine structure/flow; prefer prose for trivial points.
16
18
 
17
19
  TOOLS
18
20
  ===================================
@@ -29,7 +31,6 @@ Use tools whenever they materially improve correctness, completeness, or groundi
29
31
  {{#if intentTracing}}- Most tools have a `{{intentField}}` parameter. Fill it with a concise intent in present participle form, 2-6 words, no period, capitalized.{{/if}}
30
32
  {{#if secretsEnabled}}- Some values in tool output are intentionally redacted as `#XXXX#` tokens. Treat them as opaque strings.{{/if}}
31
33
  {{#has tools "inspect_image"}}- For image understanding tasks you SHOULD use `{{toolRefs.inspect_image}}` over `{{toolRefs.read}}` to avoid overloading session context.{{/has}}
32
- - In user-visible terminal prose and final chat, avoid LaTeX math delimiters (such as $ or $$) and LaTeX math commands (such as \text, \times) — the terminal cannot render them. Write equations in plain text / Unicode instead (e.g. BMR = 370 + (21.6 × 63.87) = 1,750 kcal). This does NOT apply to tool output or LaTeX/Markdown/KaTeX content you are asked to write to files.
33
34
 
34
35
  # Tool Priority
35
36
  You MUST use the specialized tool over its shell equivalent:
@@ -12,10 +12,19 @@
12
12
 
13
13
  import { logger } from "@oh-my-pi/pi-utils";
14
14
  import type { AgentSession } from "../session/agent-session";
15
- import { AgentRegistry, MAIN_AGENT_ID, type RegistryEvent } from "./agent-registry";
15
+ import { type AgentRef, AgentRegistry, MAIN_AGENT_ID, type RegistryEvent } from "./agent-registry";
16
16
 
17
17
  export type AgentReviver = () => Promise<AgentSession>;
18
18
 
19
+ /**
20
+ * Builds a reviver for a `parked` ref restored from disk (Agent Hub scan,
21
+ * collab mirror, resumed process) that carries a sessionFile but no in-memory
22
+ * adoption. Returns undefined when the ref cannot be faithfully rebuilt (no
23
+ * persisted session contract, or its workspace is gone). Injected from the
24
+ * top-level session so this manager stays free of sdk/SessionManager imports.
25
+ */
26
+ export type PersistedSubagentReviverFactory = (ref: AgentRef) => Promise<AgentReviver | undefined>;
27
+
19
28
  export interface AdoptOptions {
20
29
  /** TTL before an idle agent is parked. <= 0 disables parking. */
21
30
  idleTtlMs: number;
@@ -51,6 +60,7 @@ export class AgentLifecycleManager {
51
60
  current.#adopted.clear();
52
61
  current.#revivals.clear();
53
62
  current.#parking.clear();
63
+ current.#persistedReviverFactory = undefined;
54
64
  }
55
65
  AgentLifecycleManager.#global = undefined;
56
66
  }
@@ -62,12 +72,26 @@ export class AgentLifecycleManager {
62
72
  /** In-flight revives, so concurrent {@link ensureLive} calls coalesce. */
63
73
  readonly #revivals = new Map<string, Promise<AgentSession>>();
64
74
  #unsubscribe: (() => void) | undefined;
75
+ #persistedReviverFactory: PersistedSubagentReviverFactory | undefined;
76
+ /** TTL applied when a cold-revived ref is adopted on demand. */
77
+ #persistedReviveTtlMs = 0;
65
78
 
66
79
  constructor(registry: AgentRegistry = AgentRegistry.global()) {
67
80
  this.#registry = registry;
68
81
  this.#unsubscribe = registry.onChange(event => this.#onRegistryEvent(event));
69
82
  }
70
83
 
84
+ /**
85
+ * Install the factory used to cold-revive `parked` refs restored from disk
86
+ * (Agent Hub scan, collab mirror, resumed process) — they carry a sessionFile
87
+ * but no adoption. Set by the top-level session, which owns the ambient deps
88
+ * (auth, models, MCP, artifacts) the factory needs at revive time.
89
+ */
90
+ setPersistedSubagentReviverFactory(factory: PersistedSubagentReviverFactory, idleTtlMs: number): void {
91
+ this.#persistedReviverFactory = factory;
92
+ this.#persistedReviveTtlMs = idleTtlMs;
93
+ }
94
+
71
95
  /**
72
96
  * Take ownership of a finished subagent. Caller has already set registry
73
97
  * status to "idle". Arms the TTL timer (idleTtlMs <= 0 adopts without one).
@@ -137,13 +161,7 @@ export class AgentLifecycleManager {
137
161
  if (ref.session) return ref.session;
138
162
  const inflight = this.#revivals.get(id);
139
163
  if (inflight) return inflight;
140
- const adopted = this.#adopted.get(id);
141
- if (ref.status !== "parked" || !adopted?.revive) {
142
- throw new Error(
143
- `Agent "${id}" is ${ref.status} and cannot be revived${adopted?.revive ? "" : " (no reviver registered)"}. Its transcript remains readable at history://${id}.`,
144
- );
145
- }
146
- const revival = this.#revive(id, adopted.revive, ref.sessionFile);
164
+ const revival = this.#resolveAndRevive(id, ref);
147
165
  this.#revivals.set(id, revival);
148
166
  try {
149
167
  return await revival;
@@ -152,6 +170,39 @@ export class AgentLifecycleManager {
152
170
  }
153
171
  }
154
172
 
173
+ /**
174
+ * Resolve a reviver and bring the agent back to a live session. A ref
175
+ * restored from disk is `parked` with a sessionFile but no in-memory
176
+ * adoption; build a reviver via the injected persisted-subagent factory and
177
+ * adopt it so the agent rejoins the normal idle↔parked lifecycle. Throws
178
+ * when the agent is not revivable or no reviver can be produced.
179
+ */
180
+ async #resolveAndRevive(id: string, ref: AgentRef): Promise<AgentSession> {
181
+ let revive = this.#adopted.get(id)?.revive;
182
+ let coldAdopted = false;
183
+ if (!revive && ref.status === "parked" && ref.sessionFile && this.#persistedReviverFactory) {
184
+ revive = await this.#persistedReviverFactory(ref);
185
+ if (revive) {
186
+ this.#adopted.set(id, { idleTtlMs: this.#persistedReviveTtlMs, revive });
187
+ coldAdopted = true;
188
+ }
189
+ }
190
+ if (ref.status !== "parked" || !revive) {
191
+ throw new Error(
192
+ `Agent "${id}" is ${ref.status} and cannot be revived${revive ? "" : " (no reviver registered)"}. Its transcript remains readable at history://${id}.`,
193
+ );
194
+ }
195
+ try {
196
+ return await this.#revive(id, revive, ref.sessionFile);
197
+ } catch (error) {
198
+ // A failed cold revive (stale ctx, missing cwd, bad MCP) must not leave a
199
+ // poisoned reviver stuck in #adopted — drop it so a later ensureLive
200
+ // rebuilds via the factory (which may have fresher context by then).
201
+ if (coldAdopted) this.#adopted.delete(id);
202
+ throw error;
203
+ }
204
+ }
205
+
155
206
  /** Hard removal: dispose if live, unregister from registry, drop timers. */
156
207
  async release(id: string): Promise<void> {
157
208
  const adopted = this.#adopted.get(id);
@@ -176,6 +227,7 @@ export class AgentLifecycleManager {
176
227
  await Promise.all(ids.map(id => this.release(id)));
177
228
  this.#revivals.clear();
178
229
  this.#parking.clear();
230
+ this.#persistedReviverFactory = undefined;
179
231
  }
180
232
 
181
233
  async #revive(id: string, revive: AgentReviver, sessionFile: string | null): Promise<AgentSession> {
package/src/sdk.ts CHANGED
@@ -21,7 +21,6 @@ import {
21
21
  getOpenAICodexTransportDetails,
22
22
  prewarmOpenAICodexResponses,
23
23
  } from "@oh-my-pi/pi-ai/providers/openai-codex-responses";
24
- import { DEFAULT_MODEL_PER_PROVIDER } from "@oh-my-pi/pi-catalog/provider-models";
25
24
  import type { Component } from "@oh-my-pi/pi-tui";
26
25
  import {
27
26
  $env,
@@ -49,6 +48,7 @@ import {
49
48
  getModelMatchPreferences,
50
49
  parseModelPattern,
51
50
  parseModelString,
51
+ pickDefaultAvailableModel,
52
52
  resolveAllowedModels,
53
53
  resolveModelRoleValue,
54
54
  } from "./config/model-resolver";
@@ -507,6 +507,14 @@ export interface CreateAgentSessionOptions {
507
507
  agentRegistry?: AgentRegistry;
508
508
  /** Parent task ID prefix for nested artifact naming (e.g., "Extensions") */
509
509
  parentTaskPrefix?: string;
510
+ /**
511
+ * Registry id of the spawning agent, recorded as this subagent's parent in
512
+ * the agent registry. Distinct from `parentTaskPrefix`, which is this agent's
513
+ * own artifact/output-id prefix (the executor passes the child's own id
514
+ * there, so it must never double as the parent link). Undefined for the
515
+ * top-level "Main" session, which has no parent.
516
+ */
517
+ parentAgentId?: string;
510
518
  /** Inherited eval executor session id for subagents sharing parent eval state. */
511
519
  parentEvalSessionId?: string;
512
520
 
@@ -1928,29 +1936,9 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1928
1936
  // Re-resolve the allowed set: extension factories above may have
1929
1937
  // registered providers/models that weren't visible at startup.
1930
1938
  const fallbackCandidates = await resolveAllowedModels(modelRegistry, settings, modelMatchPreferences);
1931
- // Prefer each provider's configured default model
1932
- // (DEFAULT_MODEL_PER_PROVIDER) over raw catalog order. Without this the
1933
- // first-run fallback picks whatever model sorts first in models.json for
1934
- // the winning provider (e.g. anthropic's claude-3-5-sonnet-20240620)
1935
- // instead of the intended provider default (claude-sonnet-4-6). Mirrors
1936
- // findInitialModel's precedence.
1937
- for (const [provider, defaultId] of Object.entries(DEFAULT_MODEL_PER_PROVIDER)) {
1938
- const preferred = fallbackCandidates.find(
1939
- candidate => candidate.provider === provider && candidate.id === defaultId,
1940
- );
1941
- if (preferred && hasModelAuth(preferred)) {
1942
- model = preferred;
1943
- break;
1944
- }
1945
- }
1946
- // Otherwise, first available model with a valid API key.
1947
- if (!model) {
1948
- for (const candidate of fallbackCandidates) {
1949
- if (hasModelAuth(candidate)) {
1950
- model = candidate;
1951
- break;
1952
- }
1953
- }
1939
+ const defaultModel = pickDefaultAvailableModel(fallbackCandidates.filter(hasModelAuth));
1940
+ if (defaultModel) {
1941
+ model = defaultModel;
1954
1942
  }
1955
1943
  if (model) {
1956
1944
  if (modelFallbackMessage) {
@@ -2020,7 +2008,13 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2020
2008
  return { definition, extensionPath: "<sdk>" };
2021
2009
  }) ?? []),
2022
2010
  ];
2023
- const wrappedExtensionTools: Tool[] = wrapRegisteredTools(allCustomTools, extensionRunner);
2011
+ // `wrapToolWithMetaNotice` runs the centralized large-output → artifact spill.
2012
+ // Built-in tools get it in `createTools`; extension, SDK-custom, image-gen,
2013
+ // TTS, and startup (non-deferred) MCP tools all funnel through here, so apply
2014
+ // it once at this adapter boundary (idempotent — a no-op if already wrapped).
2015
+ const wrappedExtensionTools: Tool[] = wrapRegisteredTools(allCustomTools, extensionRunner).map(
2016
+ wrapToolWithMetaNotice,
2017
+ );
2024
2018
 
2025
2019
  // All built-in tools are active (conditional tools like git/ask return null from factory if disabled)
2026
2020
  const toolRegistry = new Map<string, Tool>();
@@ -2338,7 +2332,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2338
2332
  id: resolvedAgentId,
2339
2333
  displayName: resolvedAgentDisplayName,
2340
2334
  kind: agentKind,
2341
- parentId: options.parentTaskPrefix,
2335
+ parentId: options.parentAgentId,
2342
2336
  session: null,
2343
2337
  sessionFile: sessionManager.getSessionFile() ?? null,
2344
2338
  status: "running",