@oh-my-pi/pi-coding-agent 15.4.3 → 15.5.1
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 +81 -5
- package/dist/types/cli/args.d.ts +2 -0
- package/dist/types/cli/auth-broker-cli.d.ts +1 -1
- package/dist/types/commands/launch.d.ts +8 -0
- package/dist/types/config/settings-schema.d.ts +42 -1
- package/dist/types/edit/index.d.ts +2 -0
- package/dist/types/extensibility/custom-tools/types.d.ts +8 -2
- package/dist/types/extensibility/hooks/types.d.ts +4 -0
- package/dist/types/hashline/executor.d.ts +6 -3
- package/dist/types/lsp/index.d.ts +9 -1
- package/dist/types/mcp/client.d.ts +2 -1
- package/dist/types/mcp/oauth-discovery.d.ts +4 -3
- package/dist/types/mcp/timeout.d.ts +9 -0
- package/dist/types/mcp/types.d.ts +1 -1
- package/dist/types/sdk.d.ts +2 -0
- package/dist/types/session/streaming-output.d.ts +1 -1
- package/dist/types/task/index.d.ts +2 -0
- package/dist/types/task/types.d.ts +4 -0
- package/dist/types/tools/approval.d.ts +46 -0
- package/dist/types/tools/ask.d.ts +1 -0
- package/dist/types/tools/ast-edit.d.ts +2 -0
- package/dist/types/tools/ast-grep.d.ts +1 -0
- package/dist/types/tools/bash.d.ts +11 -1
- package/dist/types/tools/browser.d.ts +2 -0
- package/dist/types/tools/calculator.d.ts +1 -0
- package/dist/types/tools/checkpoint.d.ts +2 -0
- package/dist/types/tools/debug.d.ts +9 -1
- package/dist/types/tools/eval.d.ts +2 -0
- package/dist/types/tools/find.d.ts +10 -0
- package/dist/types/tools/gh.d.ts +2 -1
- package/dist/types/tools/hindsight-recall.d.ts +1 -0
- package/dist/types/tools/hindsight-reflect.d.ts +1 -0
- package/dist/types/tools/hindsight-retain.d.ts +1 -0
- package/dist/types/tools/inspect-image.d.ts +1 -0
- package/dist/types/tools/irc.d.ts +1 -0
- package/dist/types/tools/job.d.ts +1 -0
- package/dist/types/tools/read.d.ts +1 -0
- package/dist/types/tools/recipe/index.d.ts +1 -0
- package/dist/types/tools/render-mermaid.d.ts +1 -0
- package/dist/types/tools/resolve.d.ts +1 -0
- package/dist/types/tools/search-tool-bm25.d.ts +1 -0
- package/dist/types/tools/search.d.ts +1 -0
- package/dist/types/tools/ssh.d.ts +2 -0
- package/dist/types/tools/todo-write.d.ts +1 -0
- package/dist/types/tools/write.d.ts +2 -0
- package/dist/types/tools/yield.d.ts +1 -0
- package/dist/types/web/search/index.d.ts +1 -0
- package/package.json +7 -7
- package/src/cli/args.ts +14 -0
- package/src/cli/auth-broker-cli.ts +171 -22
- package/src/commands/auth-broker.ts +3 -0
- package/src/commands/launch.ts +16 -0
- package/src/config/mcp-schema.json +2 -2
- package/src/config/model-registry.ts +19 -4
- package/src/config/prompt-templates.ts +0 -125
- package/src/config/settings-schema.ts +59 -1
- package/src/config/settings.ts +2 -1
- package/src/dap/session.ts +35 -2
- package/src/discovery/builtin.ts +2 -2
- package/src/discovery/mcp-json.ts +1 -1
- package/src/edit/index.ts +26 -0
- package/src/edit/modes/patch.ts +1 -1
- package/src/edit/streaming.ts +12 -2
- package/src/exec/bash-executor.ts +6 -2
- package/src/extensibility/custom-commands/bundled/review/index.ts +18 -14
- package/src/extensibility/custom-tools/types.ts +16 -2
- package/src/extensibility/extensions/wrapper.ts +36 -1
- package/src/extensibility/hooks/types.ts +8 -1
- package/src/hashline/apply.ts +47 -2
- package/src/hashline/executor.ts +46 -24
- package/src/internal-urls/docs-index.generated.ts +8 -7
- package/src/lsp/edits.ts +82 -29
- package/src/lsp/index.ts +38 -1
- package/src/lsp/utils.ts +1 -1
- package/src/main.ts +6 -0
- package/src/mcp/client.ts +8 -6
- package/src/mcp/oauth-discovery.ts +120 -32
- package/src/mcp/oauth-flow.ts +34 -6
- package/src/mcp/timeout.ts +59 -0
- package/src/mcp/transports/http.ts +42 -44
- package/src/mcp/transports/stdio.ts +8 -5
- package/src/mcp/types.ts +1 -1
- package/src/modes/components/hook-editor.ts +11 -3
- package/src/modes/components/mcp-add-wizard.ts +6 -2
- package/src/modes/components/model-selector.ts +33 -11
- package/src/modes/controllers/command-controller.ts +6 -4
- package/src/modes/controllers/mcp-command-controller.ts +8 -4
- package/src/prompts/review-custom-request.md +22 -0
- package/src/prompts/review-headless-request.md +16 -0
- package/src/prompts/review-request.md +2 -3
- package/src/prompts/system/project-prompt.md +4 -0
- package/src/prompts/tools/debug.md +1 -0
- package/src/prompts/tools/find.md +4 -2
- package/src/prompts/tools/hashline.md +43 -93
- package/src/sdk.ts +47 -73
- package/src/session/agent-session.ts +93 -27
- package/src/session/streaming-output.ts +1 -1
- package/src/slash-commands/helpers/usage-report.ts +3 -1
- package/src/task/executor.ts +11 -0
- package/src/task/index.ts +19 -0
- package/src/task/render.ts +12 -2
- package/src/task/types.ts +4 -0
- package/src/tools/approval.ts +185 -0
- package/src/tools/ask.ts +1 -0
- package/src/tools/ast-edit.ts +25 -1
- package/src/tools/ast-grep.ts +1 -0
- package/src/tools/bash.ts +69 -1
- package/src/tools/browser/tab-supervisor.ts +1 -1
- package/src/tools/browser.ts +15 -0
- package/src/tools/calculator.ts +1 -0
- package/src/tools/checkpoint.ts +2 -0
- package/src/tools/debug.ts +38 -0
- package/src/tools/eval.ts +15 -0
- package/src/tools/find.ts +17 -8
- package/src/tools/gh.ts +21 -1
- package/src/tools/hindsight-recall.ts +1 -0
- package/src/tools/hindsight-reflect.ts +1 -0
- package/src/tools/hindsight-retain.ts +1 -0
- package/src/tools/image-gen.ts +1 -0
- package/src/tools/inspect-image.ts +1 -0
- package/src/tools/irc.ts +1 -0
- package/src/tools/job.ts +1 -0
- package/src/tools/path-utils.ts +14 -1
- package/src/tools/read.ts +1 -0
- package/src/tools/recipe/index.ts +1 -0
- package/src/tools/render-mermaid.ts +1 -0
- package/src/tools/report-tool-issue.ts +1 -0
- package/src/tools/resolve.ts +1 -0
- package/src/tools/review.ts +1 -0
- package/src/tools/search-tool-bm25.ts +1 -0
- package/src/tools/search.ts +1 -0
- package/src/tools/ssh.ts +8 -0
- package/src/tools/todo-write.ts +1 -0
- package/src/tools/write.ts +12 -1
- package/src/tools/yield.ts +1 -0
- package/src/web/search/index.ts +2 -0
|
@@ -900,9 +900,20 @@ export class ModelRegistry {
|
|
|
900
900
|
|
|
901
901
|
this.#addImplicitDiscoverableProviders(configuredProviders);
|
|
902
902
|
let builtInModels = this.#applyHardcodedModelPolicies(this.#loadBuiltInModels(overrides));
|
|
903
|
-
const
|
|
903
|
+
const cachedStandardResult = this.#loadCachedStandardProviderModels();
|
|
904
|
+
const cachedStandardModels = this.#applyHardcodedModelPolicies(cachedStandardResult.models);
|
|
904
905
|
const cachedDiscoveries = this.#applyHardcodedModelPolicies(this.#loadCachedDiscoverableModels());
|
|
905
|
-
|
|
906
|
+
// Only drop bundled fallback models when the cached project-catalog row is
|
|
907
|
+
// itself fresh AND authoritative. A stale or non-authoritative snapshot
|
|
908
|
+
// (e.g. after ADC discovery failure rewrote the row with authoritative=0)
|
|
909
|
+
// must not strip bundled Vertex Gemini entries — that would leave only the
|
|
910
|
+
// stale project-scoped rows in API-key-only environments.
|
|
911
|
+
const cachedAuthoritativeProviders = new Set<string>();
|
|
912
|
+
for (const provider of providersWithAuthoritativeProjectCatalog(cachedStandardModels)) {
|
|
913
|
+
if (cachedStandardResult.authoritativeFreshProviders.has(provider)) {
|
|
914
|
+
cachedAuthoritativeProviders.add(provider);
|
|
915
|
+
}
|
|
916
|
+
}
|
|
906
917
|
if (cachedAuthoritativeProviders.size > 0) {
|
|
907
918
|
builtInModels = dropProviderModels(builtInModels, cachedAuthoritativeProviders);
|
|
908
919
|
}
|
|
@@ -1008,9 +1019,10 @@ export class ModelRegistry {
|
|
|
1008
1019
|
return merged;
|
|
1009
1020
|
}
|
|
1010
1021
|
|
|
1011
|
-
#loadCachedStandardProviderModels(): Model<Api>[] {
|
|
1022
|
+
#loadCachedStandardProviderModels(): { models: Model<Api>[]; authoritativeFreshProviders: Set<string> } {
|
|
1012
1023
|
const configuredDiscoveryProviders = new Set(this.#discoverableProviders.map(provider => provider.provider));
|
|
1013
1024
|
const cachedModels: Model<Api>[] = [];
|
|
1025
|
+
const authoritativeFreshProviders = new Set<string>();
|
|
1014
1026
|
for (const descriptor of PROVIDER_DESCRIPTORS) {
|
|
1015
1027
|
if (configuredDiscoveryProviders.has(descriptor.providerId)) {
|
|
1016
1028
|
continue;
|
|
@@ -1019,6 +1031,9 @@ export class ModelRegistry {
|
|
|
1019
1031
|
if (!cache) {
|
|
1020
1032
|
continue;
|
|
1021
1033
|
}
|
|
1034
|
+
if (cache.fresh && cache.authoritative) {
|
|
1035
|
+
authoritativeFreshProviders.add(descriptor.providerId);
|
|
1036
|
+
}
|
|
1022
1037
|
const models = cache.models.map(model =>
|
|
1023
1038
|
model.provider === descriptor.providerId ? model : { ...model, provider: descriptor.providerId },
|
|
1024
1039
|
);
|
|
@@ -1031,7 +1046,7 @@ export class ModelRegistry {
|
|
|
1031
1046
|
: withTransport;
|
|
1032
1047
|
cachedModels.push(...this.#applyProviderModelOverrides(descriptor.providerId, withCompat));
|
|
1033
1048
|
}
|
|
1034
|
-
return cachedModels;
|
|
1049
|
+
return { models: cachedModels, authoritativeFreshProviders };
|
|
1035
1050
|
}
|
|
1036
1051
|
|
|
1037
1052
|
#loadCachedDiscoverableModels(): Model<Api>[] {
|
|
@@ -8,7 +8,6 @@ import {
|
|
|
8
8
|
parseFrontmatter,
|
|
9
9
|
prompt,
|
|
10
10
|
} from "@oh-my-pi/pi-utils";
|
|
11
|
-
import { HL_LINE_BODY_SEP } from "../hashline/hash";
|
|
12
11
|
import { jtdToTypeScript } from "../tools/jtd-to-typescript";
|
|
13
12
|
import { parseCommandArgs, substituteArgs } from "../utils/command-args";
|
|
14
13
|
|
|
@@ -30,130 +29,6 @@ prompt.registerHelper("jtdToTypeScript", (schema: unknown): string => {
|
|
|
30
29
|
}
|
|
31
30
|
});
|
|
32
31
|
|
|
33
|
-
function formatHashlineRef(lineNum: unknown, content: unknown): { num: number; text: string; ref: string } {
|
|
34
|
-
const num = typeof lineNum === "number" ? lineNum : Number.parseInt(String(lineNum), 10);
|
|
35
|
-
const raw = typeof content === "string" ? content : String(content ?? "");
|
|
36
|
-
const text = raw.replace(/\\t/g, "\t").replace(/\\n/g, "\n").replace(/\\r/g, "\r");
|
|
37
|
-
const ref = `${num}`;
|
|
38
|
-
return { num, text, ref };
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
interface HashlineHelperRef {
|
|
42
|
-
line: number;
|
|
43
|
-
ref: string;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
interface HashlineHelperState {
|
|
47
|
-
last?: HashlineHelperRef;
|
|
48
|
-
byLine: Map<number, HashlineHelperRef>;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
const HL_HELPER_STATE = Symbol("hashlineHelperState");
|
|
52
|
-
|
|
53
|
-
interface HashlineHelperStateHolder {
|
|
54
|
-
[HL_HELPER_STATE]?: HashlineHelperState;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function isHelperOptions(value: unknown): value is prompt.HelperOptions {
|
|
58
|
-
return typeof value === "object" && value !== null && "hash" in value;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function splitHelperArgs(args: unknown[]): { positional: unknown[]; options?: prompt.HelperOptions } {
|
|
62
|
-
const maybeOptions = args.at(-1);
|
|
63
|
-
if (!isHelperOptions(maybeOptions)) return { positional: args };
|
|
64
|
-
return { positional: args.slice(0, -1), options: maybeOptions };
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function getHashlineHelperState(context: unknown, options: prompt.HelperOptions | undefined): HashlineHelperState {
|
|
68
|
-
const data = options?.data;
|
|
69
|
-
const root = data?.root;
|
|
70
|
-
const holderTarget = data && typeof data === "object" ? data : root && typeof root === "object" ? root : context;
|
|
71
|
-
if (!holderTarget || typeof holderTarget !== "object") {
|
|
72
|
-
throw new Error("hashline prompt helpers require an object render context");
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
const holder = holderTarget as HashlineHelperStateHolder;
|
|
76
|
-
if (!holder[HL_HELPER_STATE]) {
|
|
77
|
-
holder[HL_HELPER_STATE] = { byLine: new Map() };
|
|
78
|
-
}
|
|
79
|
-
return holder[HL_HELPER_STATE];
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
function isLineNumberArg(value: unknown): boolean {
|
|
83
|
-
const num = typeof value === "number" ? value : Number.parseInt(String(value), 10);
|
|
84
|
-
return Number.isFinite(num);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
function rememberHashlineRef(state: HashlineHelperState, line: number, ref: string): void {
|
|
88
|
-
const entry = { line, ref };
|
|
89
|
-
state.last = entry;
|
|
90
|
-
state.byLine.set(line, entry);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
function requireStoredHashlineRef(state: HashlineHelperState, lineArg?: unknown): string {
|
|
94
|
-
if (lineArg === undefined) {
|
|
95
|
-
if (!state.last) {
|
|
96
|
-
throw new Error("{{href}} requires a previous {{hline}} call in the same prompt render");
|
|
97
|
-
}
|
|
98
|
-
return state.last.ref;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
const line = typeof lineArg === "number" ? lineArg : Number.parseInt(String(lineArg), 10);
|
|
102
|
-
const entry = state.byLine.get(line);
|
|
103
|
-
if (!entry) {
|
|
104
|
-
throw new Error(`{{href ${line}}} requires a previous {{hline ${line} ...}} call in the same prompt render`);
|
|
105
|
-
}
|
|
106
|
-
return entry.ref;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
function wrapHashlineRef(ref: string, args: unknown[]): string {
|
|
110
|
-
const preStr = typeof args[0] === "string" ? args[0] : "";
|
|
111
|
-
const postStr = typeof args[1] === "string" ? args[1] : "";
|
|
112
|
-
return `${preStr}${ref}${postStr}`;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
function resolveHashlineRef(state: HashlineHelperState, args: unknown[]): string {
|
|
116
|
-
if (args.length === 0) return requireStoredHashlineRef(state);
|
|
117
|
-
const [first, second, ...rest] = args;
|
|
118
|
-
if (isLineNumberArg(first)) {
|
|
119
|
-
if (second === undefined) return requireStoredHashlineRef(state, first);
|
|
120
|
-
const { ref } = formatHashlineRef(first, second);
|
|
121
|
-
return wrapHashlineRef(ref, rest);
|
|
122
|
-
}
|
|
123
|
-
return wrapHashlineRef(requireStoredHashlineRef(state), args);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
/**
|
|
127
|
-
* {{href lineNum "content"}} — compute a hashline line ref for prompt examples.
|
|
128
|
-
* {{href lineNum}} — quote the ref remembered by the earlier {{hline lineNum "..."}}
|
|
129
|
-
* {{href}} — quote the ref from the previous {{hline}} call.
|
|
130
|
-
* {{href "[" "]"}} — wrap the previous {{hline}} ref with pre/post chars.
|
|
131
|
-
* Returns `"lineNum"` (e.g., `"42"`), or `"[42]"` when pre/post are supplied.
|
|
132
|
-
*/
|
|
133
|
-
prompt.registerHelper("href", function (this: unknown, ...args: unknown[]): string {
|
|
134
|
-
const { positional, options } = splitHelperArgs(args);
|
|
135
|
-
const state = getHashlineHelperState(this, options);
|
|
136
|
-
return JSON.stringify(resolveHashlineRef(state, positional));
|
|
137
|
-
});
|
|
138
|
-
prompt.registerHelper("hrefr", function (this: unknown, ...args: unknown[]): string {
|
|
139
|
-
const { positional, options } = splitHelperArgs(args);
|
|
140
|
-
const state = getHashlineHelperState(this, options);
|
|
141
|
-
return resolveHashlineRef(state, positional);
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
/**
|
|
145
|
-
* {{hline lineNum "content"}} — format a full read-style line with prefix.
|
|
146
|
-
* Returns `"lineNum:content"` (colon between line number and content).
|
|
147
|
-
*/
|
|
148
|
-
prompt.registerHelper("hline", function (this: unknown, ...args: unknown[]): string {
|
|
149
|
-
const { positional, options } = splitHelperArgs(args);
|
|
150
|
-
const [lineNum, content] = positional;
|
|
151
|
-
const { num, ref, text } = formatHashlineRef(lineNum, content);
|
|
152
|
-
const state = getHashlineHelperState(this, options);
|
|
153
|
-
rememberHashlineRef(state, num, ref);
|
|
154
|
-
return `${ref}${HL_LINE_BODY_SEP}${text}`;
|
|
155
|
-
});
|
|
156
|
-
|
|
157
32
|
const INLINE_ARG_SHELL_PATTERN = /\$(?:ARGUMENTS|@(?:\[\d+(?::\d*)?\])?|\d+)/;
|
|
158
33
|
const INLINE_ARG_TEMPLATE_PATTERN = /\{\{[\s\S]*?(?:\b(?:arguments|ARGUMENTS|args)\b|\barg\s+[^}]+)[\s\S]*?\}\}/;
|
|
159
34
|
|
|
@@ -1574,7 +1574,8 @@ export const SETTINGS_SCHEMA = {
|
|
|
1574
1574
|
ui: {
|
|
1575
1575
|
tab: "editing",
|
|
1576
1576
|
label: "Hashline Duplicate Insert Drop",
|
|
1577
|
-
description:
|
|
1577
|
+
description:
|
|
1578
|
+
"Drop payload lines that duplicate adjacent file context — 2+-line context echoes on `↑`/`↓` inserts, and a single boundary line at either edge of an `A-B:` replacement",
|
|
1578
1579
|
},
|
|
1579
1580
|
},
|
|
1580
1581
|
"edit.blockAutoGenerated": {
|
|
@@ -1789,6 +1790,53 @@ export const SETTINGS_SCHEMA = {
|
|
|
1789
1790
|
// Tools
|
|
1790
1791
|
// ────────────────────────────────────────────────────────────────────────
|
|
1791
1792
|
|
|
1793
|
+
// Tool approval policies
|
|
1794
|
+
"tools.approval": {
|
|
1795
|
+
type: "record",
|
|
1796
|
+
default: {},
|
|
1797
|
+
ui: {
|
|
1798
|
+
tab: "tools",
|
|
1799
|
+
label: "Tool Approval Policies",
|
|
1800
|
+
description:
|
|
1801
|
+
"Per-tool approval policies. Set to 'allow' to auto-approve, 'prompt' to require confirmation, or 'deny' to block. Overrides are honored in every approval mode.",
|
|
1802
|
+
},
|
|
1803
|
+
},
|
|
1804
|
+
|
|
1805
|
+
// Default tool approval mode (interaction tab, but governs the tool wrapper).
|
|
1806
|
+
// "always-ask" — auto-approves read-tier tools only; prompts for write/exec.
|
|
1807
|
+
// "write" — auto-approves read and write-tier tools; prompts for exec.
|
|
1808
|
+
// "yolo" — auto-approves every tier unless a tool declares `override: true`.
|
|
1809
|
+
"tools.approvalMode": {
|
|
1810
|
+
type: "enum",
|
|
1811
|
+
values: ["always-ask", "write", "yolo"] as const,
|
|
1812
|
+
default: "yolo",
|
|
1813
|
+
ui: {
|
|
1814
|
+
tab: "interaction",
|
|
1815
|
+
label: "Tool Approval",
|
|
1816
|
+
description:
|
|
1817
|
+
"Default approval behaviour for tool calls. 'Always ask' auto-approves read-only tools only. 'Write' auto-approves read and workspace-write tools. 'Yolo' auto-approves every tier unless a tool declares a safety override. `tools.approval.<tool>` overrides are honored in every mode.",
|
|
1818
|
+
options: [
|
|
1819
|
+
{
|
|
1820
|
+
value: "always-ask",
|
|
1821
|
+
label: "Always ask",
|
|
1822
|
+
description: "Auto-approve read-only tools; require confirmation for write and exec tools.",
|
|
1823
|
+
},
|
|
1824
|
+
{
|
|
1825
|
+
value: "write",
|
|
1826
|
+
label: "Write",
|
|
1827
|
+
description:
|
|
1828
|
+
"Auto-approve read-only and write tools; require confirmation for exec tools such as bash, eval, browser, task, recipe, and ssh.",
|
|
1829
|
+
},
|
|
1830
|
+
{
|
|
1831
|
+
value: "yolo",
|
|
1832
|
+
label: "Yolo",
|
|
1833
|
+
description:
|
|
1834
|
+
"Auto-approve read, write, and exec tools. Safety overrides declared by tools (for example critical bash patterns) still require confirmation.",
|
|
1835
|
+
},
|
|
1836
|
+
],
|
|
1837
|
+
},
|
|
1838
|
+
},
|
|
1839
|
+
|
|
1792
1840
|
// Todo tool
|
|
1793
1841
|
"todo.enabled": {
|
|
1794
1842
|
type: "boolean",
|
|
@@ -2477,6 +2525,16 @@ export const SETTINGS_SCHEMA = {
|
|
|
2477
2525
|
},
|
|
2478
2526
|
},
|
|
2479
2527
|
|
|
2528
|
+
"task.showResolvedModelBadge": {
|
|
2529
|
+
type: "boolean",
|
|
2530
|
+
default: false,
|
|
2531
|
+
ui: {
|
|
2532
|
+
tab: "appearance",
|
|
2533
|
+
label: "Show Resolved Model Badge",
|
|
2534
|
+
description: "Display the actual model ID used by each subagent in the task widget status line",
|
|
2535
|
+
},
|
|
2536
|
+
},
|
|
2537
|
+
|
|
2480
2538
|
// Skills
|
|
2481
2539
|
"skills.enabled": { type: "boolean", default: true },
|
|
2482
2540
|
|
package/src/config/settings.ts
CHANGED
|
@@ -100,7 +100,6 @@ function setByPath(obj: RawSettings, segments: string[], value: unknown): void {
|
|
|
100
100
|
}
|
|
101
101
|
|
|
102
102
|
const PATH_SCOPED_ARRAY_SETTINGS = new Set<SettingPath>(["enabledModels", "disabledProviders"]);
|
|
103
|
-
|
|
104
103
|
type PathScopedStringArrayEntry = {
|
|
105
104
|
path?: unknown;
|
|
106
105
|
paths?: unknown;
|
|
@@ -218,6 +217,8 @@ export class Settings {
|
|
|
218
217
|
for (const [key, value] of Object.entries(options.overrides)) {
|
|
219
218
|
setByPath(this.#overrides, key.split("."), value);
|
|
220
219
|
}
|
|
220
|
+
|
|
221
|
+
this.#overrides = this.#migrateRawSettings(this.#overrides);
|
|
221
222
|
}
|
|
222
223
|
}
|
|
223
224
|
|
package/src/dap/session.ts
CHANGED
|
@@ -108,14 +108,28 @@ function toErrorMessage(value: unknown): string {
|
|
|
108
108
|
interface DapStartRequestFailure {
|
|
109
109
|
rejected: boolean;
|
|
110
110
|
error?: unknown;
|
|
111
|
+
/**
|
|
112
|
+
* Resolves (never rejects) when the underlying launch/attach request
|
|
113
|
+
* settles either way. Set by {@link trackDapStartRequest} on each call,
|
|
114
|
+
* so a single failure object must not be reused across launch attempts.
|
|
115
|
+
* Consumed by {@link throwPreferredDapStartError} to bound how long to
|
|
116
|
+
* wait for a delayed adapter-side rejection before falling back to the
|
|
117
|
+
* cascade error from configurationDone.
|
|
118
|
+
*/
|
|
119
|
+
settled?: Promise<void>;
|
|
111
120
|
}
|
|
112
121
|
|
|
113
122
|
function trackDapStartRequest<T>(promise: Promise<T>, failure: DapStartRequestFailure): Promise<T> {
|
|
114
|
-
|
|
123
|
+
const tracked = promise.catch(error => {
|
|
115
124
|
failure.rejected = true;
|
|
116
125
|
failure.error = error;
|
|
117
126
|
throw error;
|
|
118
127
|
});
|
|
128
|
+
failure.settled = tracked.then(
|
|
129
|
+
() => {},
|
|
130
|
+
() => {},
|
|
131
|
+
);
|
|
132
|
+
return tracked;
|
|
119
133
|
}
|
|
120
134
|
|
|
121
135
|
function combineDapStartErrors(command: "launch" | "attach", startError: unknown, configurationError: unknown): Error {
|
|
@@ -134,12 +148,27 @@ async function throwPreferredDapStartError(
|
|
|
134
148
|
startFailure: DapStartRequestFailure,
|
|
135
149
|
configurationError: unknown,
|
|
136
150
|
): Promise<never> {
|
|
137
|
-
await Promise.resolve();
|
|
151
|
+
await Promise.race([startFailure.settled ?? Promise.resolve(), timers.setTimeout(50)]);
|
|
138
152
|
if (startFailure.rejected) {
|
|
139
153
|
throw combineDapStartErrors(command, startFailure.error, configurationError);
|
|
140
154
|
}
|
|
141
155
|
throw configurationError;
|
|
142
156
|
}
|
|
157
|
+
|
|
158
|
+
const DEBUGPY_MISSING_MODULE_RE = /No module named ['"]?debugpy['"]?/;
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Map a generic adapter-side failure into the targeted `pip install debugpy`
|
|
162
|
+
* hint when the adapter is debugpy and stderr/the wrapping error mentions
|
|
163
|
+
* the missing module. Returns null when the heuristic does not apply, so the
|
|
164
|
+
* caller can rethrow the original error untouched.
|
|
165
|
+
*/
|
|
166
|
+
function mapDebugpyMissingModule(adapterName: string, error: unknown): Error | null {
|
|
167
|
+
if (adapterName !== "debugpy") return null;
|
|
168
|
+
if (!DEBUGPY_MISSING_MODULE_RE.test(toErrorMessage(error))) return null;
|
|
169
|
+
return new Error("adapter 'debugpy' is not available: install with 'pip install debugpy'");
|
|
170
|
+
}
|
|
171
|
+
|
|
143
172
|
function normalizePath(filePath: string): string {
|
|
144
173
|
return path.resolve(filePath);
|
|
145
174
|
}
|
|
@@ -274,6 +303,8 @@ export class DapSessionManager {
|
|
|
274
303
|
return buildSummary(session);
|
|
275
304
|
} catch (error) {
|
|
276
305
|
await this.#disposeSession(session);
|
|
306
|
+
const mapped = mapDebugpyMissingModule(options.adapter.name, error);
|
|
307
|
+
if (mapped) throw mapped;
|
|
277
308
|
throw error;
|
|
278
309
|
}
|
|
279
310
|
}
|
|
@@ -330,6 +361,8 @@ export class DapSessionManager {
|
|
|
330
361
|
return buildSummary(session);
|
|
331
362
|
} catch (error) {
|
|
332
363
|
await this.#disposeSession(session);
|
|
364
|
+
const mapped = mapDebugpyMissingModule(options.adapter.name, error);
|
|
365
|
+
if (mapped) throw mapped;
|
|
333
366
|
throw error;
|
|
334
367
|
}
|
|
335
368
|
}
|
package/src/discovery/builtin.ts
CHANGED
|
@@ -132,7 +132,7 @@ async function loadMCPServers(ctx: LoadContext): Promise<LoadResult<MCPServer>>
|
|
|
132
132
|
if (serverConfig.timeout === undefined || serverConfig.timeout === null) {
|
|
133
133
|
timeout = undefined;
|
|
134
134
|
} else if (typeof serverConfig.timeout === "number") {
|
|
135
|
-
if (Number.isFinite(serverConfig.timeout) && serverConfig.timeout
|
|
135
|
+
if (Number.isFinite(serverConfig.timeout) && serverConfig.timeout >= 0) {
|
|
136
136
|
timeout = serverConfig.timeout;
|
|
137
137
|
} else {
|
|
138
138
|
logger.warn(`MCP server "${serverName}": invalid timeout ${serverConfig.timeout}, ignoring`);
|
|
@@ -140,7 +140,7 @@ async function loadMCPServers(ctx: LoadContext): Promise<LoadResult<MCPServer>>
|
|
|
140
140
|
}
|
|
141
141
|
} else if (typeof serverConfig.timeout === "string") {
|
|
142
142
|
const parsed = Number(serverConfig.timeout);
|
|
143
|
-
if (Number.isFinite(parsed) && parsed
|
|
143
|
+
if (Number.isFinite(parsed) && parsed >= 0) {
|
|
144
144
|
timeout = parsed;
|
|
145
145
|
} else {
|
|
146
146
|
logger.warn(`MCP server "${serverName}": invalid timeout "${serverConfig.timeout}", ignoring`);
|
|
@@ -74,7 +74,7 @@ function transformMCPConfig(config: MCPConfigFile, source: SourceMeta): MCPServe
|
|
|
74
74
|
if (
|
|
75
75
|
typeof serverConfig.timeout === "number" &&
|
|
76
76
|
Number.isFinite(serverConfig.timeout) &&
|
|
77
|
-
serverConfig.timeout
|
|
77
|
+
serverConfig.timeout >= 0
|
|
78
78
|
) {
|
|
79
79
|
timeout = serverConfig.timeout;
|
|
80
80
|
} else {
|
package/src/edit/index.ts
CHANGED
|
@@ -20,6 +20,8 @@ import hashlineDescription from "../prompts/tools/hashline.md" with { type: "tex
|
|
|
20
20
|
import patchDescription from "../prompts/tools/patch.md" with { type: "text" };
|
|
21
21
|
import replaceDescription from "../prompts/tools/replace.md" with { type: "text" };
|
|
22
22
|
import type { ToolSession } from "../tools";
|
|
23
|
+
import { truncateForPrompt } from "../tools/approval";
|
|
24
|
+
import { isInternalUrlPath } from "../tools/path-utils";
|
|
23
25
|
import { type EditMode, normalizeEditMode, resolveEditMode } from "../utils/edit-mode";
|
|
24
26
|
import { type ApplyPatchParams, applyPatchSchema, expandApplyPatchToEntries } from "./modes/apply-patch";
|
|
25
27
|
import applyPatchGrammar from "./modes/apply-patch.lark" with { type: "text" };
|
|
@@ -266,7 +268,31 @@ async function executeSinglePathEntries(
|
|
|
266
268
|
};
|
|
267
269
|
}
|
|
268
270
|
|
|
271
|
+
function extractApprovalPath(args: unknown): string {
|
|
272
|
+
const record = args && typeof args === "object" ? (args as Record<string, unknown>) : {};
|
|
273
|
+
const targetPath = record.path;
|
|
274
|
+
if (typeof targetPath === "string" && targetPath.length > 0) {
|
|
275
|
+
return targetPath;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const input = typeof record.input === "string" ? record.input : undefined;
|
|
279
|
+
if (!input) return "(unknown)";
|
|
280
|
+
|
|
281
|
+
const hashlineMatch = /^(?:¶|§|@)([^\s#]+)/m.exec(input);
|
|
282
|
+
if (hashlineMatch?.[1]) return hashlineMatch[1];
|
|
283
|
+
|
|
284
|
+
const applyPatchMatch = /^\*\*\* (?:Add|Update|Delete) File:\s*(.+)$/m.exec(input);
|
|
285
|
+
return applyPatchMatch?.[1]?.trim() || "(unknown)";
|
|
286
|
+
}
|
|
287
|
+
|
|
269
288
|
export class EditTool implements AgentTool<TInput> {
|
|
289
|
+
readonly approval = (args: unknown) => {
|
|
290
|
+
const targetPath = extractApprovalPath(args);
|
|
291
|
+
return targetPath !== "(unknown)" && isInternalUrlPath(targetPath) ? "read" : "write";
|
|
292
|
+
};
|
|
293
|
+
readonly formatApprovalDetails = (args: unknown): string[] => [
|
|
294
|
+
`File: ${truncateForPrompt(extractApprovalPath(args))}`,
|
|
295
|
+
];
|
|
270
296
|
readonly name = "edit";
|
|
271
297
|
readonly label = "Edit";
|
|
272
298
|
readonly loadMode = "essential";
|
package/src/edit/modes/patch.ts
CHANGED
|
@@ -1763,7 +1763,7 @@ export async function executePatchSingle(
|
|
|
1763
1763
|
postEditContent.length === preEditContent.length &&
|
|
1764
1764
|
postEditContent.every((b, i) => b === preEditContent[i]);
|
|
1765
1765
|
if (unchanged) {
|
|
1766
|
-
throw new ToolError(`edit appeared successful but file content did not change on disk: ${
|
|
1766
|
+
throw new ToolError(`edit appeared successful but file content did not change on disk: ${path}`, {
|
|
1767
1767
|
path: resolvedPath,
|
|
1768
1768
|
});
|
|
1769
1769
|
}
|
package/src/edit/streaming.ts
CHANGED
|
@@ -386,14 +386,24 @@ function buildHashlineNaturalOrderPreviews(
|
|
|
386
386
|
case "envelope-begin":
|
|
387
387
|
case "envelope-end":
|
|
388
388
|
case "abort":
|
|
389
|
-
case "op-insert":
|
|
390
|
-
case "op-replace":
|
|
391
389
|
case "op-delete":
|
|
392
390
|
continue;
|
|
393
391
|
case "header":
|
|
394
392
|
currentPath = token.path;
|
|
395
393
|
if (currentPath) ensure(currentPath);
|
|
396
394
|
continue;
|
|
395
|
+
case "op-insert":
|
|
396
|
+
case "op-replace":
|
|
397
|
+
// Inline body on the op line itself (`N↓payload`, `A-B:payload`) is
|
|
398
|
+
// payload content that just happens to share a line with the op
|
|
399
|
+
// header — render it the same as a standalone payload token so
|
|
400
|
+
// the very first character the model types after the sigil shows
|
|
401
|
+
// up in the streaming preview. Without this, the preview is
|
|
402
|
+
// empty until a newline arrives, and the renderer falls back to
|
|
403
|
+
// raw input ("A-B: bla bla bla") instead of "+ bla bla bla".
|
|
404
|
+
if (!currentPath || token.inlineBody === undefined) continue;
|
|
405
|
+
ensure(currentPath).push(`+${token.inlineBody}`);
|
|
406
|
+
continue;
|
|
397
407
|
case "blank":
|
|
398
408
|
if (!currentPath) continue;
|
|
399
409
|
ensure(currentPath).push("+");
|
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
* Uses brush-core via native bindings for shell execution.
|
|
5
5
|
*/
|
|
6
6
|
import * as fs from "node:fs/promises";
|
|
7
|
-
import {
|
|
7
|
+
import { ExponentialYield } from "@oh-my-pi/pi-agent-core/utils/yield";
|
|
8
|
+
import { executeShell, type MinimizerOptions, Shell, type ShellRunResult } from "@oh-my-pi/pi-natives";
|
|
8
9
|
import { Settings, type ShellMinimizerSettings } from "../config/settings";
|
|
9
10
|
import { OutputSink } from "../session/streaming-output";
|
|
10
11
|
import { resolveOutputMaxColumns, resolveOutputSinkHeadBytes } from "../tools/output-meta";
|
|
@@ -196,7 +197,10 @@ export async function executeBash(command: string, options?: BashExecutorOptions
|
|
|
196
197
|
},
|
|
197
198
|
);
|
|
198
199
|
|
|
199
|
-
const
|
|
200
|
+
const ey = new ExponentialYield();
|
|
201
|
+
const winner = await ey.race<
|
|
202
|
+
{ kind: "result"; result: ShellRunResult } | { kind: "timeout" } | { kind: "abort" }
|
|
203
|
+
>([
|
|
200
204
|
runPromise.then(result => ({ kind: "result" as const, result })),
|
|
201
205
|
timeoutDeferred.promise.then(kind => ({ kind })),
|
|
202
206
|
abortDeferred.promise.then(kind => ({ kind })),
|
|
@@ -14,6 +14,8 @@
|
|
|
14
14
|
import { prompt } from "@oh-my-pi/pi-utils";
|
|
15
15
|
import type { CustomCommand, CustomCommandAPI } from "../../../../extensibility/custom-commands/types";
|
|
16
16
|
import type { HookCommandContext } from "../../../../extensibility/hooks/types";
|
|
17
|
+
import reviewCustomRequestTemplate from "../../../../prompts/review-custom-request.md" with { type: "text" };
|
|
18
|
+
import reviewHeadlessRequestTemplate from "../../../../prompts/review-headless-request.md" with { type: "text" };
|
|
17
19
|
import reviewRequestTemplate from "../../../../prompts/review-request.md" with { type: "text" };
|
|
18
20
|
import * as git from "../../../../utils/git";
|
|
19
21
|
|
|
@@ -225,6 +227,14 @@ function buildReviewPrompt(mode: string, stats: DiffStats, rawDiff: string, addi
|
|
|
225
227
|
});
|
|
226
228
|
}
|
|
227
229
|
|
|
230
|
+
function buildCustomReviewPrompt(instructions: string): string {
|
|
231
|
+
return prompt.render(reviewCustomRequestTemplate, { instructions });
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function buildHeadlessReviewPrompt(focus?: string): string {
|
|
235
|
+
return prompt.render(reviewHeadlessRequestTemplate, { focus });
|
|
236
|
+
}
|
|
237
|
+
|
|
228
238
|
export class ReviewCommand implements CustomCommand {
|
|
229
239
|
name = "review";
|
|
230
240
|
description = "Launch interactive code review";
|
|
@@ -233,8 +243,7 @@ export class ReviewCommand implements CustomCommand {
|
|
|
233
243
|
|
|
234
244
|
async execute(args: string[], ctx: HookCommandContext): Promise<string | undefined> {
|
|
235
245
|
if (!ctx.hasUI) {
|
|
236
|
-
|
|
237
|
-
return args.length > 0 ? `${base} Focus: ${args.join(" ")}` : base;
|
|
246
|
+
return buildHeadlessReviewPrompt(args.length > 0 ? args.join(" ") : undefined);
|
|
238
247
|
}
|
|
239
248
|
|
|
240
249
|
// Inline args act as additional instructions appended to the generated prompt.
|
|
@@ -379,7 +388,12 @@ export class ReviewCommand implements CustomCommand {
|
|
|
379
388
|
|
|
380
389
|
case 4: {
|
|
381
390
|
// Custom instructions - still uses the old approach since user provides context
|
|
382
|
-
const instructions = await ctx.ui.editor(
|
|
391
|
+
const instructions = await ctx.ui.editor(
|
|
392
|
+
"Enter custom review instructions",
|
|
393
|
+
"Review the following:\n\n",
|
|
394
|
+
undefined,
|
|
395
|
+
{ promptStyle: true },
|
|
396
|
+
);
|
|
383
397
|
if (!instructions?.trim()) return undefined;
|
|
384
398
|
|
|
385
399
|
// For custom, we still try to get current diff for context
|
|
@@ -402,17 +416,7 @@ export class ReviewCommand implements CustomCommand {
|
|
|
402
416
|
);
|
|
403
417
|
}
|
|
404
418
|
|
|
405
|
-
|
|
406
|
-
return `## Code Review Request
|
|
407
|
-
|
|
408
|
-
### Mode
|
|
409
|
-
Custom review instructions
|
|
410
|
-
|
|
411
|
-
### Instructions
|
|
412
|
-
|
|
413
|
-
${instructions}
|
|
414
|
-
|
|
415
|
-
Use the Task tool with \`agent: "reviewer"\` to execute this review.`;
|
|
419
|
+
return buildCustomReviewPrompt(instructions);
|
|
416
420
|
}
|
|
417
421
|
|
|
418
422
|
default:
|
|
@@ -4,7 +4,13 @@
|
|
|
4
4
|
* Custom tools are TypeScript modules that define additional tools for the agent.
|
|
5
5
|
* They can provide custom rendering for tool calls and results in the TUI.
|
|
6
6
|
*/
|
|
7
|
-
import type {
|
|
7
|
+
import type {
|
|
8
|
+
AgentToolResult,
|
|
9
|
+
AgentToolUpdateCallback,
|
|
10
|
+
ToolApproval,
|
|
11
|
+
ToolApprovalDecision,
|
|
12
|
+
ToolTier,
|
|
13
|
+
} from "@oh-my-pi/pi-agent-core";
|
|
8
14
|
import type { CompactionResult } from "@oh-my-pi/pi-agent-core/compaction";
|
|
9
15
|
import type { Model, Static, TSchema } from "@oh-my-pi/pi-ai";
|
|
10
16
|
import type { Component } from "@oh-my-pi/pi-tui";
|
|
@@ -23,7 +29,7 @@ export type CustomToolUIContext = HookUIContext;
|
|
|
23
29
|
// Re-export for backward compatibility
|
|
24
30
|
export type { ExecOptions, ExecResult } from "../../exec/exec";
|
|
25
31
|
/** Re-export for custom tools to use in execute signature */
|
|
26
|
-
export type { AgentToolResult, AgentToolUpdateCallback };
|
|
32
|
+
export type { AgentToolResult, AgentToolUpdateCallback, ToolApproval, ToolApprovalDecision, ToolTier };
|
|
27
33
|
|
|
28
34
|
/** Pending action entry consumed by the hidden resolve tool */
|
|
29
35
|
export interface CustomToolPendingAction {
|
|
@@ -80,6 +86,8 @@ export interface CustomToolContext {
|
|
|
80
86
|
abort(): void;
|
|
81
87
|
/** Settings instance for the current session. Prefer over the global singleton. */
|
|
82
88
|
settings?: Settings;
|
|
89
|
+
/** Whether to auto-approve all destructive tool operations (--auto-approve CLI flag) */
|
|
90
|
+
autoApprove?: boolean;
|
|
83
91
|
}
|
|
84
92
|
|
|
85
93
|
/** Session event passed to onSession callback */
|
|
@@ -191,6 +199,12 @@ export interface CustomTool<TParams extends TSchema = TSchema, TDetails = any> {
|
|
|
191
199
|
mcpServerName?: string;
|
|
192
200
|
/** Original MCP tool name for discovery/search metadata. */
|
|
193
201
|
mcpToolName?: string;
|
|
202
|
+
|
|
203
|
+
/** Capability tier declaration used by approval gates. Omitted means "exec". */
|
|
204
|
+
approval?: ToolApproval;
|
|
205
|
+
|
|
206
|
+
/** Lines appended after the standard approval prompt header. */
|
|
207
|
+
formatApprovalDetails?: (args: unknown) => string | string[] | undefined;
|
|
194
208
|
/**
|
|
195
209
|
* Execute the tool.
|
|
196
210
|
* @param toolCallId - Unique ID for this tool call
|
|
@@ -3,7 +3,9 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import type { AgentTool, AgentToolContext, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
|
|
5
5
|
import type { ImageContent, Static, TextContent, TSchema } from "@oh-my-pi/pi-ai";
|
|
6
|
+
import type { Settings } from "../../config/settings";
|
|
6
7
|
import type { Theme } from "../../modes/theme/theme";
|
|
8
|
+
import { type ApprovalMode, formatApprovalPrompt, requiresApproval } from "../../tools/approval";
|
|
7
9
|
import { applyToolProxy } from "../tool-proxy";
|
|
8
10
|
import type { ExtensionRunner } from "./runner";
|
|
9
11
|
import type { RegisteredTool, ToolCallEventResult } from "./types";
|
|
@@ -108,7 +110,40 @@ export class ExtensionToolWrapper<TParameters extends TSchema = TSchema, TDetail
|
|
|
108
110
|
onUpdate?: AgentToolUpdateCallback<TDetails, TParameters>,
|
|
109
111
|
context?: AgentToolContext,
|
|
110
112
|
) {
|
|
111
|
-
//
|
|
113
|
+
// 1. Check approval policy (before extension handlers).
|
|
114
|
+
// CLI `--auto-approve` / `--yolo` forces yolo mode for the session, but
|
|
115
|
+
// tool-level safety overrides still prompt. User `tools.approval.<tool>`
|
|
116
|
+
// policies are honored in every mode.
|
|
117
|
+
const cliAutoApprove = context?.autoApprove === true;
|
|
118
|
+
const settings: Settings | undefined = context?.settings;
|
|
119
|
+
const configuredMode = (settings?.get("tools.approvalMode") ?? "yolo") as ApprovalMode;
|
|
120
|
+
const approvalMode: ApprovalMode = cliAutoApprove ? "yolo" : configuredMode;
|
|
121
|
+
const userPolicies = (settings?.get("tools.approval") ?? {}) as Record<string, unknown>;
|
|
122
|
+
const approvalCheck = requiresApproval(this.tool, params, approvalMode, userPolicies);
|
|
123
|
+
|
|
124
|
+
if (approvalCheck.required) {
|
|
125
|
+
// Check if UI is available
|
|
126
|
+
if (!this.runner.hasUI()) {
|
|
127
|
+
throw new Error(
|
|
128
|
+
`Tool "${this.tool.name}" requires approval but no interactive UI available.\n` +
|
|
129
|
+
`Options:\n` +
|
|
130
|
+
` 1. Set tools.approvalMode: yolo in /settings\n` +
|
|
131
|
+
` 2. Add tools.approval.${this.tool.name}: allow to config\n` +
|
|
132
|
+
` 3. Use an interactive UI to approve the tool call`,
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const uiContext = this.runner.getUIContext();
|
|
137
|
+
const choice = await uiContext.select(formatApprovalPrompt(this.tool, params, approvalCheck.reason), [
|
|
138
|
+
"Approve",
|
|
139
|
+
"Deny",
|
|
140
|
+
]);
|
|
141
|
+
if (choice !== "Approve") {
|
|
142
|
+
throw new Error(`Tool call denied by user: ${this.tool.name}`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// 2. Emit tool_call event - extensions can block execution
|
|
112
147
|
if (this.runner.hasHandlers("tool_call")) {
|
|
113
148
|
try {
|
|
114
149
|
const callResult = (await this.runner.emitToolCall({
|