@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/task/executor.ts
CHANGED
|
@@ -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(
|
|
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
|
+
}
|
package/src/tiny/models.ts
CHANGED
|
@@ -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
|
|
package/src/tiny/worker.ts
CHANGED
|
@@ -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
|
|
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 ??
|
|
430
|
-
const maxNewTokens = Math.min(Math.max(1, requested),
|
|
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,
|
package/src/tools/index.ts
CHANGED
|
@@ -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.
|
package/src/tools/output-meta.ts
CHANGED
|
@@ -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
|
-
|
|
621
|
-
|
|
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
|
-
}
|