@psnext/slingcli 2.4.20260601-1 → 2.5.20260602-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/node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/node-http-handler/package.json +4 -4
- package/node_modules/@aws-sdk/credential-provider-node/dist-cjs/index.js +12 -1
- package/node_modules/@aws-sdk/credential-provider-node/dist-es/runtime/memoize-chain.js +12 -1
- package/node_modules/@aws-sdk/credential-provider-node/package.json +1 -1
- package/node_modules/@aws-sdk/nested-clients/node_modules/@smithy/node-http-handler/package.json +4 -4
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/agent-harness.js +81 -18
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/compaction/branch-summarization.js +1 -0
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/compaction/compaction.js +1 -0
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/session.js +14 -1
- package/node_modules/@earendil-works/pi-agent-core/package.json +3 -3
- package/node_modules/@earendil-works/pi-ai/dist/models.generated.js +197 -190
- package/node_modules/@earendil-works/pi-ai/dist/providers/amazon-bedrock.js +84 -21
- package/node_modules/@earendil-works/pi-ai/dist/providers/anthropic.js +24 -14
- package/node_modules/@earendil-works/pi-ai/dist/providers/azure-openai-responses.js +5 -9
- package/node_modules/@earendil-works/pi-ai/dist/providers/google-vertex.js +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/google.js +5 -3
- package/node_modules/@earendil-works/pi-ai/dist/providers/images/openrouter.js +2 -3
- package/node_modules/@earendil-works/pi-ai/dist/providers/mistral.js +2 -3
- package/node_modules/@earendil-works/pi-ai/dist/providers/openai-codex-responses.js +130 -62
- package/node_modules/@earendil-works/pi-ai/dist/providers/openai-completions.js +18 -12
- package/node_modules/@earendil-works/pi-ai/dist/providers/openai-responses-shared.js +4 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/openai-responses.js +5 -9
- package/node_modules/@earendil-works/pi-ai/dist/providers/simple-options.js +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/stream.js +14 -2
- package/node_modules/@earendil-works/pi-ai/dist/utils/abort-signals.js +34 -0
- package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/device-code.js +8 -7
- package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/github-copilot.js +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/index.js +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/openai-codex.js +179 -79
- package/node_modules/@earendil-works/pi-ai/package.json +2 -2
- package/node_modules/@earendil-works/pi-coding-agent/dist/cli/args.js +27 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/config.js +9 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/agent-session-services.js +1 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/agent-session.js +29 -5
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/extensions/runner.js +21 -3
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/footer-data-provider.js +29 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/model-registry.js +65 -13
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/model-resolver.js +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/resolve-config-value.js +134 -11
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/sdk.js +12 -6
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/session-manager.js +167 -96
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/settings-manager.js +14 -9
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/system-prompt.js +0 -3
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/edit.js +7 -10
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/ls.js +5 -7
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/read.js +6 -7
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/render-utils.js +17 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/write.js +5 -6
- package/node_modules/@earendil-works/pi-coding-agent/dist/index.js +2 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/main.js +69 -16
- package/node_modules/@earendil-works/pi-coding-agent/dist/migrations.js +118 -1
- 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/login-dialog.js +2 -4
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/interactive-mode.js +61 -6
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/theme/theme.js +10 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/print-mode.js +1 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/rpc/rpc-mode.js +4 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/utils/deprecation.js +13 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/utils/json.js +7 -0
- package/node_modules/@earendil-works/pi-coding-agent/npm-shrinkwrap.json +71 -56
- package/node_modules/@earendil-works/pi-coding-agent/package.json +7 -7
- package/node_modules/@earendil-works/pi-tui/dist/components/editor.js +9 -53
- package/node_modules/@earendil-works/pi-tui/dist/components/input.js +6 -54
- package/node_modules/@earendil-works/pi-tui/dist/terminal-image.js +34 -7
- package/node_modules/@earendil-works/pi-tui/dist/terminal.js +172 -37
- package/node_modules/@earendil-works/pi-tui/dist/tui.js +166 -22
- package/node_modules/@earendil-works/pi-tui/dist/utils.js +8 -3
- package/node_modules/@earendil-works/pi-tui/dist/word-navigation.js +96 -0
- package/node_modules/@earendil-works/pi-tui/package.json +2 -2
- package/node_modules/@mariozechner/clipboard/Cargo.toml +3 -3
- package/node_modules/@mariozechner/clipboard/index.d.ts +34 -20
- package/node_modules/@mariozechner/clipboard/index.js +546 -257
- package/node_modules/@mariozechner/clipboard/package.json +14 -14
- package/node_modules/@mariozechner/clipboard/src/lib.rs +4 -9
- package/node_modules/@mariozechner/clipboard-linux-x64-gnu/clipboard.linux-x64-gnu.node +0 -0
- package/node_modules/@mariozechner/clipboard-linux-x64-gnu/package.json +2 -2
- package/node_modules/@smithy/core/dist-cjs/submodules/retry/index.browser.js +39 -22
- package/node_modules/@smithy/core/dist-cjs/submodules/retry/index.js +39 -22
- package/node_modules/@smithy/core/dist-es/submodules/retry/middleware-retry/configurations.js +19 -6
- package/node_modules/@smithy/core/dist-es/submodules/retry/middleware-retry/retryMiddleware.js +4 -5
- package/node_modules/@smithy/core/dist-es/submodules/retry/service-error-classification/constants.js +1 -1
- package/node_modules/@smithy/core/dist-es/submodules/retry/util-retry/ConfiguredRetryStrategy.js +4 -5
- package/node_modules/@smithy/core/dist-es/submodules/retry/util-retry/DefaultRetryToken.js +3 -0
- package/node_modules/@smithy/core/dist-es/submodules/retry/util-retry/StandardRetryStrategy.js +9 -5
- package/node_modules/@smithy/core/dist-types/submodules/retry/middleware-retry/configurations.d.ts +4 -1
- package/node_modules/@smithy/core/dist-types/submodules/retry/util-retry/ConfiguredRetryStrategy.d.ts +1 -2
- package/node_modules/@smithy/core/dist-types/submodules/retry/util-retry/DefaultRetryToken.d.ts +3 -0
- package/node_modules/@smithy/core/dist-types/submodules/retry/util-retry/StandardRetryStrategy.d.ts +1 -1
- package/node_modules/@smithy/core/package.json +2 -2
- package/node_modules/@smithy/credential-provider-imds/package.json +3 -3
- package/node_modules/@smithy/fetch-http-handler/package.json +4 -4
- package/node_modules/@smithy/signature-v4/package.json +3 -3
- package/node_modules/@smithy/types/dist-types/retry.d.ts +25 -0
- package/node_modules/@smithy/types/package.json +1 -1
- package/package.json +6 -6
- package/slingshot/index.js +231 -230
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { uuidv7 } from "@earendil-works/pi-agent-core";
|
|
2
2
|
import { randomUUID } from "crypto";
|
|
3
|
-
import { appendFileSync, closeSync, existsSync, mkdirSync, openSync, readdirSync,
|
|
4
|
-
import { readdir,
|
|
3
|
+
import { appendFileSync, closeSync, createReadStream, existsSync, mkdirSync, openSync, readdirSync, readSync, statSync, writeFileSync, } from "fs";
|
|
4
|
+
import { readdir, stat } from "fs/promises";
|
|
5
5
|
import { join, resolve } from "path";
|
|
6
|
+
import { createInterface } from "readline";
|
|
7
|
+
import { StringDecoder } from "string_decoder";
|
|
6
8
|
import { getAgentDir as getDefaultAgentDir, getSessionsDir } from "../config.js";
|
|
7
9
|
import { normalizePath, resolvePath } from "../utils/paths.js";
|
|
8
10
|
import { createBranchSummaryMessage, createCompactionSummaryMessage, createCustomMessage, } from "./messages.js";
|
|
@@ -10,6 +12,11 @@ export const CURRENT_SESSION_VERSION = 3;
|
|
|
10
12
|
function createSessionId() {
|
|
11
13
|
return uuidv7();
|
|
12
14
|
}
|
|
15
|
+
export function assertValidSessionId(id) {
|
|
16
|
+
if (!/^[A-Za-z0-9](?:[A-Za-z0-9._-]*[A-Za-z0-9])?$/.test(id)) {
|
|
17
|
+
throw new Error("Session id must be non-empty, contain only alphanumeric characters, '-', '_', and '.', and start and end with an alphanumeric character");
|
|
18
|
+
}
|
|
19
|
+
}
|
|
13
20
|
/** Generate a unique short ID (8 hex chars, collision-checked) */
|
|
14
21
|
function generateId(byId) {
|
|
15
22
|
for (let i = 0; i < 100; i++) {
|
|
@@ -210,34 +217,65 @@ export function buildSessionContext(entries, leafId, byId) {
|
|
|
210
217
|
* Compute the default session directory for a cwd.
|
|
211
218
|
* Encodes cwd into a safe directory name under ~/.pi/agent/sessions/.
|
|
212
219
|
*/
|
|
213
|
-
|
|
220
|
+
function getDefaultSessionDirPath(cwd, agentDir = getDefaultAgentDir()) {
|
|
214
221
|
const resolvedCwd = resolvePath(cwd);
|
|
215
222
|
const resolvedAgentDir = resolvePath(agentDir);
|
|
216
223
|
const safePath = `--${resolvedCwd.replace(/^[/\\]/, "").replace(/[/\\:]/g, "-")}--`;
|
|
217
|
-
|
|
224
|
+
return join(resolvedAgentDir, "sessions", safePath);
|
|
225
|
+
}
|
|
226
|
+
export function getDefaultSessionDir(cwd, agentDir = getDefaultAgentDir()) {
|
|
227
|
+
const sessionDir = getDefaultSessionDirPath(cwd, agentDir);
|
|
218
228
|
if (!existsSync(sessionDir)) {
|
|
219
229
|
mkdirSync(sessionDir, { recursive: true });
|
|
220
230
|
}
|
|
221
231
|
return sessionDir;
|
|
222
232
|
}
|
|
233
|
+
const SESSION_READ_BUFFER_SIZE = 1024 * 1024;
|
|
234
|
+
function parseSessionEntryLine(line) {
|
|
235
|
+
if (!line.trim())
|
|
236
|
+
return null;
|
|
237
|
+
try {
|
|
238
|
+
return JSON.parse(line);
|
|
239
|
+
}
|
|
240
|
+
catch {
|
|
241
|
+
// Skip malformed lines
|
|
242
|
+
return null;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
223
245
|
/** Exported for testing */
|
|
224
246
|
export function loadEntriesFromFile(filePath) {
|
|
225
247
|
const resolvedFilePath = normalizePath(filePath);
|
|
226
248
|
if (!existsSync(resolvedFilePath))
|
|
227
249
|
return [];
|
|
228
|
-
const content = readFileSync(resolvedFilePath, "utf8");
|
|
229
250
|
const entries = [];
|
|
230
|
-
const
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
251
|
+
const fd = openSync(resolvedFilePath, "r");
|
|
252
|
+
try {
|
|
253
|
+
const decoder = new StringDecoder("utf8");
|
|
254
|
+
const buffer = Buffer.allocUnsafe(SESSION_READ_BUFFER_SIZE);
|
|
255
|
+
let pending = "";
|
|
256
|
+
while (true) {
|
|
257
|
+
const bytesRead = readSync(fd, buffer, 0, buffer.length, null);
|
|
258
|
+
if (bytesRead === 0)
|
|
259
|
+
break;
|
|
260
|
+
pending += decoder.write(buffer.subarray(0, bytesRead));
|
|
261
|
+
let lineStart = 0;
|
|
262
|
+
let newlineIndex = pending.indexOf("\n", lineStart);
|
|
263
|
+
while (newlineIndex !== -1) {
|
|
264
|
+
const entry = parseSessionEntryLine(pending.slice(lineStart, newlineIndex));
|
|
265
|
+
if (entry)
|
|
266
|
+
entries.push(entry);
|
|
267
|
+
lineStart = newlineIndex + 1;
|
|
268
|
+
newlineIndex = pending.indexOf("\n", lineStart);
|
|
269
|
+
}
|
|
270
|
+
pending = pending.slice(lineStart);
|
|
240
271
|
}
|
|
272
|
+
pending += decoder.end();
|
|
273
|
+
const finalEntry = parseSessionEntryLine(pending);
|
|
274
|
+
if (finalEntry)
|
|
275
|
+
entries.push(finalEntry);
|
|
276
|
+
}
|
|
277
|
+
finally {
|
|
278
|
+
closeSync(fd);
|
|
241
279
|
}
|
|
242
280
|
// Validate session header
|
|
243
281
|
if (entries.length === 0)
|
|
@@ -248,7 +286,7 @@ export function loadEntriesFromFile(filePath) {
|
|
|
248
286
|
}
|
|
249
287
|
return entries;
|
|
250
288
|
}
|
|
251
|
-
function
|
|
289
|
+
function readSessionHeader(filePath) {
|
|
252
290
|
try {
|
|
253
291
|
const fd = openSync(filePath, "r");
|
|
254
292
|
const buffer = Buffer.alloc(512);
|
|
@@ -256,23 +294,36 @@ function isValidSessionFile(filePath) {
|
|
|
256
294
|
closeSync(fd);
|
|
257
295
|
const firstLine = buffer.toString("utf8", 0, bytesRead).split("\n")[0];
|
|
258
296
|
if (!firstLine)
|
|
259
|
-
return
|
|
297
|
+
return null;
|
|
260
298
|
const header = JSON.parse(firstLine);
|
|
261
|
-
|
|
299
|
+
if (header.type !== "session" || typeof header.id !== "string") {
|
|
300
|
+
return null;
|
|
301
|
+
}
|
|
302
|
+
return header;
|
|
262
303
|
}
|
|
263
304
|
catch {
|
|
264
|
-
return
|
|
305
|
+
return null;
|
|
265
306
|
}
|
|
266
307
|
}
|
|
308
|
+
function getSessionHeaderCwd(header) {
|
|
309
|
+
const cwd = header.cwd;
|
|
310
|
+
return typeof cwd === "string" ? cwd : undefined;
|
|
311
|
+
}
|
|
312
|
+
function sessionCwdMatches(cwd, resolvedCwd) {
|
|
313
|
+
return cwd !== undefined && cwd !== "" && resolvePath(cwd) === resolvedCwd;
|
|
314
|
+
}
|
|
267
315
|
/** Exported for testing */
|
|
268
|
-
export function findMostRecentSession(sessionDir) {
|
|
316
|
+
export function findMostRecentSession(sessionDir, cwd) {
|
|
269
317
|
const resolvedSessionDir = normalizePath(sessionDir);
|
|
318
|
+
const resolvedCwd = cwd ? resolvePath(cwd) : undefined;
|
|
270
319
|
try {
|
|
271
320
|
const files = readdirSync(resolvedSessionDir)
|
|
272
321
|
.filter((f) => f.endsWith(".jsonl"))
|
|
273
322
|
.map((f) => join(resolvedSessionDir, f))
|
|
274
|
-
.
|
|
275
|
-
.
|
|
323
|
+
.map((path) => ({ path, header: readSessionHeader(path) }))
|
|
324
|
+
.filter((file) => file.header !== null &&
|
|
325
|
+
(!resolvedCwd || sessionCwdMatches(getSessionHeaderCwd(file.header), resolvedCwd)))
|
|
326
|
+
.map(({ path }) => ({ path, mtime: statSync(path).mtime }))
|
|
276
327
|
.sort((a, b) => b.mtime.getTime() - a.mtime.getTime());
|
|
277
328
|
return files[0]?.path || null;
|
|
278
329
|
}
|
|
@@ -293,73 +344,53 @@ function extractTextContent(message) {
|
|
|
293
344
|
.map((block) => block.text)
|
|
294
345
|
.join(" ");
|
|
295
346
|
}
|
|
296
|
-
function
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
continue;
|
|
306
|
-
const msgTimestamp = message.timestamp;
|
|
307
|
-
if (typeof msgTimestamp === "number") {
|
|
308
|
-
lastActivityTime = Math.max(lastActivityTime ?? 0, msgTimestamp);
|
|
309
|
-
continue;
|
|
310
|
-
}
|
|
311
|
-
const entryTimestamp = entry.timestamp;
|
|
312
|
-
if (typeof entryTimestamp === "string") {
|
|
313
|
-
const t = new Date(entryTimestamp).getTime();
|
|
314
|
-
if (!Number.isNaN(t)) {
|
|
315
|
-
lastActivityTime = Math.max(lastActivityTime ?? 0, t);
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
return lastActivityTime;
|
|
320
|
-
}
|
|
321
|
-
function getSessionModifiedDate(entries, header, statsMtime) {
|
|
322
|
-
const lastActivityTime = getLastActivityTime(entries);
|
|
323
|
-
if (typeof lastActivityTime === "number" && lastActivityTime > 0) {
|
|
324
|
-
return new Date(lastActivityTime);
|
|
347
|
+
function getMessageActivityTime(entry) {
|
|
348
|
+
const message = entry.message;
|
|
349
|
+
if (!isMessageWithContent(message))
|
|
350
|
+
return undefined;
|
|
351
|
+
if (message.role !== "user" && message.role !== "assistant")
|
|
352
|
+
return undefined;
|
|
353
|
+
const msgTimestamp = message.timestamp;
|
|
354
|
+
if (typeof msgTimestamp === "number") {
|
|
355
|
+
return msgTimestamp;
|
|
325
356
|
}
|
|
326
|
-
const
|
|
327
|
-
return
|
|
357
|
+
const t = new Date(entry.timestamp).getTime();
|
|
358
|
+
return Number.isNaN(t) ? undefined : t;
|
|
328
359
|
}
|
|
329
360
|
async function buildSessionInfo(filePath) {
|
|
330
361
|
try {
|
|
331
|
-
const content = await readFile(filePath, "utf8");
|
|
332
|
-
const entries = [];
|
|
333
|
-
const lines = content.trim().split("\n");
|
|
334
|
-
for (const line of lines) {
|
|
335
|
-
if (!line.trim())
|
|
336
|
-
continue;
|
|
337
|
-
try {
|
|
338
|
-
entries.push(JSON.parse(line));
|
|
339
|
-
}
|
|
340
|
-
catch {
|
|
341
|
-
// Skip malformed lines
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
if (entries.length === 0)
|
|
345
|
-
return null;
|
|
346
|
-
const header = entries[0];
|
|
347
|
-
if (header.type !== "session")
|
|
348
|
-
return null;
|
|
349
362
|
const stats = await stat(filePath);
|
|
363
|
+
let header = null;
|
|
350
364
|
let messageCount = 0;
|
|
351
365
|
let firstMessage = "";
|
|
352
366
|
const allMessages = [];
|
|
353
367
|
let name;
|
|
354
|
-
|
|
368
|
+
let lastActivityTime;
|
|
369
|
+
const rl = createInterface({
|
|
370
|
+
input: createReadStream(filePath, { encoding: "utf8" }),
|
|
371
|
+
crlfDelay: Infinity,
|
|
372
|
+
});
|
|
373
|
+
for await (const line of rl) {
|
|
374
|
+
const entry = parseSessionEntryLine(line);
|
|
375
|
+
if (!entry)
|
|
376
|
+
continue;
|
|
377
|
+
if (!header) {
|
|
378
|
+
if (entry.type !== "session")
|
|
379
|
+
return null;
|
|
380
|
+
header = entry;
|
|
381
|
+
continue;
|
|
382
|
+
}
|
|
355
383
|
// Extract session name (use latest, including explicit clears)
|
|
356
384
|
if (entry.type === "session_info") {
|
|
357
|
-
|
|
358
|
-
name = infoEntry.name?.trim() || undefined;
|
|
385
|
+
name = entry.name?.trim() || undefined;
|
|
359
386
|
}
|
|
360
387
|
if (entry.type !== "message")
|
|
361
388
|
continue;
|
|
362
389
|
messageCount++;
|
|
390
|
+
const activityTime = getMessageActivityTime(entry);
|
|
391
|
+
if (typeof activityTime === "number") {
|
|
392
|
+
lastActivityTime = Math.max(lastActivityTime ?? 0, activityTime);
|
|
393
|
+
}
|
|
363
394
|
const message = entry.message;
|
|
364
395
|
if (!isMessageWithContent(message))
|
|
365
396
|
continue;
|
|
@@ -373,9 +404,16 @@ async function buildSessionInfo(filePath) {
|
|
|
373
404
|
firstMessage = textContent;
|
|
374
405
|
}
|
|
375
406
|
}
|
|
407
|
+
if (!header)
|
|
408
|
+
return null;
|
|
376
409
|
const cwd = typeof header.cwd === "string" ? header.cwd : "";
|
|
377
410
|
const parentSessionPath = header.parentSession;
|
|
378
|
-
const
|
|
411
|
+
const headerTime = typeof header.timestamp === "string" ? new Date(header.timestamp).getTime() : NaN;
|
|
412
|
+
const modified = typeof lastActivityTime === "number" && lastActivityTime > 0
|
|
413
|
+
? new Date(lastActivityTime)
|
|
414
|
+
: !Number.isNaN(headerTime)
|
|
415
|
+
? new Date(headerTime)
|
|
416
|
+
: stats.mtime;
|
|
379
417
|
return {
|
|
380
418
|
path: filePath,
|
|
381
419
|
id: header.id,
|
|
@@ -475,7 +513,7 @@ export class SessionManager {
|
|
|
475
513
|
labelsById = new Map();
|
|
476
514
|
labelTimestampsById = new Map();
|
|
477
515
|
leafId = null;
|
|
478
|
-
constructor(cwd, sessionDir, sessionFile, persist) {
|
|
516
|
+
constructor(cwd, sessionDir, sessionFile, persist, newSessionOptions) {
|
|
479
517
|
this.cwd = resolvePath(cwd);
|
|
480
518
|
this.sessionDir = normalizePath(sessionDir);
|
|
481
519
|
this.persist = persist;
|
|
@@ -486,7 +524,7 @@ export class SessionManager {
|
|
|
486
524
|
this.setSessionFile(sessionFile);
|
|
487
525
|
}
|
|
488
526
|
else {
|
|
489
|
-
this.newSession();
|
|
527
|
+
this.newSession(newSessionOptions);
|
|
490
528
|
}
|
|
491
529
|
}
|
|
492
530
|
/** Switch to a different session file (used for resume and branching) */
|
|
@@ -519,6 +557,9 @@ export class SessionManager {
|
|
|
519
557
|
}
|
|
520
558
|
}
|
|
521
559
|
newSession(options) {
|
|
560
|
+
if (options?.id !== undefined) {
|
|
561
|
+
assertValidSessionId(options.id);
|
|
562
|
+
}
|
|
522
563
|
this.sessionId = options?.id ?? createSessionId();
|
|
523
564
|
const timestamp = new Date().toISOString();
|
|
524
565
|
const header = {
|
|
@@ -565,8 +606,15 @@ export class SessionManager {
|
|
|
565
606
|
_rewriteFile() {
|
|
566
607
|
if (!this.persist || !this.sessionFile)
|
|
567
608
|
return;
|
|
568
|
-
const
|
|
569
|
-
|
|
609
|
+
const fd = openSync(this.sessionFile, "w");
|
|
610
|
+
try {
|
|
611
|
+
for (const entry of this.fileEntries) {
|
|
612
|
+
writeFileSync(fd, `${JSON.stringify(entry)}\n`);
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
finally {
|
|
616
|
+
closeSync(fd);
|
|
617
|
+
}
|
|
570
618
|
}
|
|
571
619
|
isPersisted() {
|
|
572
620
|
return this.persist;
|
|
@@ -577,6 +625,9 @@ export class SessionManager {
|
|
|
577
625
|
getSessionDir() {
|
|
578
626
|
return this.sessionDir;
|
|
579
627
|
}
|
|
628
|
+
usesDefaultSessionDir() {
|
|
629
|
+
return this.sessionDir === getDefaultSessionDirPath(this.cwd);
|
|
630
|
+
}
|
|
580
631
|
getSessionId() {
|
|
581
632
|
return this.sessionId;
|
|
582
633
|
}
|
|
@@ -588,13 +639,24 @@ export class SessionManager {
|
|
|
588
639
|
return;
|
|
589
640
|
const hasAssistant = this.fileEntries.some((e) => e.type === "message" && e.message.role === "assistant");
|
|
590
641
|
if (!hasAssistant) {
|
|
591
|
-
|
|
592
|
-
|
|
642
|
+
if (this.flushed) {
|
|
643
|
+
appendFileSync(this.sessionFile, `${JSON.stringify(entry)}\n`);
|
|
644
|
+
}
|
|
645
|
+
else {
|
|
646
|
+
// Mark as not flushed so when assistant arrives, all entries get written
|
|
647
|
+
this.flushed = false;
|
|
648
|
+
}
|
|
593
649
|
return;
|
|
594
650
|
}
|
|
595
651
|
if (!this.flushed) {
|
|
596
|
-
|
|
597
|
-
|
|
652
|
+
const fd = openSync(this.sessionFile, "wx");
|
|
653
|
+
try {
|
|
654
|
+
for (const e of this.fileEntries) {
|
|
655
|
+
writeFileSync(fd, `${JSON.stringify(e)}\n`);
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
finally {
|
|
659
|
+
closeSync(fd);
|
|
598
660
|
}
|
|
599
661
|
this.flushed = true;
|
|
600
662
|
}
|
|
@@ -1004,9 +1066,9 @@ export class SessionManager {
|
|
|
1004
1066
|
* @param cwd Working directory (stored in session header)
|
|
1005
1067
|
* @param sessionDir Optional session directory. If omitted, uses default (~/.pi/agent/sessions/<encoded-cwd>/).
|
|
1006
1068
|
*/
|
|
1007
|
-
static create(cwd, sessionDir) {
|
|
1069
|
+
static create(cwd, sessionDir, options) {
|
|
1008
1070
|
const dir = sessionDir ? normalizePath(sessionDir) : getDefaultSessionDir(cwd);
|
|
1009
|
-
return new SessionManager(cwd, dir, undefined, true);
|
|
1071
|
+
return new SessionManager(cwd, dir, undefined, true, options);
|
|
1010
1072
|
}
|
|
1011
1073
|
/**
|
|
1012
1074
|
* Open a specific session file.
|
|
@@ -1031,7 +1093,8 @@ export class SessionManager {
|
|
|
1031
1093
|
*/
|
|
1032
1094
|
static continueRecent(cwd, sessionDir) {
|
|
1033
1095
|
const dir = sessionDir ? normalizePath(sessionDir) : getDefaultSessionDir(cwd);
|
|
1034
|
-
const
|
|
1096
|
+
const filterCwd = sessionDir !== undefined && dir !== getDefaultSessionDirPath(cwd);
|
|
1097
|
+
const mostRecent = findMostRecentSession(dir, filterCwd ? cwd : undefined);
|
|
1035
1098
|
if (mostRecent) {
|
|
1036
1099
|
return new SessionManager(cwd, dir, mostRecent, true);
|
|
1037
1100
|
}
|
|
@@ -1048,7 +1111,7 @@ export class SessionManager {
|
|
|
1048
1111
|
* @param targetCwd Target working directory (where the new session will be stored)
|
|
1049
1112
|
* @param sessionDir Optional session directory. If omitted, uses default for targetCwd.
|
|
1050
1113
|
*/
|
|
1051
|
-
static forkFrom(sourcePath, targetCwd, sessionDir) {
|
|
1114
|
+
static forkFrom(sourcePath, targetCwd, sessionDir, options) {
|
|
1052
1115
|
const resolvedSourcePath = resolvePath(sourcePath);
|
|
1053
1116
|
const resolvedTargetCwd = resolvePath(targetCwd);
|
|
1054
1117
|
const sourceEntries = loadEntriesFromFile(resolvedSourcePath);
|
|
@@ -1064,7 +1127,10 @@ export class SessionManager {
|
|
|
1064
1127
|
mkdirSync(dir, { recursive: true });
|
|
1065
1128
|
}
|
|
1066
1129
|
// Create new session file with new ID but forked content
|
|
1067
|
-
|
|
1130
|
+
if (options?.id !== undefined) {
|
|
1131
|
+
assertValidSessionId(options.id);
|
|
1132
|
+
}
|
|
1133
|
+
const newSessionId = options?.id ?? createSessionId();
|
|
1068
1134
|
const timestamp = new Date().toISOString();
|
|
1069
1135
|
const fileTimestamp = timestamp.replace(/[:.]/g, "-");
|
|
1070
1136
|
const newSessionFile = join(dir, `${fileTimestamp}_${newSessionId}.jsonl`);
|
|
@@ -1077,7 +1143,7 @@ export class SessionManager {
|
|
|
1077
1143
|
cwd: resolvedTargetCwd,
|
|
1078
1144
|
parentSession: resolvedSourcePath,
|
|
1079
1145
|
};
|
|
1080
|
-
|
|
1146
|
+
writeFileSync(newSessionFile, `${JSON.stringify(newHeader)}\n`, { flag: "wx" });
|
|
1081
1147
|
// Copy all non-header entries from source
|
|
1082
1148
|
for (const entry of sourceEntries) {
|
|
1083
1149
|
if (entry.type !== "session") {
|
|
@@ -1094,15 +1160,20 @@ export class SessionManager {
|
|
|
1094
1160
|
*/
|
|
1095
1161
|
static async list(cwd, sessionDir, onProgress) {
|
|
1096
1162
|
const dir = sessionDir ? normalizePath(sessionDir) : getDefaultSessionDir(cwd);
|
|
1097
|
-
const
|
|
1163
|
+
const filterCwd = sessionDir !== undefined && dir !== getDefaultSessionDirPath(cwd);
|
|
1164
|
+
const resolvedCwd = resolvePath(cwd);
|
|
1165
|
+
const sessions = (await listSessionsFromDir(dir, onProgress)).filter((session) => !filterCwd || sessionCwdMatches(session.cwd, resolvedCwd));
|
|
1098
1166
|
sessions.sort((a, b) => b.modified.getTime() - a.modified.getTime());
|
|
1099
1167
|
return sessions;
|
|
1100
1168
|
}
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1169
|
+
static async listAll(sessionDirOrOnProgress, onProgress) {
|
|
1170
|
+
const customSessionDir = typeof sessionDirOrOnProgress === "string" ? normalizePath(sessionDirOrOnProgress) : undefined;
|
|
1171
|
+
const progress = typeof sessionDirOrOnProgress === "function" ? sessionDirOrOnProgress : onProgress;
|
|
1172
|
+
if (customSessionDir) {
|
|
1173
|
+
const sessions = await listSessionsFromDir(customSessionDir, progress);
|
|
1174
|
+
sessions.sort((a, b) => b.modified.getTime() - a.modified.getTime());
|
|
1175
|
+
return sessions;
|
|
1176
|
+
}
|
|
1106
1177
|
const sessionsDir = getSessionsDir();
|
|
1107
1178
|
try {
|
|
1108
1179
|
if (!existsSync(sessionsDir)) {
|
|
@@ -1129,7 +1200,7 @@ export class SessionManager {
|
|
|
1129
1200
|
const allFiles = dirFiles.flat();
|
|
1130
1201
|
const results = await buildSessionInfosWithConcurrency(allFiles, () => {
|
|
1131
1202
|
loaded++;
|
|
1132
|
-
|
|
1203
|
+
progress?.(loaded, totalFiles);
|
|
1133
1204
|
});
|
|
1134
1205
|
for (const info of results) {
|
|
1135
1206
|
if (info) {
|
|
@@ -29,6 +29,16 @@ function deepMergeSettings(base, overrides) {
|
|
|
29
29
|
}
|
|
30
30
|
return result;
|
|
31
31
|
}
|
|
32
|
+
function parseTimeoutSetting(value, settingName) {
|
|
33
|
+
const timeoutMs = parseHttpIdleTimeoutMs(value);
|
|
34
|
+
if (timeoutMs !== undefined) {
|
|
35
|
+
return timeoutMs;
|
|
36
|
+
}
|
|
37
|
+
if (value !== undefined) {
|
|
38
|
+
throw new Error(`Invalid ${settingName} setting: ${String(value)}`);
|
|
39
|
+
}
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
32
42
|
export class FileSettingsStorage {
|
|
33
43
|
globalSettingsPath;
|
|
34
44
|
projectSettingsPath;
|
|
@@ -494,15 +504,7 @@ export class SettingsManager {
|
|
|
494
504
|
};
|
|
495
505
|
}
|
|
496
506
|
getHttpIdleTimeoutMs() {
|
|
497
|
-
|
|
498
|
-
const timeoutMs = parseHttpIdleTimeoutMs(value);
|
|
499
|
-
if (timeoutMs !== undefined) {
|
|
500
|
-
return timeoutMs;
|
|
501
|
-
}
|
|
502
|
-
if (value !== undefined) {
|
|
503
|
-
throw new Error(`Invalid httpIdleTimeoutMs setting: ${String(value)}`);
|
|
504
|
-
}
|
|
505
|
-
return DEFAULT_HTTP_IDLE_TIMEOUT_MS;
|
|
507
|
+
return parseTimeoutSetting(this.settings.httpIdleTimeoutMs, "httpIdleTimeoutMs") ?? DEFAULT_HTTP_IDLE_TIMEOUT_MS;
|
|
506
508
|
}
|
|
507
509
|
setHttpIdleTimeoutMs(timeoutMs) {
|
|
508
510
|
if (!Number.isFinite(timeoutMs) || timeoutMs < 0) {
|
|
@@ -519,6 +521,9 @@ export class SettingsManager {
|
|
|
519
521
|
maxRetryDelayMs: this.settings.retry?.provider?.maxRetryDelayMs ?? 60000,
|
|
520
522
|
};
|
|
521
523
|
}
|
|
524
|
+
getWebSocketConnectTimeoutMs() {
|
|
525
|
+
return parseTimeoutSetting(this.settings.websocketConnectTimeoutMs, "websocketConnectTimeoutMs");
|
|
526
|
+
}
|
|
522
527
|
getHideThinkingBlock() {
|
|
523
528
|
return this.settings.hideThinkingBlock ?? false;
|
|
524
529
|
}
|
|
@@ -68,9 +68,6 @@ export function buildSystemPrompt(options) {
|
|
|
68
68
|
if (hasBash && !hasGrep && !hasFind && !hasLs) {
|
|
69
69
|
addGuideline("Use bash for file operations like ls, rg, find");
|
|
70
70
|
}
|
|
71
|
-
else if (hasBash && (hasGrep || hasFind || hasLs)) {
|
|
72
|
-
addGuideline("Prefer grep/find/ls tools over bash for file exploration (faster, respects .gitignore)");
|
|
73
|
-
}
|
|
74
71
|
for (const guideline of promptGuidelines ?? []) {
|
|
75
72
|
const normalized = guideline.trim();
|
|
76
73
|
if (normalized.length > 0) {
|
|
@@ -6,7 +6,7 @@ import { renderDiff } from "../../modes/interactive/components/diff.js";
|
|
|
6
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
|
-
import {
|
|
9
|
+
import { renderToolPath, str } from "./render-utils.js";
|
|
10
10
|
import { wrapToolDefinition } from "./tool-definition-wrapper.js";
|
|
11
11
|
const replaceEditSchema = Type.Object({
|
|
12
12
|
oldText: Type.String({
|
|
@@ -93,11 +93,8 @@ function getRenderablePreviewInput(args) {
|
|
|
93
93
|
}
|
|
94
94
|
return null;
|
|
95
95
|
}
|
|
96
|
-
function formatEditCall(args, theme) {
|
|
97
|
-
const
|
|
98
|
-
const rawPath = str(args?.file_path ?? args?.path);
|
|
99
|
-
const path = rawPath !== null ? shortenPath(rawPath) : null;
|
|
100
|
-
const pathDisplay = path === null ? invalidArg : path ? theme.fg("accent", path) : theme.fg("toolOutput", "...");
|
|
96
|
+
function formatEditCall(args, theme, cwd) {
|
|
97
|
+
const pathDisplay = renderToolPath(str(args?.file_path ?? args?.path), theme, cwd);
|
|
101
98
|
return `${theme.fg("toolTitle", theme.bold("edit"))} ${pathDisplay}`;
|
|
102
99
|
}
|
|
103
100
|
function formatEditResult(args, preview, result, theme, isError) {
|
|
@@ -132,10 +129,10 @@ function getEditHeaderBg(preview, settledError, theme) {
|
|
|
132
129
|
}
|
|
133
130
|
return (text) => theme.bg("toolPendingBg", text);
|
|
134
131
|
}
|
|
135
|
-
function buildEditCallComponent(component, args, theme) {
|
|
132
|
+
function buildEditCallComponent(component, args, theme, cwd) {
|
|
136
133
|
component.setBgFn(getEditHeaderBg(component.preview, component.settledError, theme));
|
|
137
134
|
component.clear();
|
|
138
|
-
component.addChild(new Text(formatEditCall(args, theme), 0, 0));
|
|
135
|
+
component.addChild(new Text(formatEditCall(args, theme, cwd), 0, 0));
|
|
139
136
|
if (!component.preview) {
|
|
140
137
|
return component;
|
|
141
138
|
}
|
|
@@ -245,7 +242,7 @@ export function createEditToolDefinition(cwd, options) {
|
|
|
245
242
|
}
|
|
246
243
|
});
|
|
247
244
|
}
|
|
248
|
-
return buildEditCallComponent(component, args, theme);
|
|
245
|
+
return buildEditCallComponent(component, args, theme, context.cwd);
|
|
249
246
|
},
|
|
250
247
|
renderResult(result, _options, theme, context) {
|
|
251
248
|
const callComponent = context.state.callComponent;
|
|
@@ -266,7 +263,7 @@ export function createEditToolDefinition(cwd, options) {
|
|
|
266
263
|
changed = true;
|
|
267
264
|
}
|
|
268
265
|
if (changed) {
|
|
269
|
-
buildEditCallComponent(callComponent, context.args, theme);
|
|
266
|
+
buildEditCallComponent(callComponent, context.args, theme, context.cwd);
|
|
270
267
|
}
|
|
271
268
|
}
|
|
272
269
|
const output = formatEditResult(context.args, callComponent?.preview, typedResult, theme, context.isError);
|
|
@@ -4,7 +4,7 @@ import nodePath from "path";
|
|
|
4
4
|
import { Type } from "typebox";
|
|
5
5
|
import { keyHint } from "../../modes/interactive/components/keybinding-hints.js";
|
|
6
6
|
import { pathExists, resolveToCwd } from "./path-utils.js";
|
|
7
|
-
import { getTextOutput,
|
|
7
|
+
import { getTextOutput, renderToolPath, 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";
|
|
10
10
|
const lsSchema = Type.Object({
|
|
@@ -17,12 +17,10 @@ const defaultLsOperations = {
|
|
|
17
17
|
stat: fsStat,
|
|
18
18
|
readdir: fsReaddir,
|
|
19
19
|
};
|
|
20
|
-
function formatLsCall(args, theme) {
|
|
21
|
-
const rawPath = str(args?.path);
|
|
22
|
-
const path = rawPath !== null ? shortenPath(rawPath || ".") : null;
|
|
20
|
+
function formatLsCall(args, theme, cwd) {
|
|
23
21
|
const limit = args?.limit;
|
|
24
|
-
const
|
|
25
|
-
let text = `${theme.fg("toolTitle", theme.bold("ls"))} ${
|
|
22
|
+
const pathDisplay = renderToolPath(str(args?.path), theme, cwd, { emptyFallback: "." });
|
|
23
|
+
let text = `${theme.fg("toolTitle", theme.bold("ls"))} ${pathDisplay}`;
|
|
26
24
|
if (limit !== undefined) {
|
|
27
25
|
text += theme.fg("toolOutput", ` (limit ${limit})`);
|
|
28
26
|
}
|
|
@@ -153,7 +151,7 @@ export function createLsToolDefinition(cwd, options) {
|
|
|
153
151
|
},
|
|
154
152
|
renderCall(args, theme, context) {
|
|
155
153
|
const text = context.lastComponent ?? new Text("", 0, 0);
|
|
156
|
-
text.setText(formatLsCall(args, theme));
|
|
154
|
+
text.setText(formatLsCall(args, theme, context.cwd));
|
|
157
155
|
return text;
|
|
158
156
|
},
|
|
159
157
|
renderResult(result, options, theme, context) {
|
|
@@ -10,7 +10,7 @@ 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
12
|
import { resolveReadPathAsync, resolveToCwd } from "./path-utils.js";
|
|
13
|
-
import { getTextOutput,
|
|
13
|
+
import { getTextOutput, renderToolPath, replaceTabs, 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";
|
|
16
16
|
const readSchema = Type.Object({
|
|
@@ -31,11 +31,8 @@ function formatReadLineRange(args, theme) {
|
|
|
31
31
|
const endLine = args.limit !== undefined ? startLine + args.limit - 1 : "";
|
|
32
32
|
return theme.fg("warning", `:${startLine}${endLine ? `-${endLine}` : ""}`);
|
|
33
33
|
}
|
|
34
|
-
function formatReadCall(args, theme) {
|
|
35
|
-
const
|
|
36
|
-
const path = rawPath !== null ? shortenPath(rawPath) : null;
|
|
37
|
-
const invalidArg = invalidArgText(theme);
|
|
38
|
-
const pathDisplay = path === null ? invalidArg : path ? theme.fg("accent", path) : theme.fg("toolOutput", "...");
|
|
34
|
+
function formatReadCall(args, theme, cwd) {
|
|
35
|
+
const pathDisplay = renderToolPath(str(args?.file_path ?? args?.path), theme, cwd);
|
|
39
36
|
return `${theme.fg("toolTitle", theme.bold("read"))} ${pathDisplay}${formatReadLineRange(args, theme)}`;
|
|
40
37
|
}
|
|
41
38
|
function trimTrailingEmptyLines(lines) {
|
|
@@ -274,7 +271,9 @@ export function createReadToolDefinition(cwd, options) {
|
|
|
274
271
|
renderCall(args, theme, context) {
|
|
275
272
|
const text = context.lastComponent ?? new Text("", 0, 0);
|
|
276
273
|
const classification = !context.expanded ? getCompactReadClassification(args, context.cwd) : undefined;
|
|
277
|
-
text.setText(classification
|
|
274
|
+
text.setText(classification
|
|
275
|
+
? formatCompactReadCall(classification, args, theme)
|
|
276
|
+
: formatReadCall(args, theme, context.cwd));
|
|
278
277
|
return text;
|
|
279
278
|
},
|
|
280
279
|
renderResult(result, options, theme, context) {
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import * as os from "node:os";
|
|
2
|
-
import {
|
|
2
|
+
import { pathToFileURL } from "node:url";
|
|
3
|
+
import { getCapabilities, getImageDimensions, hyperlink, imageFallback } from "@earendil-works/pi-tui";
|
|
3
4
|
import { stripAnsi } from "../../utils/ansi.js";
|
|
5
|
+
import { resolvePath } from "../../utils/paths.js";
|
|
4
6
|
import { sanitizeBinaryOutput } from "../../utils/shell.js";
|
|
5
7
|
export function shortenPath(path) {
|
|
6
8
|
if (typeof path !== "string")
|
|
@@ -11,6 +13,12 @@ export function shortenPath(path) {
|
|
|
11
13
|
}
|
|
12
14
|
return path;
|
|
13
15
|
}
|
|
16
|
+
export function linkPath(styledText, rawPath, cwd) {
|
|
17
|
+
if (!getCapabilities().hyperlinks)
|
|
18
|
+
return styledText;
|
|
19
|
+
const absolutePath = resolvePath(rawPath, cwd);
|
|
20
|
+
return hyperlink(styledText, pathToFileURL(absolutePath).href);
|
|
21
|
+
}
|
|
14
22
|
export function str(value) {
|
|
15
23
|
if (typeof value === "string")
|
|
16
24
|
return value;
|
|
@@ -46,4 +54,12 @@ export function getTextOutput(result, showImages) {
|
|
|
46
54
|
export function invalidArgText(theme) {
|
|
47
55
|
return theme.fg("error", "[invalid arg]");
|
|
48
56
|
}
|
|
57
|
+
export function renderToolPath(rawPath, theme, cwd, options) {
|
|
58
|
+
if (rawPath === null)
|
|
59
|
+
return invalidArgText(theme);
|
|
60
|
+
const value = rawPath || options?.emptyFallback;
|
|
61
|
+
if (!value)
|
|
62
|
+
return theme.fg("toolOutput", "...");
|
|
63
|
+
return linkPath(theme.fg("accent", shortenPath(value)), value, cwd);
|
|
64
|
+
}
|
|
49
65
|
//# sourceMappingURL=render-utils.js.map
|