@oh-my-pi/pi-coding-agent 17.2.10 → 17.2.11
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 +40 -0
- package/dist/{CHANGELOG-rhwzpexa.md → CHANGELOG-d9xenpn9.md} +40 -0
- package/dist/cli.js +3025 -3016
- package/dist/types/capability/mcp.d.ts +11 -0
- package/dist/types/capability/skill.d.ts +6 -0
- package/dist/types/cli/command-help.d.ts +3 -0
- package/dist/types/commands/share.d.ts +25 -0
- package/dist/types/commit/agentic/index.d.ts +3 -1
- package/dist/types/commit/execute.d.ts +32 -0
- package/dist/types/commit/index.d.ts +2 -1
- package/dist/types/commit/pipeline.d.ts +7 -1
- package/dist/types/config/model-registry.d.ts +4 -0
- package/dist/types/config/settings-schema.d.ts +1 -34
- package/dist/types/discovery/agent-plugin-format.d.ts +143 -0
- package/dist/types/discovery/agent-plugins.d.ts +1 -0
- package/dist/types/discovery/contained-path.d.ts +34 -0
- package/dist/types/discovery/index.d.ts +1 -0
- package/dist/types/extensibility/custom-tools/types.d.ts +2 -2
- package/dist/types/extensibility/extensions/loader.d.ts +7 -0
- package/dist/types/extensibility/extensions/types.d.ts +11 -0
- package/dist/types/extensibility/shared-events.d.ts +3 -2
- package/dist/types/extensibility/skills.d.ts +5 -0
- package/dist/types/mcp/transports/header-policy.d.ts +45 -0
- package/dist/types/mcp/types.d.ts +15 -0
- package/dist/types/modes/components/agent-hub.d.ts +2 -0
- package/dist/types/modes/components/agent-transcript-viewer.d.ts +2 -0
- package/dist/types/modes/components/chat-transcript-builder.d.ts +2 -0
- package/dist/types/modes/components/custom-editor.d.ts +1 -2
- package/dist/types/modes/components/status-line/types.d.ts +2 -0
- package/dist/types/modes/components/tool-execution.d.ts +2 -0
- package/dist/types/modes/utils/transcript-render-helpers.d.ts +3 -2
- package/dist/types/secrets/index.d.ts +23 -1
- package/dist/types/session/agent-session-events.d.ts +2 -2
- package/dist/types/session/agent-session.d.ts +1 -1
- package/dist/types/session/messages.d.ts +1 -0
- package/dist/types/session/prewalk.d.ts +3 -1
- package/dist/types/session/session-tools.d.ts +8 -0
- package/dist/types/session/turn-recovery.d.ts +7 -2
- package/dist/types/task/index.d.ts +2 -0
- package/dist/types/tools/shell-tokenize.d.ts +16 -0
- package/package.json +13 -13
- package/scripts/legacy-pi-virtual-module.ts +4 -1
- package/src/capability/mcp.ts +11 -0
- package/src/capability/skill.ts +6 -0
- package/src/cli/command-help.ts +4 -0
- package/src/cli/gallery-cli.ts +1 -1
- package/src/cli-commands.ts +5 -0
- package/src/commands/commit.ts +16 -3
- package/src/commands/share.ts +71 -0
- package/src/commit/agentic/index.ts +39 -21
- package/src/commit/execute.ts +56 -0
- package/src/commit/index.ts +2 -1
- package/src/commit/pipeline.ts +20 -7
- package/src/config/model-discovery.ts +1 -1
- package/src/config/model-registry.ts +21 -1
- package/src/config/settings-schema.ts +2 -33
- package/src/config/settings.ts +46 -0
- package/src/discovery/agent-plugin-format.ts +551 -0
- package/src/discovery/agent-plugins.ts +341 -0
- package/src/discovery/claude-plugins.ts +21 -5
- package/src/discovery/contained-path.ts +78 -0
- package/src/discovery/helpers.ts +23 -9
- package/src/discovery/index.ts +1 -0
- package/src/discovery/omp-plugins.ts +20 -7
- package/src/exec/non-interactive-env.ts +1 -0
- package/src/extensibility/custom-tools/types.ts +2 -2
- package/src/extensibility/extensions/loader.ts +78 -14
- package/src/extensibility/extensions/runner.ts +6 -0
- package/src/extensibility/extensions/types.ts +12 -0
- package/src/extensibility/plugins/marketplace/manager.ts +2 -1
- package/src/extensibility/shared-events.ts +3 -2
- package/src/extensibility/skills.ts +9 -0
- package/src/internal-urls/skill-protocol.ts +14 -0
- package/src/internal-urls/vault-protocol.ts +2 -2
- package/src/launch/client.ts +11 -1
- package/src/launch/protocol.ts +7 -2
- package/src/main.ts +3 -2
- package/src/mcp/config.ts +3 -0
- package/src/mcp/manager.ts +12 -9
- package/src/mcp/transports/header-policy.ts +95 -0
- package/src/mcp/transports/http.ts +35 -52
- package/src/mcp/transports/sse.ts +20 -27
- package/src/mcp/types.ts +15 -0
- package/src/modes/acp/acp-agent.ts +15 -4
- package/src/modes/components/agent-dashboard.ts +2 -0
- package/src/modes/components/agent-hub.ts +5 -0
- package/src/modes/components/agent-transcript-viewer.ts +3 -0
- package/src/modes/components/chat-transcript-builder.ts +3 -0
- package/src/modes/components/custom-editor.ts +0 -9
- package/src/modes/components/status-line/component.ts +1 -0
- package/src/modes/components/status-line/segments.ts +6 -4
- package/src/modes/components/status-line/types.ts +2 -0
- package/src/modes/components/tool-execution.ts +13 -16
- package/src/modes/components/tree-selector.ts +62 -3
- package/src/modes/controllers/command-controller.ts +4 -1
- package/src/modes/controllers/event-controller.ts +15 -15
- package/src/modes/controllers/input-controller.ts +20 -2
- package/src/modes/controllers/selector-controller.ts +1 -0
- package/src/modes/utils/transcript-render-helpers.ts +4 -2
- package/src/modes/utils/ui-helpers.ts +1 -0
- package/src/sdk.ts +6 -49
- package/src/secrets/index.ts +52 -1
- package/src/session/agent-session-events.ts +2 -2
- package/src/session/agent-session.ts +33 -14
- package/src/session/messages.ts +1 -0
- package/src/session/prewalk.ts +57 -17
- package/src/session/session-context.ts +3 -5
- package/src/session/session-handoff.ts +5 -1
- package/src/session/session-tools.ts +37 -3
- package/src/session/todo-tracker.ts +11 -1
- package/src/session/turn-recovery.ts +94 -72
- package/src/slash-commands/builtin-registry.ts +12 -8
- package/src/task/index.ts +21 -6
- package/src/tools/bash-skill-urls.ts +15 -0
- package/src/tools/bash.ts +10 -11
- package/src/tools/debug.ts +1 -1
- package/src/tools/jtd-to-json-schema.ts +123 -21
- package/src/tools/read.ts +5 -4
- package/src/tools/shell-tokenize.ts +98 -0
- package/src/web/search/providers/exa.ts +1 -1
package/src/tools/bash.ts
CHANGED
|
@@ -49,7 +49,7 @@ import {
|
|
|
49
49
|
previewWindowRows,
|
|
50
50
|
replaceTabs,
|
|
51
51
|
} from "./render-utils";
|
|
52
|
-
import { tokenizeShellSegments } from "./shell-tokenize";
|
|
52
|
+
import { extractLeadingCdTarget, tokenizeShellSegments } from "./shell-tokenize";
|
|
53
53
|
import { ToolAbortError, ToolError } from "./tool-errors";
|
|
54
54
|
import { toolResult } from "./tool-result";
|
|
55
55
|
import { clampTimeout, TOOL_TIMEOUTS } from "./tool-timeouts";
|
|
@@ -960,17 +960,16 @@ export class BashTool implements AgentTool<typeof bashSchemaBase | typeof bashSc
|
|
|
960
960
|
let command = rawCommand;
|
|
961
961
|
const env = normalizeBashEnv(rawEnv);
|
|
962
962
|
|
|
963
|
-
// Extract leading `cd <path> && ...` into cwd when the model ignores the
|
|
964
|
-
//
|
|
965
|
-
//
|
|
963
|
+
// Extract a leading `cd <path> && ...` into cwd when the model ignores the
|
|
964
|
+
// cwd parameter. The scanner captures only a single path token and defers
|
|
965
|
+
// to the shell for anything else (redirects, extra args, shell expansion),
|
|
966
|
+
// so it never absorbs shell syntax like `cd /tmp 2>/dev/null && ...` into
|
|
967
|
+
// the structured cwd. Constrained to a top-level `&&` on the first line.
|
|
966
968
|
if (!cwd) {
|
|
967
|
-
const
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
if (cdMatch && !/[$`(]/.test(cdMatch[1])) {
|
|
972
|
-
cwd = cdMatch[1].trim().replace(/^["']|["']$/g, "");
|
|
973
|
-
command = command.slice(cdMatch[0].length);
|
|
969
|
+
const cd = extractLeadingCdTarget(command);
|
|
970
|
+
if (cd) {
|
|
971
|
+
cwd = cd.path;
|
|
972
|
+
command = cd.rest;
|
|
974
973
|
}
|
|
975
974
|
}
|
|
976
975
|
if (asyncRequested && !this.#asyncEnabled) {
|
package/src/tools/debug.ts
CHANGED
|
@@ -505,7 +505,7 @@ const ADAPTER_UNAVAILABLE_MESSAGES: Readonly<Record<string, string>> = {
|
|
|
505
505
|
dlv: "adapter 'dlv' is not available: install with 'go install github.com/go-delve/delve/cmd/dlv@latest'",
|
|
506
506
|
rdbg: "adapter 'rdbg' is not available: install with 'gem install debug'",
|
|
507
507
|
"js-debug-adapter":
|
|
508
|
-
"adapter 'js-debug-adapter' is not available:
|
|
508
|
+
"adapter 'js-debug-adapter' is not available: download it from https://github.com/microsoft/vscode-js-debug",
|
|
509
509
|
};
|
|
510
510
|
|
|
511
511
|
const ADAPTER_CANONICAL_COMMANDS: Readonly<Record<string, string>> = {
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* @see https://datatracker.ietf.org/doc/html/rfc8927
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
+
import { isRecord } from "@oh-my-pi/pi-utils";
|
|
11
12
|
import type { JTDPrimitive } from "./jtd-utils.js";
|
|
12
13
|
import {
|
|
13
14
|
isJTDDiscriminator,
|
|
@@ -134,6 +135,18 @@ function convertSchema(schema: unknown): unknown {
|
|
|
134
135
|
return {};
|
|
135
136
|
}
|
|
136
137
|
|
|
138
|
+
const jtdOnlyPrimitiveTypes: Record<string, true> = {
|
|
139
|
+
timestamp: true,
|
|
140
|
+
float32: true,
|
|
141
|
+
float64: true,
|
|
142
|
+
int8: true,
|
|
143
|
+
uint8: true,
|
|
144
|
+
int16: true,
|
|
145
|
+
uint16: true,
|
|
146
|
+
int32: true,
|
|
147
|
+
uint32: true,
|
|
148
|
+
};
|
|
149
|
+
|
|
137
150
|
/**
|
|
138
151
|
* Detect if a schema is JTD format (vs JSON Schema).
|
|
139
152
|
*
|
|
@@ -155,11 +168,8 @@ export function isJTDSchema(schema: unknown): boolean {
|
|
|
155
168
|
if ("ref" in obj) return true;
|
|
156
169
|
|
|
157
170
|
// JTD type primitives (JSON Schema doesn't have int32, float64, etc.)
|
|
158
|
-
if (
|
|
159
|
-
|
|
160
|
-
if (jtdPrimitives.includes(obj.type as string)) {
|
|
161
|
-
return true;
|
|
162
|
-
}
|
|
171
|
+
if (typeof obj.type === "string" && Object.hasOwn(jtdOnlyPrimitiveTypes, obj.type)) {
|
|
172
|
+
return true;
|
|
163
173
|
}
|
|
164
174
|
|
|
165
175
|
// JTD properties form without type: "object" (JSON Schema requires it)
|
|
@@ -170,36 +180,128 @@ export function isJTDSchema(schema: unknown): boolean {
|
|
|
170
180
|
return false;
|
|
171
181
|
}
|
|
172
182
|
|
|
173
|
-
function
|
|
174
|
-
if (schema
|
|
175
|
-
|
|
183
|
+
function isUnambiguousJTDSchema(schema: unknown): boolean {
|
|
184
|
+
if (!isRecord(schema)) return false;
|
|
185
|
+
|
|
186
|
+
if (isRecord(schema.elements) || isRecord(schema.values) || isRecord(schema.optionalProperties)) {
|
|
187
|
+
return true;
|
|
188
|
+
}
|
|
189
|
+
if (typeof schema.ref === "string") return true;
|
|
190
|
+
if (typeof schema.type === "string" && Object.hasOwn(jtdOnlyPrimitiveTypes, schema.type)) {
|
|
191
|
+
return true;
|
|
192
|
+
}
|
|
193
|
+
if (typeof schema.discriminator !== "string" || !isRecord(schema.mapping)) {
|
|
194
|
+
return false;
|
|
176
195
|
}
|
|
177
196
|
|
|
178
|
-
|
|
179
|
-
|
|
197
|
+
for (const key in schema.mapping) {
|
|
198
|
+
if (!Object.hasOwn(schema.mapping, key)) continue;
|
|
199
|
+
const mapping = schema.mapping[key];
|
|
200
|
+
if (!isRecord(mapping)) return false;
|
|
201
|
+
|
|
202
|
+
let hasSchemaProperties = false;
|
|
203
|
+
if (Object.hasOwn(mapping, "properties")) {
|
|
204
|
+
if (!isRecord(mapping.properties)) return false;
|
|
205
|
+
hasSchemaProperties = true;
|
|
206
|
+
}
|
|
207
|
+
if (Object.hasOwn(mapping, "optionalProperties")) {
|
|
208
|
+
if (!isRecord(mapping.optionalProperties)) return false;
|
|
209
|
+
hasSchemaProperties = true;
|
|
210
|
+
}
|
|
211
|
+
if (!hasSchemaProperties) return false;
|
|
180
212
|
}
|
|
181
213
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
214
|
+
return true;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function normalizeJsonSchemaArray(value: unknown): unknown {
|
|
218
|
+
if (!Array.isArray(value)) return value;
|
|
219
|
+
|
|
220
|
+
let normalized: unknown[] | undefined;
|
|
221
|
+
for (let index = 0; index < value.length; index++) {
|
|
222
|
+
const item = value[index];
|
|
223
|
+
const converted = normalizeJsonSchemaNode(item);
|
|
224
|
+
if (converted === item) continue;
|
|
225
|
+
normalized ??= value.slice();
|
|
226
|
+
normalized[index] = converted;
|
|
227
|
+
}
|
|
228
|
+
return normalized ?? value;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function normalizeJsonSchemaMap(value: unknown): unknown {
|
|
232
|
+
if (!isRecord(value)) return value;
|
|
233
|
+
|
|
234
|
+
let normalized: Record<string, unknown> | undefined;
|
|
235
|
+
for (const key in value) {
|
|
236
|
+
if (!Object.hasOwn(value, key)) continue;
|
|
237
|
+
const item = value[key];
|
|
238
|
+
const converted = normalizeJsonSchemaNode(item);
|
|
239
|
+
if (converted === item) continue;
|
|
240
|
+
normalized ??= { ...value };
|
|
241
|
+
normalized[key] = converted;
|
|
188
242
|
}
|
|
243
|
+
return normalized ?? value;
|
|
244
|
+
}
|
|
189
245
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
246
|
+
function normalizeJsonSchemaNode(schema: unknown): unknown {
|
|
247
|
+
if (!isRecord(schema)) return schema;
|
|
248
|
+
if (isUnambiguousJTDSchema(schema)) return convertSchema(schema);
|
|
249
|
+
|
|
250
|
+
let normalized: Record<string, unknown> | undefined;
|
|
251
|
+
for (const key in schema) {
|
|
252
|
+
if (!Object.hasOwn(schema, key)) continue;
|
|
253
|
+
|
|
254
|
+
const value = schema[key];
|
|
255
|
+
let converted: unknown;
|
|
256
|
+
switch (key) {
|
|
257
|
+
case "not":
|
|
258
|
+
case "if":
|
|
259
|
+
case "then":
|
|
260
|
+
case "else":
|
|
261
|
+
case "items":
|
|
262
|
+
case "contains":
|
|
263
|
+
case "propertyNames":
|
|
264
|
+
case "additionalProperties":
|
|
265
|
+
case "unevaluatedProperties":
|
|
266
|
+
case "unevaluatedItems":
|
|
267
|
+
case "contentSchema":
|
|
268
|
+
converted = normalizeJsonSchemaNode(value);
|
|
269
|
+
break;
|
|
270
|
+
case "allOf":
|
|
271
|
+
case "anyOf":
|
|
272
|
+
case "oneOf":
|
|
273
|
+
case "prefixItems":
|
|
274
|
+
converted = normalizeJsonSchemaArray(value);
|
|
275
|
+
break;
|
|
276
|
+
case "properties":
|
|
277
|
+
case "patternProperties":
|
|
278
|
+
case "$defs":
|
|
279
|
+
case "definitions":
|
|
280
|
+
case "dependentSchemas":
|
|
281
|
+
converted = normalizeJsonSchemaMap(value);
|
|
282
|
+
break;
|
|
283
|
+
default:
|
|
284
|
+
continue;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if (converted === value) continue;
|
|
288
|
+
normalized ??= { ...schema };
|
|
289
|
+
normalized[key] = converted;
|
|
193
290
|
}
|
|
194
291
|
|
|
195
|
-
return normalized;
|
|
292
|
+
return normalized ?? schema;
|
|
196
293
|
}
|
|
294
|
+
|
|
197
295
|
/**
|
|
198
296
|
* Convert JTD schema to JSON Schema.
|
|
199
297
|
* If already JSON Schema, returns as-is.
|
|
200
298
|
*/
|
|
201
299
|
export function jtdToJsonSchema(schema: unknown): unknown {
|
|
202
|
-
|
|
300
|
+
if (isJTDSchema(schema)) {
|
|
301
|
+
// convertSchema is recursive; re-walking its JSON Schema output caused #1345.
|
|
302
|
+
return convertSchema(schema);
|
|
303
|
+
}
|
|
304
|
+
return normalizeJsonSchemaNode(schema);
|
|
203
305
|
}
|
|
204
306
|
|
|
205
307
|
/**
|
package/src/tools/read.ts
CHANGED
|
@@ -2497,6 +2497,7 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
|
|
2497
2497
|
const imageMetadata = await readImageMetadata(absolutePath);
|
|
2498
2498
|
const mimeType = imageMetadata?.mimeType;
|
|
2499
2499
|
const ext = path.extname(absolutePath).toLowerCase();
|
|
2500
|
+
const resolvedDisplayPath = formatPathRelativeToCwd(absolutePath, this.session.cwd);
|
|
2500
2501
|
const shouldConvertWithMarkit = CONVERTIBLE_EXTENSIONS.has(ext);
|
|
2501
2502
|
|
|
2502
2503
|
// Profiler reports (macOS `sample` call trees, V8 `.cpuprofile` JSON):
|
|
@@ -2541,7 +2542,7 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
|
|
2541
2542
|
fileSize,
|
|
2542
2543
|
}));
|
|
2543
2544
|
} else if (isNotebookPath(absolutePath) && !isRawSelector(parsed)) {
|
|
2544
|
-
const notebookText = await readEditableNotebookText(absolutePath,
|
|
2545
|
+
const notebookText = await readEditableNotebookText(absolutePath, resolvedDisplayPath);
|
|
2545
2546
|
if (isMultiRange(parsed) && parsed.kind === "lines") {
|
|
2546
2547
|
return this.#buildInMemoryMultiRangeResult(notebookText, parsed.ranges, {
|
|
2547
2548
|
details: { resolvedPath: absolutePath },
|
|
@@ -2560,7 +2561,7 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
|
|
2560
2561
|
const result = await convertFileWithMarkit(absolutePath, signal);
|
|
2561
2562
|
if (result.ok) {
|
|
2562
2563
|
const renderedContent =
|
|
2563
|
-
ext === ".pdf" ? rewritePdfImagePlaceholders(result.content,
|
|
2564
|
+
ext === ".pdf" ? rewritePdfImagePlaceholders(result.content, resolvedDisplayPath) : result.content;
|
|
2564
2565
|
// Route the converted markdown through the in-memory text builder
|
|
2565
2566
|
// so line-range selectors (`file.pdf:50-100`, `:5-16,40-80`) and
|
|
2566
2567
|
// raw mode apply against the converted output. Without this,
|
|
@@ -2603,7 +2604,7 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
|
|
2603
2604
|
return toolResult<ReadToolDetails>({ resolvedPath: absolutePath, suffixResolution })
|
|
2604
2605
|
.text(
|
|
2605
2606
|
prependSuffixResolutionNotice(
|
|
2606
|
-
`[Cannot read binary file '${
|
|
2607
|
+
`[Cannot read binary file '${resolvedDisplayPath}' (${formatBytes(fileSize)}); not valid UTF-8 text. Use ':raw' to read bytes verbatim.]`,
|
|
2607
2608
|
suffixResolution,
|
|
2608
2609
|
),
|
|
2609
2610
|
)
|
|
@@ -2620,7 +2621,7 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
|
|
2620
2621
|
if (summary?.parsed && summary.elided) {
|
|
2621
2622
|
const renderedSummary = this.#renderSummary(summary);
|
|
2622
2623
|
const footer = formatSummaryElisionFooter(
|
|
2623
|
-
|
|
2624
|
+
resolvedDisplayPath,
|
|
2624
2625
|
renderedSummary.elidedRanges,
|
|
2625
2626
|
renderedSummary.elidedLines,
|
|
2626
2627
|
);
|
|
@@ -211,3 +211,101 @@ export function extractFlatShellCommandSegments(command: string): FlatShellComma
|
|
|
211
211
|
pushSegment(command.length);
|
|
212
212
|
return segments;
|
|
213
213
|
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Shell metacharacters that end an unquoted `cd` target token. A redirect,
|
|
217
|
+
* extra argument, or any operator in this set means the leading construct is
|
|
218
|
+
* more than a bare `cd <path>`, so extraction must bail.
|
|
219
|
+
*/
|
|
220
|
+
const CD_TARGET_TERMINATORS: Record<string, true> = {
|
|
221
|
+
" ": true,
|
|
222
|
+
"\t": true,
|
|
223
|
+
"\n": true,
|
|
224
|
+
"\r": true,
|
|
225
|
+
"&": true,
|
|
226
|
+
"|": true,
|
|
227
|
+
";": true,
|
|
228
|
+
"<": true,
|
|
229
|
+
">": true,
|
|
230
|
+
"(": true,
|
|
231
|
+
")": true,
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Parses a leading `cd <path> && ...` prefix so the bash tool can route the
|
|
236
|
+
* target through its structured `cwd` parameter when the model omits it.
|
|
237
|
+
*
|
|
238
|
+
* Returns the single path token (quotes and backslash escapes resolved to their
|
|
239
|
+
* literal value) and the command remainder after the top-level `&&`, or `null`
|
|
240
|
+
* when the command does not begin with exactly `cd`, one path token, and a
|
|
241
|
+
* top-level `&&`. The scanner deliberately bails on anything else in the prefix
|
|
242
|
+
* — redirects (`cd /tmp 2>/dev/null && ...`), extra arguments, or paths needing
|
|
243
|
+
* shell expansion (`$`, backticks, `(`) — leaving the whole command for the
|
|
244
|
+
* shell instead of absorbing shell syntax into `cwd`.
|
|
245
|
+
*/
|
|
246
|
+
export function extractLeadingCdTarget(command: string): { path: string; rest: string } | null {
|
|
247
|
+
const prefix = /^cd[ \t]+/.exec(command);
|
|
248
|
+
if (!prefix) return null;
|
|
249
|
+
let i = prefix[0].length;
|
|
250
|
+
let path = "";
|
|
251
|
+
let inSingle = false;
|
|
252
|
+
let inDouble = false;
|
|
253
|
+
for (; i < command.length; i++) {
|
|
254
|
+
const ch = command[i];
|
|
255
|
+
if (inSingle) {
|
|
256
|
+
if (ch === "'") {
|
|
257
|
+
inSingle = false;
|
|
258
|
+
continue;
|
|
259
|
+
}
|
|
260
|
+
path += ch;
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
263
|
+
if (inDouble) {
|
|
264
|
+
if (ch === "\\" && i + 1 < command.length) {
|
|
265
|
+
const next = command[i + 1];
|
|
266
|
+
// A line continuation crosses the first physical line. Leave it to
|
|
267
|
+
// the shell rather than turning the escaped newline into cwd text.
|
|
268
|
+
if (next === "\n" || next === "\r") return null;
|
|
269
|
+
if (next === '"' || next === "\\" || next === "$" || next === "`") {
|
|
270
|
+
path += next;
|
|
271
|
+
i++;
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
if (ch === '"') {
|
|
276
|
+
inDouble = false;
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
279
|
+
path += ch;
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
if (ch === "'") {
|
|
283
|
+
inSingle = true;
|
|
284
|
+
continue;
|
|
285
|
+
}
|
|
286
|
+
if (ch === '"') {
|
|
287
|
+
inDouble = true;
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
290
|
+
if (ch === "\\" && i + 1 < command.length) {
|
|
291
|
+
// Preserve shell line-continuation semantics by declining extraction.
|
|
292
|
+
if (command[i + 1] === "\n" || command[i + 1] === "\r") return null;
|
|
293
|
+
path += command[i + 1];
|
|
294
|
+
i++;
|
|
295
|
+
continue;
|
|
296
|
+
}
|
|
297
|
+
if (CD_TARGET_TERMINATORS[ch]) break;
|
|
298
|
+
path += ch;
|
|
299
|
+
}
|
|
300
|
+
// Unterminated quote or empty target: leave the command for the shell.
|
|
301
|
+
if (inSingle || inDouble || path.length === 0) return null;
|
|
302
|
+
// A path needing shell expansion can't be resolved literally through cwd.
|
|
303
|
+
if (/[$`(]/.test(path)) return null;
|
|
304
|
+
// Skip inter-token whitespace, then require a top-level `&&` (a single `&`,
|
|
305
|
+
// `||`, `;`, `|`, or a redirect all mean this is not a bare `cd <path>`).
|
|
306
|
+
while (command[i] === " " || command[i] === "\t") i++;
|
|
307
|
+
if (command[i] !== "&" || command[i + 1] !== "&") return null;
|
|
308
|
+
i += 2;
|
|
309
|
+
while (command[i] === " " || command[i] === "\t") i++;
|
|
310
|
+
return { path, rest: command.slice(i) };
|
|
311
|
+
}
|
|
@@ -464,7 +464,7 @@ export class ExaProvider extends SearchProvider {
|
|
|
464
464
|
|
|
465
465
|
#settingsAllowSearch(): boolean {
|
|
466
466
|
try {
|
|
467
|
-
if (settings.get("exa.enabled") === false
|
|
467
|
+
if (settings.get("exa.enabled") === false) {
|
|
468
468
|
return false;
|
|
469
469
|
}
|
|
470
470
|
} catch {
|