@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.
- package/CHANGELOG.md +45 -0
- package/README.md +0 -1
- package/dist/cli.js +217 -276
- package/dist/types/advisor/advise-tool.d.ts +30 -1
- package/dist/types/commands/install.d.ts +1 -1
- package/dist/types/config/model-resolver.d.ts +8 -0
- package/dist/types/config/settings-schema.d.ts +0 -10
- package/dist/types/eval/js/shared/runtime.d.ts +1 -0
- package/dist/types/eval/js/worker-core.d.ts +1 -0
- package/dist/types/extensibility/extensions/loader.d.ts +2 -2
- package/dist/types/goals/runtime.d.ts +0 -1
- package/dist/types/mcp/tool-bridge.d.ts +3 -0
- package/dist/types/modes/components/custom-editor.d.ts +14 -4
- package/dist/types/modes/controllers/command-controller.d.ts +1 -1
- package/dist/types/modes/interactive-mode.d.ts +1 -1
- package/dist/types/modes/setup-wizard/wizard-overlay.d.ts +3 -2
- package/dist/types/modes/theme/mermaid-cache.d.ts +18 -1
- package/dist/types/modes/types.d.ts +1 -1
- package/dist/types/registry/agent-lifecycle.d.ts +16 -1
- package/dist/types/sdk.d.ts +8 -0
- package/dist/types/session/agent-session.d.ts +20 -8
- package/dist/types/session/session-dump-format.d.ts +8 -2
- package/dist/types/session/session-entries.d.ts +4 -0
- package/dist/types/session/session-history-format.d.ts +2 -0
- package/dist/types/session/session-manager.d.ts +22 -0
- package/dist/types/stt/downloader.d.ts +5 -5
- package/dist/types/task/executor.d.ts +6 -0
- package/dist/types/task/persisted-revive.d.ts +36 -0
- package/dist/types/tiny/models.d.ts +8 -0
- package/dist/types/tools/builtin-names.d.ts +1 -1
- package/dist/types/tools/index.d.ts +0 -1
- package/package.json +12 -12
- package/src/advisor/__tests__/advisor.test.ts +150 -50
- package/src/advisor/advise-tool.ts +48 -6
- package/src/advisor/runtime.ts +10 -3
- package/src/auto-thinking/classifier.ts +12 -3
- package/src/cli.ts +2 -2
- package/src/commands/install.ts +3 -3
- package/src/config/model-resolver.ts +28 -11
- package/src/config/settings-schema.ts +0 -11
- package/src/eval/agent-bridge.ts +2 -0
- package/src/eval/js/context-manager.ts +2 -1
- package/src/eval/js/shared/runtime.ts +189 -15
- package/src/eval/js/worker-core.ts +19 -0
- package/src/export/html/index.ts +1 -1
- package/src/export/html/tool-views.generated.js +34 -35
- package/src/extensibility/extensions/loader.ts +21 -9
- package/src/goals/runtime.ts +1 -23
- package/src/internal-urls/docs-index.generated.ts +4 -6
- package/src/main.ts +20 -0
- package/src/mcp/render.ts +11 -1
- package/src/mcp/tool-bridge.ts +3 -0
- package/src/modes/components/custom-editor.test.ts +63 -18
- package/src/modes/components/custom-editor.ts +63 -15
- package/src/modes/controllers/command-controller.ts +2 -2
- package/src/modes/controllers/input-controller.ts +15 -9
- package/src/modes/controllers/selector-controller.ts +13 -8
- package/src/modes/controllers/tan-command-controller.ts +1 -0
- package/src/modes/interactive-mode.ts +4 -2
- package/src/modes/setup-wizard/wizard-overlay.ts +26 -4
- package/src/modes/theme/mermaid-cache.ts +74 -11
- package/src/modes/theme/theme.ts +14 -1
- package/src/modes/types.ts +1 -1
- package/src/prompts/system/system-prompt.md +2 -1
- package/src/registry/agent-lifecycle.ts +60 -8
- package/src/sdk.ts +20 -26
- package/src/session/agent-session.ts +246 -78
- package/src/session/artifacts.ts +19 -1
- package/src/session/session-dump-format.ts +167 -23
- package/src/session/session-entries.ts +4 -0
- package/src/session/session-history-format.ts +37 -3
- package/src/session/session-manager.ts +94 -4
- package/src/slash-commands/builtin-registry.ts +4 -7
- package/src/stt/asr-client.ts +6 -0
- package/src/stt/downloader.ts +13 -6
- package/src/stt/stt-controller.ts +52 -11
- package/src/task/executor.ts +18 -2
- package/src/task/index.ts +2 -2
- package/src/task/persisted-revive.ts +128 -0
- package/src/tiny/models.ts +10 -0
- package/src/tiny/worker.ts +4 -3
- package/src/tools/builtin-names.ts +0 -1
- package/src/tools/index.ts +0 -4
- package/src/tools/output-meta.ts +17 -3
- package/src/utils/title-generator.ts +4 -4
- package/dist/types/tools/render-mermaid.d.ts +0 -38
- package/src/prompts/tools/render-mermaid.md +0 -9
- package/src/tools/render-mermaid.ts +0 -69
package/src/modes/theme/theme.ts
CHANGED
|
@@ -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);
|
package/src/modes/types.ts
CHANGED
|
@@ -282,7 +282,7 @@ export interface InteractiveModeContext {
|
|
|
282
282
|
handleHotkeysCommand(): void;
|
|
283
283
|
handleToolsCommand(): void;
|
|
284
284
|
handleContextCommand(): void;
|
|
285
|
-
handleDumpCommand(
|
|
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
|
|
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
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
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
|
-
|
|
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.
|
|
2335
|
+
parentId: options.parentAgentId,
|
|
2342
2336
|
session: null,
|
|
2343
2337
|
sessionFile: sessionManager.getSessionFile() ?? null,
|
|
2344
2338
|
status: "running",
|