@oh-my-pi/pi-coding-agent 16.1.7 → 16.1.9
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 +71 -0
- package/dist/cli.js +4266 -3446
- package/dist/types/cli/flag-tables.d.ts +17 -0
- package/dist/types/cli/session-picker.d.ts +0 -1
- package/dist/types/cli/tiny-models-cli.d.ts +2 -0
- package/dist/types/cli-commands.d.ts +4 -2
- package/dist/types/collab/protocol.d.ts +20 -1
- package/dist/types/config/settings-schema.d.ts +57 -6
- package/dist/types/extensibility/legacy-pi-ai-shim.d.ts +5 -1
- package/dist/types/internal-urls/filesystem-resource.d.ts +9 -0
- package/dist/types/mcp/loader.d.ts +3 -2
- package/dist/types/mcp/manager.d.ts +4 -3
- package/dist/types/mcp/startup-events.d.ts +21 -4
- package/dist/types/mnemopi/config.d.ts +1 -0
- package/dist/types/modes/components/agent-hub.d.ts +1 -0
- package/dist/types/modes/components/agent-transcript-viewer.d.ts +1 -0
- package/dist/types/modes/components/assistant-message.d.ts +5 -1
- package/dist/types/modes/components/btw-panel.d.ts +2 -0
- package/dist/types/modes/components/chat-transcript-builder.d.ts +1 -0
- package/dist/types/modes/components/session-selector.d.ts +0 -2
- package/dist/types/modes/controllers/btw-controller.d.ts +2 -0
- package/dist/types/modes/controllers/command-controller.d.ts +1 -1
- package/dist/types/modes/controllers/selector-controller.d.ts +1 -0
- package/dist/types/modes/controllers/streaming-reveal.d.ts +3 -2
- package/dist/types/modes/interactive-mode.d.ts +4 -1
- package/dist/types/modes/types.d.ts +4 -1
- package/dist/types/session/agent-session.d.ts +18 -3
- package/dist/types/session/session-history-format.d.ts +25 -0
- package/dist/types/session/snapcompact-inline.d.ts +1 -1
- package/dist/types/slash-commands/builtin-registry.d.ts +7 -4
- package/dist/types/slash-commands/types.d.ts +2 -0
- package/dist/types/system-prompt.d.ts +1 -0
- package/dist/types/tiny/models.d.ts +11 -7
- package/dist/types/tools/browser/launch.d.ts +5 -0
- package/dist/types/tools/todo.d.ts +1 -0
- package/dist/types/utils/thinking-display.d.ts +2 -0
- package/package.json +12 -12
- package/src/advisor/__tests__/advisor.test.ts +104 -0
- package/src/advisor/runtime.ts +38 -2
- package/src/cli/args.ts +0 -1
- package/src/cli/flag-tables.ts +42 -0
- package/src/cli/profile-bootstrap.ts +1 -11
- package/src/cli/session-picker.ts +1 -2
- package/src/cli/tiny-models-cli.ts +7 -2
- package/src/cli-commands.ts +48 -3
- package/src/collab/guest.ts +172 -20
- package/src/collab/host.ts +47 -5
- package/src/collab/protocol.ts +16 -1
- package/src/config/model-registry.ts +6 -7
- package/src/config/settings-schema.ts +59 -5
- package/src/edit/renderer.ts +8 -12
- package/src/extensibility/legacy-pi-ai-shim.ts +16 -4
- package/src/internal-urls/docs-index.generated.txt +1 -1
- package/src/internal-urls/filesystem-resource.ts +34 -0
- package/src/internal-urls/local-protocol.ts +7 -1
- package/src/internal-urls/memory-protocol.ts +5 -1
- package/src/internal-urls/skill-protocol.ts +20 -4
- package/src/internal-urls/vault-protocol.ts +5 -2
- package/src/lsp/client.ts +39 -25
- package/src/main.ts +8 -8
- package/src/mcp/loader.ts +4 -3
- package/src/mcp/manager.ts +35 -15
- package/src/mcp/startup-events.ts +106 -11
- package/src/mnemopi/config.ts +2 -0
- package/src/mnemopi/state.ts +3 -1
- package/src/modes/components/agent-hub.ts +4 -0
- package/src/modes/components/agent-transcript-viewer.ts +2 -0
- package/src/modes/components/assistant-message.ts +217 -18
- package/src/modes/components/btw-panel.ts +15 -3
- package/src/modes/components/chat-transcript-builder.ts +8 -2
- package/src/modes/components/model-selector.ts +72 -9
- package/src/modes/components/omfg-panel.ts +1 -1
- package/src/modes/components/session-selector.ts +4 -9
- package/src/modes/components/snapcompact-shape-preview.ts +1 -1
- package/src/modes/components/tool-execution.ts +10 -2
- package/src/modes/controllers/btw-controller.ts +32 -7
- package/src/modes/controllers/command-controller.ts +18 -3
- package/src/modes/controllers/event-controller.ts +24 -4
- package/src/modes/controllers/input-controller.ts +43 -21
- package/src/modes/controllers/mcp-command-controller.ts +40 -12
- package/src/modes/controllers/selector-controller.ts +23 -13
- package/src/modes/controllers/streaming-reveal.ts +78 -20
- package/src/modes/controllers/todo-command-controller.ts +9 -10
- package/src/modes/interactive-mode.ts +84 -9
- package/src/modes/types.ts +4 -1
- package/src/modes/utils/ui-helpers.ts +1 -0
- package/src/prompts/advisor/system.md +1 -1
- package/src/prompts/system/side-channel-no-tools.md +3 -0
- package/src/sdk.ts +12 -8
- package/src/session/agent-session.ts +385 -85
- package/src/session/history-storage.ts +15 -16
- package/src/session/session-history-format.ts +41 -1
- package/src/session/snapcompact-inline.ts +9 -6
- package/src/slash-commands/builtin-registry.ts +171 -87
- package/src/slash-commands/helpers/todo.ts +12 -6
- package/src/slash-commands/types.ts +2 -0
- package/src/system-prompt.ts +6 -11
- package/src/tiny/models.ts +7 -3
- package/src/tiny/title-client.ts +8 -2
- package/src/tiny/worker.ts +1 -0
- package/src/tools/browser/launch.ts +107 -31
- package/src/tools/puppeteer/00_stealth_tampering.txt +16 -35
- package/src/tools/puppeteer/01_stealth_activity.txt +79 -19
- package/src/tools/puppeteer/02_stealth_hairline.txt +57 -11
- package/src/tools/puppeteer/03_stealth_botd.txt +10 -14
- package/src/tools/puppeteer/04_stealth_iframe.txt +156 -63
- package/src/tools/puppeteer/05_stealth_webgl.txt +222 -64
- package/src/tools/puppeteer/06_stealth_screen.txt +255 -67
- package/src/tools/puppeteer/07_stealth_fonts.txt +17 -15
- package/src/tools/puppeteer/08_stealth_audio.txt +32 -20
- package/src/tools/puppeteer/09_stealth_locale.txt +45 -40
- package/src/tools/puppeteer/10_stealth_plugins.txt +12 -8
- package/src/tools/puppeteer/11_stealth_hardware.txt +57 -6
- package/src/tools/puppeteer/12_stealth_codecs.txt +20 -18
- package/src/tools/puppeteer/13_stealth_worker.txt +206 -45
- package/src/tools/search.ts +10 -1
- package/src/tools/sqlite-reader.ts +59 -3
- package/src/tools/todo.ts +6 -0
- package/src/tools/write.ts +4 -6
- package/src/utils/thinking-display.ts +78 -0
|
@@ -15,25 +15,27 @@ const parseInput = (arg) => {
|
|
|
15
15
|
};
|
|
16
16
|
|
|
17
17
|
const originalCanPlayType = HTMLMediaElement.prototype.canPlayType;
|
|
18
|
+
const proxiedCanPlayType = new Window_Proxy(originalCanPlayType, {
|
|
19
|
+
apply(target, ctx, args) {
|
|
20
|
+
if (!args || !args.length) {
|
|
21
|
+
return Reflect_apply(target, ctx, args);
|
|
22
|
+
}
|
|
23
|
+
const { mime, codecs } = parseInput(args[0]);
|
|
24
|
+
if (mime === "video/mp4" && codecs.includes("avc1.42E01E")) {
|
|
25
|
+
return "probably";
|
|
26
|
+
}
|
|
27
|
+
if (mime === "audio/x-m4a" && !codecs.length) {
|
|
28
|
+
return "maybe";
|
|
29
|
+
}
|
|
30
|
+
if (mime === "audio/aac" && !codecs.length) {
|
|
31
|
+
return "probably";
|
|
32
|
+
}
|
|
33
|
+
return Reflect_apply(target, ctx, args);
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
patchToString(proxiedCanPlayType, "canPlayType");
|
|
18
37
|
Object_defineProperty(HTMLMediaElement.prototype, "canPlayType", {
|
|
19
|
-
value:
|
|
20
|
-
apply(target, ctx, args) {
|
|
21
|
-
if (!args || !args.length) {
|
|
22
|
-
return Reflect_apply(target, ctx, args);
|
|
23
|
-
}
|
|
24
|
-
const { mime, codecs } = parseInput(args[0]);
|
|
25
|
-
if (mime === "video/mp4" && codecs.includes("avc1.42E01E")) {
|
|
26
|
-
return "probably";
|
|
27
|
-
}
|
|
28
|
-
if (mime === "audio/x-m4a" && !codecs.length) {
|
|
29
|
-
return "maybe";
|
|
30
|
-
}
|
|
31
|
-
if (mime === "audio/aac" && !codecs.length) {
|
|
32
|
-
return "probably";
|
|
33
|
-
}
|
|
34
|
-
return Reflect_apply(target, ctx, args);
|
|
35
|
-
},
|
|
36
|
-
}),
|
|
38
|
+
value: proxiedCanPlayType,
|
|
37
39
|
writable: true,
|
|
38
40
|
configurable: true,
|
|
39
41
|
enumerable: true,
|
|
@@ -1,52 +1,214 @@
|
|
|
1
1
|
const patchWorkerConstructor = (name, OriginalWorker) => {
|
|
2
2
|
if (typeof OriginalWorker !== "function") return;
|
|
3
3
|
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
4
|
+
const windowDescriptor = Object_getOwnPropertyDescriptor(window, name);
|
|
5
|
+
if (windowDescriptor && windowDescriptor.configurable === false) return;
|
|
6
|
+
|
|
7
|
+
const NativeURL = window.URL;
|
|
8
|
+
const URL_createObjectURL = NativeURL && NativeURL.createObjectURL;
|
|
9
|
+
const URL_revokeObjectURL = NativeURL && NativeURL.revokeObjectURL;
|
|
10
|
+
if (
|
|
11
|
+
typeof NativeURL !== "function" ||
|
|
12
|
+
typeof URL_createObjectURL !== "function" ||
|
|
13
|
+
typeof URL_revokeObjectURL !== "function"
|
|
14
|
+
) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const sharedWorkerUrls = name === "SharedWorker" ? new Map() : undefined;
|
|
19
|
+
|
|
20
|
+
const revokeUrl = (url) => {
|
|
21
|
+
try {
|
|
22
|
+
Reflect_apply(URL_revokeObjectURL, NativeURL, [url]);
|
|
23
|
+
} catch {}
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const scheduleWorkerUrlRevoke = (worker, url) => {
|
|
27
|
+
let revoked = false;
|
|
28
|
+
const revokeOnce = () => {
|
|
29
|
+
if (revoked) return;
|
|
30
|
+
revoked = true;
|
|
31
|
+
revokeUrl(url);
|
|
32
|
+
};
|
|
33
|
+
try {
|
|
34
|
+
if (typeof worker.addEventListener === "function") {
|
|
35
|
+
Reflect_apply(worker.addEventListener, worker, ["error", revokeOnce, { once: true }]);
|
|
36
|
+
}
|
|
37
|
+
} catch {}
|
|
38
|
+
Window_setTimeout(revokeOnce, 1000);
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
if (sharedWorkerUrls) {
|
|
42
|
+
try {
|
|
43
|
+
window.addEventListener("pagehide", (event) => {
|
|
44
|
+
if (event && event.persisted) return;
|
|
45
|
+
for (const url of sharedWorkerUrls.values()) {
|
|
46
|
+
revokeUrl(url);
|
|
47
|
+
}
|
|
48
|
+
sharedWorkerUrls.clear();
|
|
49
|
+
});
|
|
50
|
+
} catch {}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const canWrapArguments = (args) => {
|
|
54
|
+
if (!args || args.length !== 1) {
|
|
55
|
+
return name === "SharedWorker" && args && args.length === 2 && typeof args[1] === "string";
|
|
56
|
+
}
|
|
57
|
+
return true;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const resolveWorkerUrl = (scriptURL) => {
|
|
61
|
+
const baseUrl = document.baseURI || window.location.href;
|
|
62
|
+
const scriptUrlString =
|
|
63
|
+
typeof scriptURL === "string"
|
|
64
|
+
? scriptURL
|
|
65
|
+
: scriptURL instanceof NativeURL
|
|
66
|
+
? scriptURL.href
|
|
67
|
+
: undefined;
|
|
68
|
+
if (scriptUrlString === undefined) return undefined;
|
|
69
|
+
let absoluteUrl;
|
|
70
|
+
try {
|
|
71
|
+
absoluteUrl = new NativeURL(scriptUrlString, baseUrl);
|
|
72
|
+
} catch {
|
|
73
|
+
return undefined;
|
|
74
|
+
}
|
|
75
|
+
if (absoluteUrl.origin !== window.location.origin) return undefined;
|
|
76
|
+
if (absoluteUrl.protocol !== "http:" && absoluteUrl.protocol !== "https:") return undefined;
|
|
77
|
+
if (absoluteUrl.username || absoluteUrl.password) return undefined;
|
|
78
|
+
return absoluteUrl.href;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const buildWorkerPrelude = () => {
|
|
82
|
+
const values = [];
|
|
83
|
+
const addNavigatorString = (prop) => {
|
|
84
|
+
try {
|
|
85
|
+
const value = navigator[prop];
|
|
86
|
+
if (typeof value === "string") values.push([prop, value]);
|
|
87
|
+
} catch {}
|
|
88
|
+
};
|
|
89
|
+
addNavigatorString("userAgent");
|
|
90
|
+
addNavigatorString("platform");
|
|
91
|
+
if (!values.length) return "";
|
|
92
|
+
|
|
93
|
+
return `(() => {
|
|
94
|
+
try {
|
|
95
|
+
const values = ${JSON.stringify(values)};
|
|
96
|
+
const nav = self.navigator;
|
|
97
|
+
if (!nav) return;
|
|
98
|
+
const defineProperty = Object.defineProperty;
|
|
99
|
+
const getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
|
|
100
|
+
const getPrototypeOf = Object.getPrototypeOf;
|
|
101
|
+
const Reflect_apply = Reflect.apply;
|
|
102
|
+
const nativeFunctionToString = Function.prototype.toString;
|
|
103
|
+
const nativeSources = new WeakMap();
|
|
104
|
+
const WeakMap_get = WeakMap.prototype.get;
|
|
105
|
+
const WeakMap_has = WeakMap.prototype.has;
|
|
106
|
+
const WeakMap_set = WeakMap.prototype.set;
|
|
107
|
+
const rememberNative = (fn, source) => {
|
|
108
|
+
if (typeof fn === "function") Reflect_apply(WeakMap_set, nativeSources, [fn, source]);
|
|
109
|
+
return fn;
|
|
110
|
+
};
|
|
111
|
+
const findDescriptor = (prop) => {
|
|
112
|
+
let owner = nav;
|
|
113
|
+
while (owner) {
|
|
114
|
+
const descriptor = getOwnPropertyDescriptor(owner, prop);
|
|
115
|
+
if (descriptor) return [owner, descriptor];
|
|
116
|
+
owner = getPrototypeOf(owner);
|
|
117
|
+
}
|
|
118
|
+
return [getPrototypeOf(nav) || nav, undefined];
|
|
119
|
+
};
|
|
120
|
+
let patched = false;
|
|
121
|
+
for (let i = 0; i < values.length; i += 1) {
|
|
122
|
+
const prop = values[i][0];
|
|
123
|
+
const value = values[i][1];
|
|
124
|
+
try {
|
|
125
|
+
if (nav[prop] === value) continue;
|
|
126
|
+
} catch (_) {}
|
|
127
|
+
const found = findDescriptor(prop);
|
|
128
|
+
const owner = found[0];
|
|
129
|
+
const descriptor = found[1];
|
|
130
|
+
if (!owner || (descriptor && descriptor.configurable === false)) continue;
|
|
131
|
+
const getterDescriptor = getOwnPropertyDescriptor({ get [prop]() { return value; } }, prop);
|
|
132
|
+
const getter = getterDescriptor && getterDescriptor.get;
|
|
133
|
+
if (typeof getter !== "function") continue;
|
|
134
|
+
rememberNative(getter, "function get " + prop + "() { [native code] }");
|
|
135
|
+
defineProperty(owner, prop, {
|
|
136
|
+
get: getter,
|
|
137
|
+
enumerable: descriptor ? descriptor.enumerable : true,
|
|
138
|
+
configurable: true,
|
|
139
|
+
});
|
|
140
|
+
patched = true;
|
|
24
141
|
}
|
|
142
|
+
if (!patched) return;
|
|
143
|
+
const toStringDescriptor = getOwnPropertyDescriptor(Function.prototype, "toString");
|
|
144
|
+
if (toStringDescriptor && toStringDescriptor.configurable === false) return;
|
|
145
|
+
const functionToString = new Proxy(nativeFunctionToString, {
|
|
146
|
+
apply(target, thisArg, args) {
|
|
147
|
+
if (Reflect_apply(WeakMap_has, nativeSources, [thisArg])) return Reflect_apply(WeakMap_get, nativeSources, [thisArg]);
|
|
148
|
+
return Reflect_apply(target, thisArg, args);
|
|
149
|
+
},
|
|
150
|
+
});
|
|
151
|
+
rememberNative(functionToString, "function toString() { [native code] }");
|
|
152
|
+
defineProperty(Function.prototype, "toString", {
|
|
153
|
+
value: functionToString,
|
|
154
|
+
writable: toStringDescriptor ? toStringDescriptor.writable : true,
|
|
155
|
+
configurable: toStringDescriptor ? toStringDescriptor.configurable : true,
|
|
156
|
+
enumerable: toStringDescriptor ? toStringDescriptor.enumerable : false,
|
|
157
|
+
});
|
|
158
|
+
} catch (_) {}
|
|
159
|
+
})();`;
|
|
160
|
+
};
|
|
25
161
|
|
|
26
|
-
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
162
|
+
const buildWrappedUrl = (scriptURL) => {
|
|
163
|
+
const originalUrl = resolveWorkerUrl(scriptURL);
|
|
164
|
+
if (!originalUrl) return undefined;
|
|
165
|
+
if (sharedWorkerUrls) {
|
|
166
|
+
const cachedUrl = sharedWorkerUrls.get(originalUrl);
|
|
167
|
+
if (cachedUrl) return { url: cachedUrl, cacheKey: originalUrl };
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const prelude = buildWorkerPrelude();
|
|
171
|
+
if (!prelude) return undefined;
|
|
172
|
+
|
|
173
|
+
try {
|
|
174
|
+
const blob = new Window_Blob(
|
|
175
|
+
[prelude, "\n", `importScripts(${JSON.stringify(originalUrl)});`],
|
|
176
|
+
{ type: "application/javascript" },
|
|
177
|
+
);
|
|
178
|
+
const url = Reflect_apply(URL_createObjectURL, NativeURL, [blob]);
|
|
179
|
+
if (sharedWorkerUrls) sharedWorkerUrls.set(originalUrl, url);
|
|
180
|
+
return { url, cacheKey: sharedWorkerUrls ? originalUrl : undefined };
|
|
181
|
+
} catch {
|
|
182
|
+
return undefined;
|
|
183
|
+
}
|
|
34
184
|
};
|
|
35
185
|
|
|
36
186
|
const handler = {
|
|
37
|
-
construct(target, args) {
|
|
187
|
+
construct(target, args, newTarget) {
|
|
188
|
+
if (!canWrapArguments(args)) {
|
|
189
|
+
return Reflect_construct(target, args || [], newTarget);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const wrapped = buildWrappedUrl(args[0]);
|
|
193
|
+
if (!wrapped) {
|
|
194
|
+
return Reflect_construct(target, args || [], newTarget);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const wrappedArgs = [wrapped.url];
|
|
198
|
+
for (let i = 1; i < args.length; i += 1) {
|
|
199
|
+
wrappedArgs[i] = args[i];
|
|
200
|
+
}
|
|
201
|
+
|
|
38
202
|
try {
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
return new target(...(args || []));
|
|
203
|
+
const worker = Reflect_construct(target, wrappedArgs, newTarget);
|
|
204
|
+
if (!sharedWorkerUrls) {
|
|
205
|
+
scheduleWorkerUrlRevoke(worker, wrapped.url);
|
|
43
206
|
}
|
|
44
|
-
const wrappedUrl = buildWrappedUrl(scriptURL, options);
|
|
45
|
-
const worker = new target(wrappedUrl, options);
|
|
46
|
-
URL.revokeObjectURL(wrappedUrl);
|
|
47
207
|
return worker;
|
|
48
208
|
} catch {
|
|
49
|
-
|
|
209
|
+
if (wrapped.cacheKey && sharedWorkerUrls) sharedWorkerUrls.delete(wrapped.cacheKey);
|
|
210
|
+
revokeUrl(wrapped.url);
|
|
211
|
+
return Reflect_construct(target, args || [], newTarget);
|
|
50
212
|
}
|
|
51
213
|
},
|
|
52
214
|
apply(target, thisArg, args) {
|
|
@@ -55,16 +217,15 @@ const patchWorkerConstructor = (name, OriginalWorker) => {
|
|
|
55
217
|
};
|
|
56
218
|
|
|
57
219
|
const proxied = new Window_Proxy(OriginalWorker, handler);
|
|
220
|
+
patchToString(proxied, name);
|
|
58
221
|
Object_defineProperty(window, name, {
|
|
59
222
|
value: proxied,
|
|
60
|
-
writable:
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
configurable: true,
|
|
67
|
-
enumerable: false,
|
|
223
|
+
writable:
|
|
224
|
+
windowDescriptor && "writable" in windowDescriptor
|
|
225
|
+
? windowDescriptor.writable
|
|
226
|
+
: true,
|
|
227
|
+
configurable: windowDescriptor ? windowDescriptor.configurable : true,
|
|
228
|
+
enumerable: windowDescriptor ? windowDescriptor.enumerable : false,
|
|
68
229
|
});
|
|
69
230
|
};
|
|
70
231
|
|
package/src/tools/search.ts
CHANGED
|
@@ -278,6 +278,15 @@ interface InternalSearchInputResolution {
|
|
|
278
278
|
virtualScopePath?: string;
|
|
279
279
|
}
|
|
280
280
|
|
|
281
|
+
function isImmutableSourcePath(filePath: string, immutableSourcePaths: ReadonlySet<string>): boolean {
|
|
282
|
+
for (const immutablePath of immutableSourcePaths) {
|
|
283
|
+
if (filePath === immutablePath || filePath.startsWith(`${immutablePath}${path.sep}`)) {
|
|
284
|
+
return true;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
return false;
|
|
288
|
+
}
|
|
289
|
+
|
|
281
290
|
interface IndexedContentLines {
|
|
282
291
|
lines: string[];
|
|
283
292
|
starts: number[];
|
|
@@ -1131,7 +1140,7 @@ export class SearchTool implements AgentTool<typeof searchSchema, SearchToolDeta
|
|
|
1131
1140
|
for (const relativePath of fileList) {
|
|
1132
1141
|
if (archiveDisplaySet.has(relativePath) || virtualPathSet.has(relativePath)) continue;
|
|
1133
1142
|
const absoluteFilePath = path.resolve(this.session.cwd, relativePath);
|
|
1134
|
-
if (
|
|
1143
|
+
if (isImmutableSourcePath(absoluteFilePath, immutableSourcePaths)) continue;
|
|
1135
1144
|
// Mint a whole-file content tag so any anchor validates while the
|
|
1136
1145
|
// file is unchanged; over-cap / unreadable files get no tag (and
|
|
1137
1146
|
// therefore plain, non-editable line output).
|
|
@@ -21,7 +21,19 @@ const MAX_QUERY_LIMIT = 500;
|
|
|
21
21
|
export const MAX_RAW_QUERY_ROWS = 1000;
|
|
22
22
|
const MAX_RENDER_WIDTH = 120;
|
|
23
23
|
const MAX_COLUMN_WIDTH = 40;
|
|
24
|
-
|
|
24
|
+
/**
|
|
25
|
+
* Floor for each ASCII-table column. At width 2 (or 1) every multi-char cell
|
|
26
|
+
* collapses to a lone ellipsis, so the renderer keeps each column wide enough
|
|
27
|
+
* to show at least one real glyph alongside the ellipsis (e.g. `Fo…`). When a
|
|
28
|
+
* row has too many columns to honor this floor inside `MAX_RENDER_WIDTH`,
|
|
29
|
+
* `buildAsciiTable` falls back to per-row vertical blocks via
|
|
30
|
+
* {@link buildVerticalBlocks} — issue #3107.
|
|
31
|
+
*/
|
|
32
|
+
const MIN_COLUMN_WIDTH = 3;
|
|
33
|
+
/** Separator overhead per column in the ASCII table (`" | "`). */
|
|
34
|
+
const COLUMN_SEPARATOR_WIDTH = 3;
|
|
35
|
+
/** Constant frame overhead added once to every row (leading `"|"` + trailing `" |"` after the per-column accounting). */
|
|
36
|
+
const TABLE_FRAME_WIDTH = 1;
|
|
25
37
|
/**
|
|
26
38
|
* Upper bound on rows scanned when counting a table for the listing. SQLite has
|
|
27
39
|
* no stored row count, so `COUNT(*)` is a full b-tree scan — multi-second on a
|
|
@@ -142,10 +154,53 @@ function padCell(value: string, width: number): string {
|
|
|
142
154
|
return `${truncated}${" ".repeat(width - visibleWidth)}`;
|
|
143
155
|
}
|
|
144
156
|
|
|
157
|
+
/**
|
|
158
|
+
* Width budget the ASCII layout needs at the floor (each column at
|
|
159
|
+
* `MIN_COLUMN_WIDTH`). When this exceeds `MAX_RENDER_WIDTH`, no choice of
|
|
160
|
+
* per-column widths can fit the header inside the budget — every cell is then
|
|
161
|
+
* forced down to width 1 by the shrink loop, rendering as a lone ellipsis, and
|
|
162
|
+
* the right edge is still chopped by the final per-line truncation (#3107).
|
|
163
|
+
*/
|
|
164
|
+
function tableFitsAtMinimum(columnCount: number): boolean {
|
|
165
|
+
return MIN_COLUMN_WIDTH * columnCount + COLUMN_SEPARATOR_WIDTH * columnCount + TABLE_FRAME_WIDTH <= MAX_RENDER_WIDTH;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Vertical fallback used when a table has too many columns to fit horizontally
|
|
170
|
+
* (>19 at the default 120-cell budget). Each row becomes a labelled block of
|
|
171
|
+
* `column: value` lines, mirroring `psql`'s expanded display mode. Column
|
|
172
|
+
* names are right-padded so colons align; the value is left raw and the whole
|
|
173
|
+
* line is truncated at `MAX_RENDER_WIDTH`.
|
|
174
|
+
*/
|
|
175
|
+
function buildVerticalBlocks(columns: string[], rows: SqliteRow[]): string {
|
|
176
|
+
if (rows.length === 0) {
|
|
177
|
+
return "(no rows)";
|
|
178
|
+
}
|
|
179
|
+
let nameWidth = MIN_COLUMN_WIDTH;
|
|
180
|
+
for (const column of columns) {
|
|
181
|
+
nameWidth = Math.max(nameWidth, Bun.stringWidth(sanitizeCell(column)));
|
|
182
|
+
}
|
|
183
|
+
nameWidth = Math.min(MAX_COLUMN_WIDTH, nameWidth);
|
|
184
|
+
return rows
|
|
185
|
+
.map((row, index) => {
|
|
186
|
+
const block = [`── Row ${index + 1} ──`];
|
|
187
|
+
for (const column of columns) {
|
|
188
|
+
const name = padCell(column, nameWidth);
|
|
189
|
+
const value = sanitizeCell(stringifySqliteValue(row[column]));
|
|
190
|
+
block.push(truncateToWidth(`${name}: ${value}`, MAX_RENDER_WIDTH));
|
|
191
|
+
}
|
|
192
|
+
return block.join("\n");
|
|
193
|
+
})
|
|
194
|
+
.join("\n\n");
|
|
195
|
+
}
|
|
196
|
+
|
|
145
197
|
function buildAsciiTable(columns: string[], rows: SqliteRow[]): string {
|
|
146
198
|
if (columns.length === 0) {
|
|
147
199
|
return rows.length === 0 ? "(no rows)" : "(rows returned without named columns)";
|
|
148
200
|
}
|
|
201
|
+
if (!tableFitsAtMinimum(columns.length)) {
|
|
202
|
+
return buildVerticalBlocks(columns, rows);
|
|
203
|
+
}
|
|
149
204
|
|
|
150
205
|
const widths = columns.map(column =>
|
|
151
206
|
Math.max(MIN_COLUMN_WIDTH, Math.min(MAX_COLUMN_WIDTH, Bun.stringWidth(sanitizeCell(column)))),
|
|
@@ -157,7 +212,8 @@ function buildAsciiTable(columns: string[], rows: SqliteRow[]): string {
|
|
|
157
212
|
}
|
|
158
213
|
}
|
|
159
214
|
|
|
160
|
-
|
|
215
|
+
const overhead = columns.length * COLUMN_SEPARATOR_WIDTH + TABLE_FRAME_WIDTH;
|
|
216
|
+
let totalWidth = widths.reduce((sum, width) => sum + width, 0) + overhead;
|
|
161
217
|
while (totalWidth > MAX_RENDER_WIDTH) {
|
|
162
218
|
let widestIndex = -1;
|
|
163
219
|
let widestWidth = MIN_COLUMN_WIDTH;
|
|
@@ -169,7 +225,7 @@ function buildAsciiTable(columns: string[], rows: SqliteRow[]): string {
|
|
|
169
225
|
}
|
|
170
226
|
if (widestIndex === -1) break;
|
|
171
227
|
widths[widestIndex] = Math.max(MIN_COLUMN_WIDTH, (widths[widestIndex] ?? MIN_COLUMN_WIDTH) - 1);
|
|
172
|
-
totalWidth = widths.reduce((sum, width) => sum + width, 0) +
|
|
228
|
+
totalWidth = widths.reduce((sum, width) => sum + width, 0) + overhead;
|
|
173
229
|
}
|
|
174
230
|
|
|
175
231
|
const header = `| ${columns.map((column, index) => padCell(column, widths[index] ?? MIN_COLUMN_WIDTH)).join(" | ")} |`;
|
package/src/tools/todo.ts
CHANGED
|
@@ -11,6 +11,7 @@ import todoDescription from "../prompts/tools/todo.md" with { type: "text" };
|
|
|
11
11
|
import type { ToolSession } from "../sdk";
|
|
12
12
|
import type { SessionEntry } from "../session/session-entries";
|
|
13
13
|
import { framedBlock, renderStatusLine, renderTreeList } from "../tui";
|
|
14
|
+
import { normalizePathLikeInput, resolveToCwd } from "./path-utils";
|
|
14
15
|
import { formatErrorDetail, PREVIEW_LIMITS } from "./render-utils";
|
|
15
16
|
|
|
16
17
|
// =============================================================================
|
|
@@ -428,6 +429,11 @@ const STATUS_TO_MARKER: Record<TodoStatus, string> = {
|
|
|
428
429
|
abandoned: "-",
|
|
429
430
|
};
|
|
430
431
|
|
|
432
|
+
export function resolveTodoMarkdownPath(input: string, cwd: string): string {
|
|
433
|
+
const raw = normalizePathLikeInput(input) || "TODO.md";
|
|
434
|
+
return resolveToCwd(raw, cwd);
|
|
435
|
+
}
|
|
436
|
+
|
|
431
437
|
/** Render todo phases as a Markdown checklist suitable for editing/copying. */
|
|
432
438
|
export function phasesToMarkdown(phases: TodoPhase[]): string {
|
|
433
439
|
if (phases.length === 0) return "# Todos\n";
|
package/src/tools/write.ts
CHANGED
|
@@ -1068,14 +1068,12 @@ export const writeToolRenderer = {
|
|
|
1068
1068
|
const lang = getLanguageFromPath(rawPath) ?? "text";
|
|
1069
1069
|
const langIcon = uiTheme.fg("muted", uiTheme.getLangIcon(lang));
|
|
1070
1070
|
const pathDisplay = filePath ? uiTheme.fg("accent", filePath) : uiTheme.fg("toolOutput", "…");
|
|
1071
|
-
//
|
|
1072
|
-
//
|
|
1073
|
-
//
|
|
1074
|
-
//
|
|
1075
|
-
// liveness cue rides the trailing "(streaming)" line instead.
|
|
1071
|
+
// No status icon on the head row: it's the head of the framed block, and
|
|
1072
|
+
// native-scrollback commits are prefix-only — an animated glyph would pin
|
|
1073
|
+
// the commit boundary at the top, and the pending hourglass just adds
|
|
1074
|
+
// noise. The liveness cue rides the trailing "(streaming)" line instead.
|
|
1076
1075
|
const header = renderStatusLine(
|
|
1077
1076
|
{
|
|
1078
|
-
icon: "pending",
|
|
1079
1077
|
title: "Write",
|
|
1080
1078
|
description: `${langIcon} ${pathDisplay}`,
|
|
1081
1079
|
},
|
|
@@ -9,3 +9,81 @@ export function canonicalizeMessage(text: string | null | undefined): string {
|
|
|
9
9
|
}
|
|
10
10
|
return "";
|
|
11
11
|
}
|
|
12
|
+
|
|
13
|
+
export function formatThinkingForDisplay(text: string, proseOnly: boolean): string {
|
|
14
|
+
if (!proseOnly || !text) return text;
|
|
15
|
+
|
|
16
|
+
const lines = text.split("\n");
|
|
17
|
+
const resultLines: string[] = [];
|
|
18
|
+
let inFence = false;
|
|
19
|
+
let fenceChar = "";
|
|
20
|
+
let fenceLen = 0;
|
|
21
|
+
|
|
22
|
+
const FENCE = /^( {0,3})([`~]{3,})/;
|
|
23
|
+
const appendEllipsis = () => {
|
|
24
|
+
let lastLineIdx = resultLines.length - 1;
|
|
25
|
+
while (lastLineIdx >= 0 && resultLines[lastLineIdx]!.trim() === "") {
|
|
26
|
+
lastLineIdx--;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (lastLineIdx >= 0) {
|
|
30
|
+
const lastLine = resultLines[lastLineIdx]!;
|
|
31
|
+
const trimmed = lastLine.trimEnd();
|
|
32
|
+
if (trimmed.endsWith("...")) {
|
|
33
|
+
resultLines[lastLineIdx] = trimmed;
|
|
34
|
+
} else if (trimmed.endsWith(".")) {
|
|
35
|
+
resultLines[lastLineIdx] = `${trimmed.slice(0, -1)}...`;
|
|
36
|
+
} else {
|
|
37
|
+
resultLines[lastLineIdx] = `${trimmed}...`;
|
|
38
|
+
}
|
|
39
|
+
} else {
|
|
40
|
+
resultLines.push("...");
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
for (let i = 0; i < lines.length; i++) {
|
|
45
|
+
const line = lines[i]!;
|
|
46
|
+
const open = FENCE.exec(line);
|
|
47
|
+
|
|
48
|
+
if (inFence) {
|
|
49
|
+
// A closing fence is the same char, at least as long, with nothing else on the line.
|
|
50
|
+
if (
|
|
51
|
+
open &&
|
|
52
|
+
open[2]![0] === fenceChar &&
|
|
53
|
+
open[2]!.length >= fenceLen &&
|
|
54
|
+
line.slice(open[1]!.length + open[2]!.length).trim() === ""
|
|
55
|
+
) {
|
|
56
|
+
inFence = false;
|
|
57
|
+
fenceChar = "";
|
|
58
|
+
fenceLen = 0;
|
|
59
|
+
}
|
|
60
|
+
// We skip all internal lines of a code fence.
|
|
61
|
+
} else if (open) {
|
|
62
|
+
const marker = open[2]!;
|
|
63
|
+
const ch = marker[0]!;
|
|
64
|
+
// A backtick fence's info string may not contain a backtick.
|
|
65
|
+
if (!(ch === "`" && line.slice(open[1]!.length + marker.length).includes("`"))) {
|
|
66
|
+
inFence = true;
|
|
67
|
+
fenceChar = ch;
|
|
68
|
+
fenceLen = marker.length;
|
|
69
|
+
appendEllipsis();
|
|
70
|
+
} else {
|
|
71
|
+
resultLines.push(line);
|
|
72
|
+
}
|
|
73
|
+
} else {
|
|
74
|
+
resultLines.push(line);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const formatted = resultLines.join("\n");
|
|
79
|
+
return formatted;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function hasDisplayableThinking(
|
|
83
|
+
text: string | null | undefined,
|
|
84
|
+
formattedText: string | null | undefined,
|
|
85
|
+
): boolean {
|
|
86
|
+
if (!text) return false;
|
|
87
|
+
if (!formattedText) return false;
|
|
88
|
+
return formattedText.length > 0 && canonicalizeMessage(text).length > 0;
|
|
89
|
+
}
|