@psnext/slingcli 2.4.20260526-1 → 2.4.20260527-2
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/bin/sling.js +4 -1
- package/node_modules/@aws-sdk/core/package.json +2 -2
- package/node_modules/@aws-sdk/credential-provider-env/package.json +2 -2
- package/node_modules/@aws-sdk/credential-provider-http/dist-cjs/fromHttp/fromHttp.js +12 -6
- package/node_modules/@aws-sdk/credential-provider-http/dist-es/fromHttp/fromHttp.js +12 -6
- package/node_modules/@aws-sdk/credential-provider-http/package.json +3 -2
- package/node_modules/@aws-sdk/credential-provider-ini/package.json +9 -9
- package/node_modules/@aws-sdk/credential-provider-login/package.json +3 -3
- package/node_modules/@aws-sdk/credential-provider-node/package.json +7 -7
- package/node_modules/@aws-sdk/credential-provider-process/package.json +2 -2
- package/node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers/package.json +3 -3
- package/node_modules/@aws-sdk/credential-provider-sso/package.json +4 -4
- package/node_modules/@aws-sdk/credential-provider-web-identity/package.json +3 -3
- package/node_modules/@aws-sdk/middleware-websocket/package.json +2 -2
- package/node_modules/@aws-sdk/nested-clients/dist-cjs/submodules/cognito-identity/index.js +1 -1
- package/node_modules/@aws-sdk/nested-clients/dist-cjs/submodules/signin/index.js +1 -1
- package/node_modules/@aws-sdk/nested-clients/dist-cjs/submodules/sso/index.js +1 -1
- package/node_modules/@aws-sdk/nested-clients/dist-cjs/submodules/sso-oidc/index.js +1 -1
- package/node_modules/@aws-sdk/nested-clients/dist-cjs/submodules/sts/index.js +1 -1
- package/node_modules/@aws-sdk/nested-clients/package.json +3 -3
- package/node_modules/@aws-sdk/signature-v4-multi-region/package.json +1 -2
- package/node_modules/@aws-sdk/xml-builder/package.json +2 -2
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/compaction/compaction.js +18 -24
- package/node_modules/@earendil-works/pi-agent-core/package.json +2 -2
- package/node_modules/@earendil-works/pi-ai/dist/cli.js +14 -0
- package/node_modules/@earendil-works/pi-ai/dist/models.generated.js +73 -58
- package/node_modules/@earendil-works/pi-ai/dist/providers/amazon-bedrock.js +2 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/anthropic.js +8 -22
- package/node_modules/@earendil-works/pi-ai/dist/providers/azure-openai-responses.js +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/images/openrouter.js +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/openai-codex-responses.js +60 -40
- package/node_modules/@earendil-works/pi-ai/dist/providers/openai-completions.js +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/openai-responses.js +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/device-code.js +55 -0
- package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/github-copilot.js +45 -69
- package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/index.js +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/utils/overflow.js +5 -2
- package/node_modules/@earendil-works/pi-ai/package.json +4 -3
- package/node_modules/@earendil-works/pi-coding-agent/dist/cli/file-processor.js +2 -3
- package/node_modules/@earendil-works/pi-coding-agent/dist/config.js +18 -12
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/agent-session-runtime.js +2 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/agent-session-services.js +3 -2
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/agent-session.js +8 -2
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/auth-storage.js +2 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/compaction/compaction.js +18 -24
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/export-html/index.js +8 -6
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/export-html/template.js +6 -3
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/extensions/loader.js +12 -29
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/model-registry.js +5 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/output-guard.js +52 -22
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/package-manager.js +63 -36
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/prompt-templates.js +6 -20
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/resource-loader.js +38 -31
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/sdk.js +9 -4
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/session-manager.js +32 -24
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/settings-manager.js +6 -13
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/skills.js +8 -22
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/bash.js +54 -53
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/edit-diff.js +8 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/edit.js +44 -81
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/file-mutation-queue.js +27 -12
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/find.js +2 -3
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/grep.js +3 -3
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/ls.js +5 -5
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/output-accumulator.js +9 -3
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/path-utils.js +39 -21
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/read.js +9 -8
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/truncate.js +12 -2
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/write.js +20 -35
- package/node_modules/@earendil-works/pi-coding-agent/dist/main.js +5 -6
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/config-selector.js +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/footer.js +14 -5
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/login-dialog.js +29 -4
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/user-message.js +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/interactive-mode.js +4 -4
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/theme/theme.js +37 -28
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/rpc/rpc-client.js +64 -7
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/rpc/rpc-mode.js +15 -3
- package/node_modules/@earendil-works/pi-coding-agent/dist/utils/clipboard-native.js +14 -8
- package/node_modules/@earendil-works/pi-coding-agent/dist/utils/image-resize-core.js +124 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/utils/image-resize-worker.js +31 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/utils/image-resize.js +75 -115
- package/node_modules/@earendil-works/pi-coding-agent/dist/utils/paths.js +33 -7
- package/node_modules/@earendil-works/pi-coding-agent/npm-shrinkwrap.json +13 -26
- package/node_modules/@earendil-works/pi-coding-agent/package.json +6 -6
- package/node_modules/@earendil-works/pi-tui/dist/components/editor.js +40 -55
- package/node_modules/@earendil-works/pi-tui/dist/components/input.js +2 -2
- package/node_modules/@earendil-works/pi-tui/dist/components/markdown.js +12 -2
- package/node_modules/@earendil-works/pi-tui/dist/native-modifiers.js +53 -0
- package/node_modules/@earendil-works/pi-tui/dist/terminal-image.js +4 -1
- package/node_modules/@earendil-works/pi-tui/dist/terminal.js +38 -16
- package/node_modules/@earendil-works/pi-tui/dist/utils.js +19 -12
- package/node_modules/@earendil-works/pi-tui/native/darwin/prebuilds/darwin-arm64/darwin-modifiers.node +0 -0
- package/node_modules/@earendil-works/pi-tui/native/darwin/prebuilds/darwin-x64/darwin-modifiers.node +0 -0
- package/node_modules/@earendil-works/pi-tui/native/win32/prebuilds/win32-arm64/win32-console-mode.node +0 -0
- package/node_modules/@earendil-works/pi-tui/native/win32/prebuilds/win32-x64/win32-console-mode.node +0 -0
- package/node_modules/@earendil-works/pi-tui/package.json +3 -5
- package/node_modules/@smithy/node-http-handler/package.json +2 -2
- package/package.json +6 -6
- package/slingshot/index.js +246 -244
- package/node_modules/koffi/CHANGELOG.md +0 -1093
- package/node_modules/koffi/LICENSE.txt +0 -22
- package/node_modules/koffi/README.md +0 -43
- package/node_modules/koffi/build/koffi/darwin_arm64/koffi.node +0 -0
- package/node_modules/koffi/build/koffi/darwin_x64/koffi.node +0 -0
- package/node_modules/koffi/build/koffi/freebsd_arm64/koffi.node +0 -0
- package/node_modules/koffi/build/koffi/freebsd_ia32/koffi.node +0 -0
- package/node_modules/koffi/build/koffi/freebsd_x64/koffi.node +0 -0
- package/node_modules/koffi/build/koffi/linux_arm64/koffi.node +0 -0
- package/node_modules/koffi/build/koffi/linux_armhf/koffi.node +0 -0
- package/node_modules/koffi/build/koffi/linux_ia32/koffi.node +0 -0
- package/node_modules/koffi/build/koffi/linux_loong64/koffi.node +0 -0
- package/node_modules/koffi/build/koffi/linux_riscv64d/koffi.node +0 -0
- package/node_modules/koffi/build/koffi/linux_x64/koffi.node +0 -0
- package/node_modules/koffi/build/koffi/musl_arm64/koffi.node +0 -0
- package/node_modules/koffi/build/koffi/musl_x64/koffi.node +0 -0
- package/node_modules/koffi/build/koffi/openbsd_ia32/koffi.node +0 -0
- package/node_modules/koffi/build/koffi/openbsd_x64/koffi.node +0 -0
- package/node_modules/koffi/build/koffi/win32_arm64/koffi.exp +0 -0
- package/node_modules/koffi/build/koffi/win32_arm64/koffi.lib +0 -0
- package/node_modules/koffi/build/koffi/win32_arm64/koffi.node +0 -0
- package/node_modules/koffi/build/koffi/win32_ia32/koffi.exp +0 -0
- package/node_modules/koffi/build/koffi/win32_ia32/koffi.lib +0 -0
- package/node_modules/koffi/build/koffi/win32_ia32/koffi.node +0 -0
- package/node_modules/koffi/build/koffi/win32_x64/koffi.exp +0 -0
- package/node_modules/koffi/build/koffi/win32_x64/koffi.lib +0 -0
- package/node_modules/koffi/build/koffi/win32_x64/koffi.node +0 -0
- package/node_modules/koffi/doc/benchmarks.md +0 -126
- package/node_modules/koffi/doc/callbacks.md +0 -210
- package/node_modules/koffi/doc/contribute.md +0 -148
- package/node_modules/koffi/doc/functions.md +0 -250
- package/node_modules/koffi/doc/index.md +0 -61
- package/node_modules/koffi/doc/input.md +0 -471
- package/node_modules/koffi/doc/migration.md +0 -159
- package/node_modules/koffi/doc/misc.md +0 -180
- package/node_modules/koffi/doc/output.md +0 -305
- package/node_modules/koffi/doc/packaging.md +0 -88
- package/node_modules/koffi/doc/platforms.md +0 -36
- package/node_modules/koffi/doc/pointers.md +0 -328
- package/node_modules/koffi/doc/start.md +0 -118
- package/node_modules/koffi/doc/unions.md +0 -186
- package/node_modules/koffi/doc/variables.md +0 -102
- package/node_modules/koffi/index.d.ts +0 -288
- package/node_modules/koffi/index.js +0 -634
- package/node_modules/koffi/indirect.js +0 -533
- package/node_modules/koffi/lib/native/base/base.cc +0 -11015
- package/node_modules/koffi/lib/native/base/base.hh +0 -6003
- package/node_modules/koffi/lib/native/base/crc.inc +0 -2214
- package/node_modules/koffi/lib/native/base/crc_gen.py +0 -72
- package/node_modules/koffi/lib/native/base/mimetypes.inc +0 -1248
- package/node_modules/koffi/lib/native/base/mimetypes_gen.py +0 -58
- package/node_modules/koffi/lib/native/base/tower.cc +0 -821
- package/node_modules/koffi/lib/native/base/tower.hh +0 -81
- package/node_modules/koffi/lib/native/base/unicode.inc +0 -408
- package/node_modules/koffi/lib/native/base/unicode_gen.py +0 -152
- package/node_modules/koffi/package.json +0 -38
- package/node_modules/koffi/src/cnoke/LICENSE.txt +0 -22
- package/node_modules/koffi/src/cnoke/README.md +0 -99
- package/node_modules/koffi/src/cnoke/assets/FindCNoke.cmake +0 -127
- package/node_modules/koffi/src/cnoke/assets/toolchains.json +0 -126
- package/node_modules/koffi/src/cnoke/assets/win_delay_hook.c +0 -36
- package/node_modules/koffi/src/cnoke/cnoke.js +0 -170
- package/node_modules/koffi/src/cnoke/package.json +0 -24
- package/node_modules/koffi/src/cnoke/src/builder.js +0 -511
- package/node_modules/koffi/src/cnoke/src/index.js +0 -10
- package/node_modules/koffi/src/cnoke/src/tools.js +0 -407
- package/node_modules/koffi/src/koffi/CMakeLists.txt +0 -182
- package/node_modules/koffi/src/koffi/src/abi_arm32.cc +0 -1018
- package/node_modules/koffi/src/koffi/src/abi_arm32_asm.S +0 -169
- package/node_modules/koffi/src/koffi/src/abi_arm64.cc +0 -1295
- package/node_modules/koffi/src/koffi/src/abi_arm64_asm.S +0 -195
- package/node_modules/koffi/src/koffi/src/abi_arm64_asm.asm +0 -174
- package/node_modules/koffi/src/koffi/src/abi_loong64.cc +0 -5
- package/node_modules/koffi/src/koffi/src/abi_loong64_asm.S +0 -204
- package/node_modules/koffi/src/koffi/src/abi_riscv64.cc +0 -915
- package/node_modules/koffi/src/koffi/src/abi_riscv64_asm.S +0 -203
- package/node_modules/koffi/src/koffi/src/abi_x64_sysv.cc +0 -939
- package/node_modules/koffi/src/koffi/src/abi_x64_sysv_asm.S +0 -231
- package/node_modules/koffi/src/koffi/src/abi_x64_win.cc +0 -715
- package/node_modules/koffi/src/koffi/src/abi_x64_win_asm.S +0 -166
- package/node_modules/koffi/src/koffi/src/abi_x64_win_asm.asm +0 -192
- package/node_modules/koffi/src/koffi/src/abi_x86.cc +0 -860
- package/node_modules/koffi/src/koffi/src/abi_x86_asm.S +0 -193
- package/node_modules/koffi/src/koffi/src/abi_x86_asm.asm +0 -177
- package/node_modules/koffi/src/koffi/src/call.cc +0 -1326
- package/node_modules/koffi/src/koffi/src/call.hh +0 -179
- package/node_modules/koffi/src/koffi/src/errno.inc +0 -462
- package/node_modules/koffi/src/koffi/src/ffi.cc +0 -2702
- package/node_modules/koffi/src/koffi/src/ffi.hh +0 -354
- package/node_modules/koffi/src/koffi/src/init.js +0 -105
- package/node_modules/koffi/src/koffi/src/parser.cc +0 -220
- package/node_modules/koffi/src/koffi/src/parser.hh +0 -54
- package/node_modules/koffi/src/koffi/src/util.cc +0 -1807
- package/node_modules/koffi/src/koffi/src/util.hh +0 -221
- package/node_modules/koffi/src/koffi/src/uv.cc +0 -193
- package/node_modules/koffi/src/koffi/src/uv.def +0 -10
- package/node_modules/koffi/src/koffi/src/uv.hh +0 -40
- package/node_modules/koffi/src/koffi/src/win32.cc +0 -198
- package/node_modules/koffi/src/koffi/src/win32.hh +0 -119
- package/node_modules/koffi/src/koffi/tools/write_trampolines.js +0 -77
- package/node_modules/koffi/vendor/node-addon-api/LICENSE.md +0 -9
- package/node_modules/koffi/vendor/node-addon-api/README.md +0 -95
- package/node_modules/koffi/vendor/node-addon-api/napi-inl.deprecated.h +0 -186
- package/node_modules/koffi/vendor/node-addon-api/napi-inl.h +0 -7033
- package/node_modules/koffi/vendor/node-addon-api/napi.h +0 -3309
- package/node_modules/koffi/vendor/node-api-headers/LICENSE +0 -21
- package/node_modules/koffi/vendor/node-api-headers/README.md +0 -95
- package/node_modules/koffi/vendor/node-api-headers/def/js_native_api.def +0 -125
- package/node_modules/koffi/vendor/node-api-headers/def/node_api.def +0 -157
- package/node_modules/koffi/vendor/node-api-headers/include/js_native_api.h +0 -591
- package/node_modules/koffi/vendor/node-api-headers/include/js_native_api_types.h +0 -210
- package/node_modules/koffi/vendor/node-api-headers/include/node_api.h +0 -265
- package/node_modules/koffi/vendor/node-api-headers/include/node_api_types.h +0 -58
- package/node_modules/koffi/vendor/node-api-headers/include/uv/aix.h +0 -32
- package/node_modules/koffi/vendor/node-api-headers/include/uv/bsd.h +0 -34
- package/node_modules/koffi/vendor/node-api-headers/include/uv/darwin.h +0 -61
- package/node_modules/koffi/vendor/node-api-headers/include/uv/errno.h +0 -483
- package/node_modules/koffi/vendor/node-api-headers/include/uv/linux.h +0 -34
- package/node_modules/koffi/vendor/node-api-headers/include/uv/os390.h +0 -33
- package/node_modules/koffi/vendor/node-api-headers/include/uv/posix.h +0 -31
- package/node_modules/koffi/vendor/node-api-headers/include/uv/sunos.h +0 -44
- package/node_modules/koffi/vendor/node-api-headers/include/uv/threadpool.h +0 -37
- package/node_modules/koffi/vendor/node-api-headers/include/uv/tree.h +0 -521
- package/node_modules/koffi/vendor/node-api-headers/include/uv/unix.h +0 -512
- package/node_modules/koffi/vendor/node-api-headers/include/uv/version.h +0 -43
- package/node_modules/koffi/vendor/node-api-headers/include/uv/win.h +0 -698
- package/node_modules/koffi/vendor/node-api-headers/include/uv.h +0 -1990
|
@@ -3,7 +3,7 @@ import { constants } from "fs";
|
|
|
3
3
|
import { access as fsAccess, readFile as fsReadFile, writeFile as fsWriteFile } from "fs/promises";
|
|
4
4
|
import { Type } from "typebox";
|
|
5
5
|
import { renderDiff } from "../../modes/interactive/components/diff.js";
|
|
6
|
-
import { applyEditsToNormalizedContent, computeEditsDiff, detectLineEnding, generateDiffString, normalizeToLF, restoreLineEndings, stripBom, } from "./edit-diff.js";
|
|
6
|
+
import { applyEditsToNormalizedContent, computeEditsDiff, detectLineEnding, generateDiffString, generateUnifiedPatch, normalizeToLF, restoreLineEndings, stripBom, } from "./edit-diff.js";
|
|
7
7
|
import { withFileMutationQueue } from "./file-mutation-queue.js";
|
|
8
8
|
import { resolveToCwd } from "./path-utils.js";
|
|
9
9
|
import { invalidArgText, shortenPath, str } from "./render-utils.js";
|
|
@@ -177,88 +177,51 @@ export function createEditToolDefinition(cwd, options) {
|
|
|
177
177
|
async execute(_toolCallId, input, signal, _onUpdate, _ctx) {
|
|
178
178
|
const { path, edits } = validateEditInput(input);
|
|
179
179
|
const absolutePath = resolveToCwd(path, cwd);
|
|
180
|
-
return withFileMutationQueue(absolutePath,
|
|
181
|
-
//
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
const onAbort = () => {
|
|
189
|
-
aborted = true;
|
|
190
|
-
reject(new Error("Operation aborted"));
|
|
180
|
+
return withFileMutationQueue(absolutePath, async () => {
|
|
181
|
+
// Do not reject from an abort event listener here: that would release the
|
|
182
|
+
// mutation queue while an in-flight filesystem operation may still finish.
|
|
183
|
+
// Checking signal.aborted after each await observes the same aborts while
|
|
184
|
+
// keeping the queue locked until the current operation has settled.
|
|
185
|
+
const throwIfAborted = () => {
|
|
186
|
+
if (signal?.aborted)
|
|
187
|
+
throw new Error("Operation aborted");
|
|
191
188
|
};
|
|
192
|
-
|
|
193
|
-
|
|
189
|
+
throwIfAborted();
|
|
190
|
+
// Check if file exists.
|
|
191
|
+
try {
|
|
192
|
+
await ops.access(absolutePath);
|
|
194
193
|
}
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
// Check if aborted before writing.
|
|
227
|
-
if (aborted) {
|
|
228
|
-
return;
|
|
229
|
-
}
|
|
230
|
-
const finalContent = bom + restoreLineEndings(newContent, originalEnding);
|
|
231
|
-
await ops.writeFile(absolutePath, finalContent);
|
|
232
|
-
// Check if aborted after writing.
|
|
233
|
-
if (aborted) {
|
|
234
|
-
return;
|
|
235
|
-
}
|
|
236
|
-
// Clean up abort handler.
|
|
237
|
-
if (signal) {
|
|
238
|
-
signal.removeEventListener("abort", onAbort);
|
|
239
|
-
}
|
|
240
|
-
const diffResult = generateDiffString(baseContent, newContent);
|
|
241
|
-
resolve({
|
|
242
|
-
content: [
|
|
243
|
-
{
|
|
244
|
-
type: "text",
|
|
245
|
-
text: `Successfully replaced ${edits.length} block(s) in ${path}.`,
|
|
246
|
-
},
|
|
247
|
-
],
|
|
248
|
-
details: { diff: diffResult.diff, firstChangedLine: diffResult.firstChangedLine },
|
|
249
|
-
});
|
|
250
|
-
}
|
|
251
|
-
catch (error) {
|
|
252
|
-
// Clean up abort handler.
|
|
253
|
-
if (signal) {
|
|
254
|
-
signal.removeEventListener("abort", onAbort);
|
|
255
|
-
}
|
|
256
|
-
if (!aborted) {
|
|
257
|
-
reject(error instanceof Error ? error : new Error(String(error)));
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
})();
|
|
261
|
-
}));
|
|
194
|
+
catch (error) {
|
|
195
|
+
throwIfAborted();
|
|
196
|
+
const errorMessage = error instanceof Error && "code" in error ? `Error code: ${error.code}` : String(error);
|
|
197
|
+
throw new Error(`Could not edit file: ${path}. ${errorMessage}.`);
|
|
198
|
+
}
|
|
199
|
+
throwIfAborted();
|
|
200
|
+
// Read the file.
|
|
201
|
+
const buffer = await ops.readFile(absolutePath);
|
|
202
|
+
const rawContent = buffer.toString("utf-8");
|
|
203
|
+
throwIfAborted();
|
|
204
|
+
// Strip BOM before matching. The model will not include an invisible BOM in oldText.
|
|
205
|
+
const { bom, text: content } = stripBom(rawContent);
|
|
206
|
+
const originalEnding = detectLineEnding(content);
|
|
207
|
+
const normalizedContent = normalizeToLF(content);
|
|
208
|
+
const { baseContent, newContent } = applyEditsToNormalizedContent(normalizedContent, edits, path);
|
|
209
|
+
throwIfAborted();
|
|
210
|
+
const finalContent = bom + restoreLineEndings(newContent, originalEnding);
|
|
211
|
+
await ops.writeFile(absolutePath, finalContent);
|
|
212
|
+
throwIfAborted();
|
|
213
|
+
const diffResult = generateDiffString(baseContent, newContent);
|
|
214
|
+
const patch = generateUnifiedPatch(path, baseContent, newContent);
|
|
215
|
+
return {
|
|
216
|
+
content: [
|
|
217
|
+
{
|
|
218
|
+
type: "text",
|
|
219
|
+
text: `Successfully replaced ${edits.length} block(s) in ${path}.`,
|
|
220
|
+
},
|
|
221
|
+
],
|
|
222
|
+
details: { diff: diffResult.diff, patch, firstChangedLine: diffResult.firstChangedLine },
|
|
223
|
+
};
|
|
224
|
+
});
|
|
262
225
|
},
|
|
263
226
|
renderCall(args, theme, context) {
|
|
264
227
|
const component = getEditCallRenderComponent(context.state, context.lastComponent);
|
|
@@ -1,13 +1,23 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { realpath } from "node:fs/promises";
|
|
2
2
|
import { resolve } from "node:path";
|
|
3
3
|
const fileMutationQueues = new Map();
|
|
4
|
-
|
|
4
|
+
let registrationQueue = Promise.resolve();
|
|
5
|
+
function isMissingPathError(error) {
|
|
6
|
+
return (typeof error === "object" &&
|
|
7
|
+
error !== null &&
|
|
8
|
+
"code" in error &&
|
|
9
|
+
(error.code === "ENOENT" || error.code === "ENOTDIR"));
|
|
10
|
+
}
|
|
11
|
+
async function getMutationQueueKey(filePath) {
|
|
5
12
|
const resolvedPath = resolve(filePath);
|
|
6
13
|
try {
|
|
7
|
-
return
|
|
14
|
+
return await realpath(resolvedPath);
|
|
8
15
|
}
|
|
9
|
-
catch {
|
|
10
|
-
|
|
16
|
+
catch (error) {
|
|
17
|
+
if (isMissingPathError(error)) {
|
|
18
|
+
return resolvedPath;
|
|
19
|
+
}
|
|
20
|
+
throw error;
|
|
11
21
|
}
|
|
12
22
|
}
|
|
13
23
|
/**
|
|
@@ -15,14 +25,19 @@ function getMutationQueueKey(filePath) {
|
|
|
15
25
|
* Operations for different files still run in parallel.
|
|
16
26
|
*/
|
|
17
27
|
export async function withFileMutationQueue(filePath, fn) {
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
28
|
+
const registration = registrationQueue.then(async () => {
|
|
29
|
+
const key = await getMutationQueueKey(filePath);
|
|
30
|
+
const currentQueue = fileMutationQueues.get(key) ?? Promise.resolve();
|
|
31
|
+
let releaseNext;
|
|
32
|
+
const nextQueue = new Promise((resolveQueue) => {
|
|
33
|
+
releaseNext = resolveQueue;
|
|
34
|
+
});
|
|
35
|
+
const chainedQueue = currentQueue.then(() => nextQueue);
|
|
36
|
+
fileMutationQueues.set(key, chainedQueue);
|
|
37
|
+
return { key, currentQueue, chainedQueue, releaseNext };
|
|
23
38
|
});
|
|
24
|
-
|
|
25
|
-
|
|
39
|
+
registrationQueue = registration.then(() => undefined, () => undefined);
|
|
40
|
+
const { key, currentQueue, chainedQueue, releaseNext } = await registration;
|
|
26
41
|
await currentQueue;
|
|
27
42
|
try {
|
|
28
43
|
return await fn();
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import { createInterface } from "node:readline";
|
|
2
2
|
import { Text } from "@earendil-works/pi-tui";
|
|
3
3
|
import { spawn } from "child_process";
|
|
4
|
-
import { existsSync } from "fs";
|
|
5
4
|
import path from "path";
|
|
6
5
|
import { Type } from "typebox";
|
|
7
6
|
import { keyHint } from "../../modes/interactive/components/keybinding-hints.js";
|
|
8
7
|
import { ensureTool } from "../../utils/tools-manager.js";
|
|
9
|
-
import { resolveToCwd } from "./path-utils.js";
|
|
8
|
+
import { pathExists, resolveToCwd } from "./path-utils.js";
|
|
10
9
|
import { getTextOutput, invalidArgText, shortenPath, str } from "./render-utils.js";
|
|
11
10
|
import { wrapToolDefinition } from "./tool-definition-wrapper.js";
|
|
12
11
|
import { DEFAULT_MAX_BYTES, formatSize, truncateHead } from "./truncate.js";
|
|
@@ -22,7 +21,7 @@ const findSchema = Type.Object({
|
|
|
22
21
|
});
|
|
23
22
|
const DEFAULT_LIMIT = 1000;
|
|
24
23
|
const defaultFindOperations = {
|
|
25
|
-
exists:
|
|
24
|
+
exists: pathExists,
|
|
26
25
|
// This is a placeholder. Actual fd execution happens in execute() when no custom glob is provided.
|
|
27
26
|
glob: () => [],
|
|
28
27
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
import { readFile as fsReadFile, stat as fsStat } from "node:fs/promises";
|
|
1
2
|
import { createInterface } from "node:readline";
|
|
2
3
|
import { Text } from "@earendil-works/pi-tui";
|
|
3
4
|
import { spawn } from "child_process";
|
|
4
|
-
import { readFileSync, statSync } from "fs";
|
|
5
5
|
import path from "path";
|
|
6
6
|
import { Type } from "typebox";
|
|
7
7
|
import { keyHint } from "../../modes/interactive/components/keybinding-hints.js";
|
|
@@ -21,8 +21,8 @@ const grepSchema = Type.Object({
|
|
|
21
21
|
});
|
|
22
22
|
const DEFAULT_LIMIT = 100;
|
|
23
23
|
const defaultGrepOperations = {
|
|
24
|
-
isDirectory: (p) =>
|
|
25
|
-
readFile: (p) =>
|
|
24
|
+
isDirectory: async (p) => (await fsStat(p)).isDirectory(),
|
|
25
|
+
readFile: (p) => fsReadFile(p, "utf-8"),
|
|
26
26
|
};
|
|
27
27
|
function formatGrepCall(args, theme) {
|
|
28
28
|
const pattern = str(args?.pattern);
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
+
import { readdir as fsReaddir, stat as fsStat } from "node:fs/promises";
|
|
1
2
|
import { Text } from "@earendil-works/pi-tui";
|
|
2
|
-
import { existsSync, readdirSync, statSync } from "fs";
|
|
3
3
|
import nodePath from "path";
|
|
4
4
|
import { Type } from "typebox";
|
|
5
5
|
import { keyHint } from "../../modes/interactive/components/keybinding-hints.js";
|
|
6
|
-
import { resolveToCwd } from "./path-utils.js";
|
|
6
|
+
import { pathExists, resolveToCwd } from "./path-utils.js";
|
|
7
7
|
import { getTextOutput, invalidArgText, shortenPath, str } from "./render-utils.js";
|
|
8
8
|
import { wrapToolDefinition } from "./tool-definition-wrapper.js";
|
|
9
9
|
import { DEFAULT_MAX_BYTES, formatSize, truncateHead } from "./truncate.js";
|
|
@@ -13,9 +13,9 @@ const lsSchema = Type.Object({
|
|
|
13
13
|
});
|
|
14
14
|
const DEFAULT_LIMIT = 500;
|
|
15
15
|
const defaultLsOperations = {
|
|
16
|
-
exists:
|
|
17
|
-
stat:
|
|
18
|
-
readdir:
|
|
16
|
+
exists: pathExists,
|
|
17
|
+
stat: fsStat,
|
|
18
|
+
readdir: fsReaddir,
|
|
19
19
|
};
|
|
20
20
|
function formatLsCall(args, theme) {
|
|
21
21
|
const rawPath = str(args?.path);
|
|
@@ -29,8 +29,10 @@ export class OutputAccumulator {
|
|
|
29
29
|
tailStartsAtLineBoundary = true;
|
|
30
30
|
totalRawBytes = 0;
|
|
31
31
|
totalDecodedBytes = 0;
|
|
32
|
-
|
|
32
|
+
completedLines = 0;
|
|
33
|
+
totalLines = 0;
|
|
33
34
|
currentLineBytes = 0;
|
|
35
|
+
hasOpenLine = false;
|
|
34
36
|
finished = false;
|
|
35
37
|
tempFilePath;
|
|
36
38
|
tempFileStream;
|
|
@@ -133,11 +135,15 @@ export class OutputAccumulator {
|
|
|
133
135
|
}
|
|
134
136
|
if (newlines === 0) {
|
|
135
137
|
this.currentLineBytes += bytes;
|
|
138
|
+
this.hasOpenLine = true;
|
|
136
139
|
}
|
|
137
140
|
else {
|
|
138
|
-
this.
|
|
139
|
-
|
|
141
|
+
this.completedLines += newlines;
|
|
142
|
+
const tail = text.slice(lastNewline + 1);
|
|
143
|
+
this.currentLineBytes = byteLength(tail);
|
|
144
|
+
this.hasOpenLine = tail.length > 0;
|
|
140
145
|
}
|
|
146
|
+
this.totalLines = this.completedLines + (this.hasOpenLine ? 1 : 0);
|
|
141
147
|
}
|
|
142
148
|
trimTail() {
|
|
143
149
|
const buffer = Buffer.from(this.tailText, "utf-8");
|
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
import { accessSync, constants } from "node:fs";
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
const UNICODE_SPACES = /[\u00A0\u2000-\u200A\u202F\u205F\u3000]/g;
|
|
2
|
+
import { access } from "node:fs/promises";
|
|
3
|
+
import { normalizePath, resolvePath } from "../../utils/paths.js";
|
|
5
4
|
const NARROW_NO_BREAK_SPACE = "\u202F";
|
|
6
|
-
function normalizeUnicodeSpaces(str) {
|
|
7
|
-
return str.replace(UNICODE_SPACES, " ");
|
|
8
|
-
}
|
|
9
5
|
function tryMacOSScreenshotPath(filePath) {
|
|
10
6
|
return filePath.replace(/ (AM|PM)\./gi, `${NARROW_NO_BREAK_SPACE}$1.`);
|
|
11
7
|
}
|
|
@@ -27,29 +23,24 @@ function fileExists(filePath) {
|
|
|
27
23
|
return false;
|
|
28
24
|
}
|
|
29
25
|
}
|
|
30
|
-
function
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
const normalized = normalizeUnicodeSpaces(normalizeAtPrefix(filePath));
|
|
35
|
-
if (normalized === "~") {
|
|
36
|
-
return os.homedir();
|
|
26
|
+
export async function pathExists(filePath) {
|
|
27
|
+
try {
|
|
28
|
+
await access(filePath, constants.F_OK);
|
|
29
|
+
return true;
|
|
37
30
|
}
|
|
38
|
-
|
|
39
|
-
return
|
|
31
|
+
catch {
|
|
32
|
+
return false;
|
|
40
33
|
}
|
|
41
|
-
|
|
34
|
+
}
|
|
35
|
+
export function expandPath(filePath) {
|
|
36
|
+
return normalizePath(filePath, { normalizeUnicodeSpaces: true, stripAtPrefix: true });
|
|
42
37
|
}
|
|
43
38
|
/**
|
|
44
39
|
* Resolve a path relative to the given cwd.
|
|
45
40
|
* Handles ~ expansion and absolute paths.
|
|
46
41
|
*/
|
|
47
42
|
export function resolveToCwd(filePath, cwd) {
|
|
48
|
-
|
|
49
|
-
if (isAbsolute(expanded)) {
|
|
50
|
-
return expanded;
|
|
51
|
-
}
|
|
52
|
-
return resolvePath(cwd, expanded);
|
|
43
|
+
return resolvePath(filePath, cwd, { normalizeUnicodeSpaces: true, stripAtPrefix: true });
|
|
53
44
|
}
|
|
54
45
|
export function resolveReadPath(filePath, cwd) {
|
|
55
46
|
const resolved = resolveToCwd(filePath, cwd);
|
|
@@ -78,4 +69,31 @@ export function resolveReadPath(filePath, cwd) {
|
|
|
78
69
|
}
|
|
79
70
|
return resolved;
|
|
80
71
|
}
|
|
72
|
+
export async function resolveReadPathAsync(filePath, cwd) {
|
|
73
|
+
const resolved = resolveToCwd(filePath, cwd);
|
|
74
|
+
if (await pathExists(resolved)) {
|
|
75
|
+
return resolved;
|
|
76
|
+
}
|
|
77
|
+
// Try macOS AM/PM variant (narrow no-break space before AM/PM)
|
|
78
|
+
const amPmVariant = tryMacOSScreenshotPath(resolved);
|
|
79
|
+
if (amPmVariant !== resolved && (await pathExists(amPmVariant))) {
|
|
80
|
+
return amPmVariant;
|
|
81
|
+
}
|
|
82
|
+
// Try NFD variant (macOS stores filenames in NFD form)
|
|
83
|
+
const nfdVariant = tryNFDVariant(resolved);
|
|
84
|
+
if (nfdVariant !== resolved && (await pathExists(nfdVariant))) {
|
|
85
|
+
return nfdVariant;
|
|
86
|
+
}
|
|
87
|
+
// Try curly quote variant (macOS uses U+2019 in screenshot names)
|
|
88
|
+
const curlyVariant = tryCurlyQuoteVariant(resolved);
|
|
89
|
+
if (curlyVariant !== resolved && (await pathExists(curlyVariant))) {
|
|
90
|
+
return curlyVariant;
|
|
91
|
+
}
|
|
92
|
+
// Try combined NFD + curly quote (for French macOS screenshots like "Capture d'écran")
|
|
93
|
+
const nfdCurlyVariant = tryCurlyQuoteVariant(nfdVariant);
|
|
94
|
+
if (nfdCurlyVariant !== resolved && (await pathExists(nfdCurlyVariant))) {
|
|
95
|
+
return nfdCurlyVariant;
|
|
96
|
+
}
|
|
97
|
+
return resolved;
|
|
98
|
+
}
|
|
81
99
|
//# sourceMappingURL=path-utils.js.map
|
|
@@ -9,7 +9,7 @@ import { getLanguageFromPath, highlightCode } from "../../modes/interactive/them
|
|
|
9
9
|
import { formatDimensionNote, resizeImage } from "../../utils/image-resize.js";
|
|
10
10
|
import { detectSupportedImageMimeTypeFromFile } from "../../utils/mime.js";
|
|
11
11
|
import { formatPathRelativeToCwdOrAbsolute } from "../../utils/paths.js";
|
|
12
|
-
import {
|
|
12
|
+
import { resolveReadPathAsync, resolveToCwd } from "./path-utils.js";
|
|
13
13
|
import { getTextOutput, invalidArgText, replaceTabs, shortenPath, str } from "./render-utils.js";
|
|
14
14
|
import { wrapToolDefinition } from "./tool-definition-wrapper.js";
|
|
15
15
|
import { DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES, formatSize, truncateHead } from "./truncate.js";
|
|
@@ -73,7 +73,7 @@ function getCompactReadClassification(args, cwd) {
|
|
|
73
73
|
const rawPath = str(args?.file_path ?? args?.path);
|
|
74
74
|
if (!rawPath)
|
|
75
75
|
return undefined;
|
|
76
|
-
const absolutePath =
|
|
76
|
+
const absolutePath = resolveToCwd(rawPath, cwd);
|
|
77
77
|
const fileName = basename(absolutePath);
|
|
78
78
|
if (fileName === "SKILL.md") {
|
|
79
79
|
return { kind: "skill", label: basename(dirname(absolutePath)) || fileName };
|
|
@@ -100,8 +100,8 @@ function formatCompactReadCall(classification, args, theme) {
|
|
|
100
100
|
formatReadLineRange(args, theme) +
|
|
101
101
|
expandHint);
|
|
102
102
|
}
|
|
103
|
-
function formatReadResult(args, result, options, theme, showImages,
|
|
104
|
-
if (!options.expanded && !isError
|
|
103
|
+
function formatReadResult(args, result, options, theme, showImages, _cwd, isError) {
|
|
104
|
+
if (!options.expanded && !isError) {
|
|
105
105
|
return "";
|
|
106
106
|
}
|
|
107
107
|
const rawPath = str(args?.file_path ?? args?.path);
|
|
@@ -141,7 +141,6 @@ export function createReadToolDefinition(cwd, options) {
|
|
|
141
141
|
promptGuidelines: ["Use read to examine files instead of cat or sed."],
|
|
142
142
|
parameters: readSchema,
|
|
143
143
|
async execute(_toolCallId, { path, offset, limit }, signal, _onUpdate, ctx) {
|
|
144
|
-
const absolutePath = resolveReadPath(path, cwd);
|
|
145
144
|
return new Promise((resolve, reject) => {
|
|
146
145
|
if (signal?.aborted) {
|
|
147
146
|
reject(new Error("Operation aborted"));
|
|
@@ -155,6 +154,9 @@ export function createReadToolDefinition(cwd, options) {
|
|
|
155
154
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
156
155
|
(async () => {
|
|
157
156
|
try {
|
|
157
|
+
const absolutePath = await resolveReadPathAsync(path, cwd);
|
|
158
|
+
if (aborted)
|
|
159
|
+
return;
|
|
158
160
|
// Check if file exists and is readable.
|
|
159
161
|
await ops.access(absolutePath);
|
|
160
162
|
if (aborted)
|
|
@@ -166,10 +168,9 @@ export function createReadToolDefinition(cwd, options) {
|
|
|
166
168
|
if (mimeType) {
|
|
167
169
|
// Read image as binary.
|
|
168
170
|
const buffer = await ops.readFile(absolutePath);
|
|
169
|
-
const base64 = buffer.toString("base64");
|
|
170
171
|
if (autoResizeImages) {
|
|
171
172
|
// Resize image if needed before sending it back to the model.
|
|
172
|
-
const resized = await resizeImage(
|
|
173
|
+
const resized = await resizeImage(buffer, mimeType);
|
|
173
174
|
if (!resized) {
|
|
174
175
|
let textNote = `Read image file [${mimeType}]\n[Image omitted: could not be resized below the inline image size limit.]`;
|
|
175
176
|
if (nonVisionImageNote)
|
|
@@ -195,7 +196,7 @@ export function createReadToolDefinition(cwd, options) {
|
|
|
195
196
|
textNote += `\n${nonVisionImageNote}`;
|
|
196
197
|
content = [
|
|
197
198
|
{ type: "text", text: textNote },
|
|
198
|
-
{ type: "image", data: base64, mimeType },
|
|
199
|
+
{ type: "image", data: buffer.toString("base64"), mimeType },
|
|
199
200
|
];
|
|
200
201
|
}
|
|
201
202
|
}
|
|
@@ -10,6 +10,16 @@
|
|
|
10
10
|
export const DEFAULT_MAX_LINES = 2000;
|
|
11
11
|
export const DEFAULT_MAX_BYTES = 50 * 1024; // 50KB
|
|
12
12
|
export const GREP_MAX_LINE_LENGTH = 500; // Max chars per grep match line
|
|
13
|
+
function splitLinesForCounting(content) {
|
|
14
|
+
if (content.length === 0) {
|
|
15
|
+
return [];
|
|
16
|
+
}
|
|
17
|
+
const lines = content.split("\n");
|
|
18
|
+
if (content.endsWith("\n")) {
|
|
19
|
+
lines.pop();
|
|
20
|
+
}
|
|
21
|
+
return lines;
|
|
22
|
+
}
|
|
13
23
|
/**
|
|
14
24
|
* Format bytes as human-readable size.
|
|
15
25
|
*/
|
|
@@ -35,7 +45,7 @@ export function truncateHead(content, options = {}) {
|
|
|
35
45
|
const maxLines = options.maxLines ?? DEFAULT_MAX_LINES;
|
|
36
46
|
const maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;
|
|
37
47
|
const totalBytes = Buffer.byteLength(content, "utf-8");
|
|
38
|
-
const lines = content
|
|
48
|
+
const lines = splitLinesForCounting(content);
|
|
39
49
|
const totalLines = lines.length;
|
|
40
50
|
// Check if no truncation needed
|
|
41
51
|
if (totalLines <= maxLines && totalBytes <= maxBytes) {
|
|
@@ -114,7 +124,7 @@ export function truncateTail(content, options = {}) {
|
|
|
114
124
|
const maxLines = options.maxLines ?? DEFAULT_MAX_LINES;
|
|
115
125
|
const maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;
|
|
116
126
|
const totalBytes = Buffer.byteLength(content, "utf-8");
|
|
117
|
-
const lines = content
|
|
127
|
+
const lines = splitLinesForCounting(content);
|
|
118
128
|
const totalLines = lines.length;
|
|
119
129
|
// Check if no truncation needed
|
|
120
130
|
if (totalLines <= maxLines && totalBytes <= maxBytes) {
|
|
@@ -141,42 +141,27 @@ export function createWriteToolDefinition(cwd, options) {
|
|
|
141
141
|
async execute(_toolCallId, { path, content }, signal, _onUpdate, _ctx) {
|
|
142
142
|
const absolutePath = resolveToCwd(path, cwd);
|
|
143
143
|
const dir = dirname(absolutePath);
|
|
144
|
-
return withFileMutationQueue(absolutePath,
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
reject(new Error("Operation aborted"));
|
|
144
|
+
return withFileMutationQueue(absolutePath, async () => {
|
|
145
|
+
// Do not reject from an abort event listener here: that would release the
|
|
146
|
+
// mutation queue while an in-flight filesystem operation may still finish.
|
|
147
|
+
// Checking signal.aborted after each await observes the same aborts while
|
|
148
|
+
// keeping the queue locked until the current operation has settled.
|
|
149
|
+
const throwIfAborted = () => {
|
|
150
|
+
if (signal?.aborted)
|
|
151
|
+
throw new Error("Operation aborted");
|
|
153
152
|
};
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
resolve({
|
|
167
|
-
content: [
|
|
168
|
-
{ type: "text", text: `Successfully wrote ${content.length} bytes to ${path}` },
|
|
169
|
-
],
|
|
170
|
-
details: undefined,
|
|
171
|
-
});
|
|
172
|
-
}
|
|
173
|
-
catch (error) {
|
|
174
|
-
signal?.removeEventListener("abort", onAbort);
|
|
175
|
-
if (!aborted)
|
|
176
|
-
reject(error);
|
|
177
|
-
}
|
|
178
|
-
})();
|
|
179
|
-
}));
|
|
153
|
+
throwIfAborted();
|
|
154
|
+
// Create parent directories if needed.
|
|
155
|
+
await ops.mkdir(dir);
|
|
156
|
+
throwIfAborted();
|
|
157
|
+
// Write the file contents.
|
|
158
|
+
await ops.writeFile(absolutePath, content);
|
|
159
|
+
throwIfAborted();
|
|
160
|
+
return {
|
|
161
|
+
content: [{ type: "text", text: `Successfully wrote ${content.length} bytes to ${path}` }],
|
|
162
|
+
details: undefined,
|
|
163
|
+
};
|
|
164
|
+
});
|
|
180
165
|
},
|
|
181
166
|
renderCall(args, theme, context) {
|
|
182
167
|
const renderArgs = args;
|
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
* This file handles CLI argument parsing and translates them into
|
|
5
5
|
* createAgentSession() options. The SDK does the heavy lifting.
|
|
6
6
|
*/
|
|
7
|
-
import { resolve } from "node:path";
|
|
8
7
|
import { createInterface } from "node:readline";
|
|
9
8
|
import { modelsAreEqual } from "@earendil-works/pi-ai";
|
|
10
9
|
import { ProcessTerminal, setKeybindings, TUI } from "@earendil-works/pi-tui";
|
|
@@ -33,7 +32,7 @@ import { InteractiveMode, runPrintMode, runRpcMode } from "./modes/index.js";
|
|
|
33
32
|
import { ExtensionSelectorComponent } from "./modes/interactive/components/extension-selector.js";
|
|
34
33
|
import { initTheme, stopThemeWatcher } from "./modes/interactive/theme/theme.js";
|
|
35
34
|
import { handleConfigCommand, handlePackageCommand } from "./package-manager-cli.js";
|
|
36
|
-
import { isLocalPath } from "./utils/paths.js";
|
|
35
|
+
import { isLocalPath, normalizePath, resolvePath } from "./utils/paths.js";
|
|
37
36
|
import { cleanupWindowsSelfUpdateQuarantine } from "./utils/windows-self-update.js";
|
|
38
37
|
/**
|
|
39
38
|
* Read all content from piped stdin.
|
|
@@ -106,9 +105,9 @@ async function prepareInitialMessage(parsed, autoResizeImages, stdinContent) {
|
|
|
106
105
|
* If it looks like a path, use as-is. Otherwise try to match as session ID prefix.
|
|
107
106
|
*/
|
|
108
107
|
async function resolveSessionPath(sessionArg, cwd, sessionDir) {
|
|
109
|
-
// If it looks like a file path,
|
|
108
|
+
// If it looks like a file path, resolve it before handing it to the session manager.
|
|
110
109
|
if (sessionArg.includes("/") || sessionArg.includes("\\") || sessionArg.endsWith(".jsonl")) {
|
|
111
|
-
return { type: "path", path: sessionArg };
|
|
110
|
+
return { type: "path", path: resolvePath(sessionArg, cwd) };
|
|
112
111
|
}
|
|
113
112
|
// Try to match as session ID in current project first
|
|
114
113
|
const localSessions = await SessionManager.list(cwd, sessionDir);
|
|
@@ -296,7 +295,7 @@ function buildSessionOptions(parsed, scopedModels, hasExistingSession, modelRegi
|
|
|
296
295
|
return { options, cliThinkingFromModel, diagnostics };
|
|
297
296
|
}
|
|
298
297
|
function resolveCliPaths(cwd, paths) {
|
|
299
|
-
return paths?.map((value) => (isLocalPath(value) ?
|
|
298
|
+
return paths?.map((value) => (isLocalPath(value) ? resolvePath(value, cwd) : value));
|
|
300
299
|
}
|
|
301
300
|
async function promptForMissingSessionCwd(issue, settingsManager) {
|
|
302
301
|
initTheme(settingsManager.getTheme());
|
|
@@ -387,7 +386,7 @@ export async function main(args, options) {
|
|
|
387
386
|
// the target session cwd is known. The startup-cwd settings manager is used only for
|
|
388
387
|
// sessionDir lookup during session selection.
|
|
389
388
|
const envSessionDir = process.env[ENV_SESSION_DIR];
|
|
390
|
-
const sessionDir = parsed.sessionDir ??
|
|
389
|
+
const sessionDir = (parsed.sessionDir ? normalizePath(parsed.sessionDir) : undefined) ??
|
|
391
390
|
(envSessionDir ? expandTildePath(envSessionDir) : undefined) ??
|
|
392
391
|
startupSettingsManager.getSessionDir();
|
|
393
392
|
let sessionManager = await createSessionManager(parsed, cwd, sessionDir, startupSettingsManager);
|
|
@@ -462,7 +462,7 @@ class ResourceList {
|
|
|
462
462
|
}
|
|
463
463
|
getResourcePattern(item) {
|
|
464
464
|
const scope = item.metadata.scope;
|
|
465
|
-
const baseDir = this.getTopLevelBaseDir(scope);
|
|
465
|
+
const baseDir = item.metadata.baseDir ?? this.getTopLevelBaseDir(scope);
|
|
466
466
|
return relative(baseDir, item.path);
|
|
467
467
|
}
|
|
468
468
|
getPackageResourcePattern(item) {
|
package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/footer.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isAbsolute, relative, resolve, sep } from "node:path";
|
|
1
2
|
import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
2
3
|
import { theme } from "../theme/theme.js";
|
|
3
4
|
/**
|
|
@@ -25,6 +26,18 @@ function formatTokens(count) {
|
|
|
25
26
|
return `${(count / 1000000).toFixed(1)}M`;
|
|
26
27
|
return `${Math.round(count / 1000000)}M`;
|
|
27
28
|
}
|
|
29
|
+
export function formatCwdForFooter(cwd, home) {
|
|
30
|
+
if (!home)
|
|
31
|
+
return cwd;
|
|
32
|
+
const resolvedCwd = resolve(cwd);
|
|
33
|
+
const resolvedHome = resolve(home);
|
|
34
|
+
const relativeToHome = relative(resolvedHome, resolvedCwd);
|
|
35
|
+
const isInsideHome = relativeToHome === "" ||
|
|
36
|
+
(relativeToHome !== ".." && !relativeToHome.startsWith(`..${sep}`) && !isAbsolute(relativeToHome));
|
|
37
|
+
if (!isInsideHome)
|
|
38
|
+
return cwd;
|
|
39
|
+
return relativeToHome === "" ? "~" : `~${sep}${relativeToHome}`;
|
|
40
|
+
}
|
|
28
41
|
/**
|
|
29
42
|
* Footer component that shows pwd, token stats, and context usage.
|
|
30
43
|
* Computes token/context stats from session, gets git branch and extension statuses from provider.
|
|
@@ -81,11 +94,7 @@ export class FooterComponent {
|
|
|
81
94
|
const contextPercentValue = contextUsage?.percent ?? 0;
|
|
82
95
|
const contextPercent = contextUsage?.percent !== null ? contextPercentValue.toFixed(1) : "?";
|
|
83
96
|
// Replace home directory with ~
|
|
84
|
-
let pwd = this.session.sessionManager.getCwd();
|
|
85
|
-
const home = process.env.HOME || process.env.USERPROFILE;
|
|
86
|
-
if (home && pwd.startsWith(home)) {
|
|
87
|
-
pwd = `~${pwd.slice(home.length)}`;
|
|
88
|
-
}
|
|
97
|
+
let pwd = formatCwdForFooter(this.session.sessionManager.getCwd(), process.env.HOME || process.env.USERPROFILE);
|
|
89
98
|
// Add git branch if available
|
|
90
99
|
const branch = this.footerData.getGitBranch();
|
|
91
100
|
if (branch) {
|