@hachej/boring-agent 0.1.59 → 0.1.60

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.
@@ -523,6 +523,51 @@ interface SessionListProps {
523
523
  declare function SessionList({ sessions, activeId, loading, onSwitch, onCreate, onDelete, onLoadMore, hasMore, loadingMore, onClose, className, }: SessionListProps): react_jsx_runtime.JSX.Element;
524
524
  declare const SessionBrowser: typeof SessionList;
525
525
 
526
+ interface PiSessionSearchItem {
527
+ id: string;
528
+ /** boring-ui session title maps to pi's session display name. */
529
+ title?: string | null;
530
+ /** Optional pi-style session name for callers that already expose it. */
531
+ name?: string | null;
532
+ /** Optional full transcript search text when available from a pi session listing. */
533
+ allMessagesText?: string | null;
534
+ /** Optional cwd from the underlying pi session. */
535
+ cwd?: string | null;
536
+ updatedAt?: string | number | Date | null;
537
+ }
538
+ type PiSessionSearchSortMode = "fuzzy" | "recent";
539
+ interface PiSessionSearchOptions {
540
+ sortMode?: PiSessionSearchSortMode;
541
+ limit?: number;
542
+ }
543
+ type SearchToken = {
544
+ kind: "fuzzy" | "phrase";
545
+ value: string;
546
+ };
547
+ type ParsedSearchQuery = {
548
+ mode: "tokens";
549
+ tokens: SearchToken[];
550
+ regex: null;
551
+ error?: undefined;
552
+ } | {
553
+ mode: "regex";
554
+ tokens: [];
555
+ regex: RegExp | null;
556
+ error?: string;
557
+ };
558
+ /**
559
+ * Parse session-search queries with the same user-facing syntax as pi's native
560
+ * session picker: whitespace fuzzy tokens, quoted exact phrases, and `re:`
561
+ * regex mode. Kept intentionally small so boring-agent frontends can reuse the
562
+ * pi search behavior without importing the terminal-only session picker UI.
563
+ */
564
+ declare function parsePiSessionSearchQuery(query: string): ParsedSearchQuery;
565
+ declare function matchPiSessionSearch(session: PiSessionSearchItem, parsed: ParsedSearchQuery): {
566
+ matches: boolean;
567
+ score: number;
568
+ };
569
+ declare function searchPiSessions<T extends PiSessionSearchItem>(sessions: readonly T[], query: string, options?: PiSessionSearchOptions): T[];
570
+
526
571
  interface ComposerBlockerAction {
527
572
  id: string;
528
573
  label: string;
@@ -721,4 +766,4 @@ declare const CodeBlock: ({ code, language, showLineNumbers, className, children
721
766
 
722
767
  declare function cn(...inputs: ClassValue[]): string;
723
768
 
724
- export { type AgentCommandContribution, type AgentCommandOptions, ArtifactOpenProvider, ChatEmptyState, type ChatEmptyStateProps, PiChatPanel as ChatPanel, type PiChatPanelProps as ChatPanelProps, type ChatSuggestion, CodeBlock, type CommandRegistry, type ComposerBlocker, type ComposerBlockerAction, Conversation, ConversationContent, ConversationScrollButton, DebugDrawer, type GroupedToolEntry, Message, MessageAction, MessageActions, MessageContent, MessageResponse, type OpenArtifactHandler, type ParsedCommand, PiChatPanel, type PiChatPanelProps, SessionBrowser as PiSessionBrowser, type PiSessionCreateInit, SessionList as PiSessionList, PromptInput, PromptInputFooter, PromptInputSubmit, PromptInputTextarea, Reasoning, ReasoningContent, ReasoningTrigger, type SessionListProps, type SlashCommand, type SlashCommandContext, ToolCallGroup, type ToolPart, type ToolRenderer, type ToolRendererOverrides, type UploadFileOptions, type UploadFileResult, type UsePiSessionsOptions, type UsePiSessionsResult, activeSessionStorageKey, builtinCommands, clearActiveSessionId, cn, createCommandRegistry, defaultChatSuggestions, defaultToolRenderers, getAgentCommands, mergeShadcnToolRenderers, mergeToolRenderers, parseSlashCommand, readActiveSessionId, resolveToolRenderer, uploadFile, useOpenArtifact, usePiSessions, writeActiveSessionId };
769
+ export { type AgentCommandContribution, type AgentCommandOptions, ArtifactOpenProvider, ChatEmptyState, type ChatEmptyStateProps, PiChatPanel as ChatPanel, type PiChatPanelProps as ChatPanelProps, type ChatSuggestion, CodeBlock, type CommandRegistry, type ComposerBlocker, type ComposerBlockerAction, Conversation, ConversationContent, ConversationScrollButton, DebugDrawer, type GroupedToolEntry, Message, MessageAction, MessageActions, MessageContent, MessageResponse, type OpenArtifactHandler, type ParsedCommand, PiChatPanel, type PiChatPanelProps, SessionBrowser as PiSessionBrowser, type PiSessionCreateInit, SessionList as PiSessionList, type PiSessionSearchItem, type PiSessionSearchOptions, type PiSessionSearchSortMode, PromptInput, PromptInputFooter, PromptInputSubmit, PromptInputTextarea, Reasoning, ReasoningContent, ReasoningTrigger, type SessionListProps, type SlashCommand, type SlashCommandContext, ToolCallGroup, type ToolPart, type ToolRenderer, type ToolRendererOverrides, type UploadFileOptions, type UploadFileResult, type UsePiSessionsOptions, type UsePiSessionsResult, activeSessionStorageKey, builtinCommands, clearActiveSessionId, cn, createCommandRegistry, defaultChatSuggestions, defaultToolRenderers, getAgentCommands, matchPiSessionSearch, mergeShadcnToolRenderers, mergeToolRenderers, parsePiSessionSearchQuery, parseSlashCommand, readActiveSessionId, resolveToolRenderer, searchPiSessions, uploadFile, useOpenArtifact, usePiSessions, writeActiveSessionId };
@@ -5663,6 +5663,190 @@ function sortByUpdatedDesc(a, b) {
5663
5663
  return a.id.localeCompare(b.id);
5664
5664
  }
5665
5665
 
5666
+ // ../../node_modules/.pnpm/@earendil-works+pi-tui@0.75.5/node_modules/@earendil-works/pi-tui/dist/fuzzy.js
5667
+ function fuzzyMatch(query, text) {
5668
+ const queryLower = query.toLowerCase();
5669
+ const textLower = text.toLowerCase();
5670
+ const matchQuery = (normalizedQuery) => {
5671
+ if (normalizedQuery.length === 0) {
5672
+ return { matches: true, score: 0 };
5673
+ }
5674
+ if (normalizedQuery.length > textLower.length) {
5675
+ return { matches: false, score: 0 };
5676
+ }
5677
+ let queryIndex = 0;
5678
+ let score = 0;
5679
+ let lastMatchIndex = -1;
5680
+ let consecutiveMatches = 0;
5681
+ for (let i = 0; i < textLower.length && queryIndex < normalizedQuery.length; i++) {
5682
+ if (textLower[i] === normalizedQuery[queryIndex]) {
5683
+ const isWordBoundary = i === 0 || /[\s\-_./:]/.test(textLower[i - 1]);
5684
+ if (lastMatchIndex === i - 1) {
5685
+ consecutiveMatches++;
5686
+ score -= consecutiveMatches * 5;
5687
+ } else {
5688
+ consecutiveMatches = 0;
5689
+ if (lastMatchIndex >= 0) {
5690
+ score += (i - lastMatchIndex - 1) * 2;
5691
+ }
5692
+ }
5693
+ if (isWordBoundary) {
5694
+ score -= 10;
5695
+ }
5696
+ score += i * 0.1;
5697
+ lastMatchIndex = i;
5698
+ queryIndex++;
5699
+ }
5700
+ }
5701
+ if (queryIndex < normalizedQuery.length) {
5702
+ return { matches: false, score: 0 };
5703
+ }
5704
+ if (normalizedQuery === textLower) {
5705
+ score -= 100;
5706
+ }
5707
+ return { matches: true, score };
5708
+ };
5709
+ const primaryMatch = matchQuery(queryLower);
5710
+ if (primaryMatch.matches) {
5711
+ return primaryMatch;
5712
+ }
5713
+ const alphaNumericMatch = queryLower.match(/^(?<letters>[a-z]+)(?<digits>[0-9]+)$/);
5714
+ const numericAlphaMatch = queryLower.match(/^(?<digits>[0-9]+)(?<letters>[a-z]+)$/);
5715
+ const swappedQuery = alphaNumericMatch ? `${alphaNumericMatch.groups?.digits ?? ""}${alphaNumericMatch.groups?.letters ?? ""}` : numericAlphaMatch ? `${numericAlphaMatch.groups?.letters ?? ""}${numericAlphaMatch.groups?.digits ?? ""}` : "";
5716
+ if (!swappedQuery) {
5717
+ return primaryMatch;
5718
+ }
5719
+ const swappedMatch = matchQuery(swappedQuery);
5720
+ if (!swappedMatch.matches) {
5721
+ return primaryMatch;
5722
+ }
5723
+ return { matches: true, score: swappedMatch.score + 5 };
5724
+ }
5725
+
5726
+ // src/front/chat/session/piSessionSearch.ts
5727
+ function normalizeWhitespaceLower(text) {
5728
+ return text.toLowerCase().replace(/\s+/g, " ").trim();
5729
+ }
5730
+ function sessionModifiedMs(session) {
5731
+ const value = session.updatedAt;
5732
+ if (value instanceof Date) return value.getTime();
5733
+ if (typeof value === "number") return Number.isFinite(value) ? value : 0;
5734
+ if (typeof value === "string") {
5735
+ const parsed = Date.parse(value);
5736
+ return Number.isFinite(parsed) ? parsed : 0;
5737
+ }
5738
+ return 0;
5739
+ }
5740
+ function getSessionSearchText(session) {
5741
+ const name = session.name ?? session.title ?? "";
5742
+ return `${session.id} ${name} ${session.allMessagesText ?? ""} ${session.cwd ?? ""}`;
5743
+ }
5744
+ function parsePiSessionSearchQuery(query) {
5745
+ const trimmed = query.trim();
5746
+ if (!trimmed) return { mode: "tokens", tokens: [], regex: null };
5747
+ if (trimmed.startsWith("re:")) {
5748
+ const pattern = trimmed.slice(3).trim();
5749
+ if (!pattern) return { mode: "regex", tokens: [], regex: null, error: "Empty regex" };
5750
+ try {
5751
+ return { mode: "regex", tokens: [], regex: new RegExp(pattern, "i") };
5752
+ } catch (error) {
5753
+ return {
5754
+ mode: "regex",
5755
+ tokens: [],
5756
+ regex: null,
5757
+ error: error instanceof Error ? error.message : String(error)
5758
+ };
5759
+ }
5760
+ }
5761
+ const tokens = [];
5762
+ let buffer = "";
5763
+ let inQuote = false;
5764
+ let hadUnclosedQuote = false;
5765
+ const flush = (kind) => {
5766
+ const value = buffer.trim();
5767
+ buffer = "";
5768
+ if (value) tokens.push({ kind, value });
5769
+ };
5770
+ for (let index = 0; index < trimmed.length; index += 1) {
5771
+ const char = trimmed[index];
5772
+ if (char === '"') {
5773
+ if (inQuote) {
5774
+ flush("phrase");
5775
+ inQuote = false;
5776
+ } else {
5777
+ flush("fuzzy");
5778
+ inQuote = true;
5779
+ }
5780
+ continue;
5781
+ }
5782
+ if (!inQuote && /\s/.test(char)) {
5783
+ flush("fuzzy");
5784
+ continue;
5785
+ }
5786
+ buffer += char;
5787
+ }
5788
+ if (inQuote) hadUnclosedQuote = true;
5789
+ if (hadUnclosedQuote) {
5790
+ return {
5791
+ mode: "tokens",
5792
+ tokens: trimmed.split(/\s+/).map((token) => token.trim()).filter((token) => token.length > 0).map((value) => ({ kind: "fuzzy", value })),
5793
+ regex: null
5794
+ };
5795
+ }
5796
+ flush(inQuote ? "phrase" : "fuzzy");
5797
+ return { mode: "tokens", tokens, regex: null };
5798
+ }
5799
+ function matchPiSessionSearch(session, parsed) {
5800
+ const text = getSessionSearchText(session);
5801
+ if (parsed.mode === "regex") {
5802
+ if (!parsed.regex) return { matches: false, score: 0 };
5803
+ const index = text.search(parsed.regex);
5804
+ if (index < 0) return { matches: false, score: 0 };
5805
+ return { matches: true, score: index * 0.1 };
5806
+ }
5807
+ if (parsed.tokens.length === 0) return { matches: true, score: 0 };
5808
+ let totalScore = 0;
5809
+ let normalizedText = null;
5810
+ for (const token of parsed.tokens) {
5811
+ if (token.kind === "phrase") {
5812
+ normalizedText ??= normalizeWhitespaceLower(text);
5813
+ const phrase = normalizeWhitespaceLower(token.value);
5814
+ if (!phrase) continue;
5815
+ const index = normalizedText.indexOf(phrase);
5816
+ if (index < 0) return { matches: false, score: 0 };
5817
+ totalScore += index * 0.1;
5818
+ continue;
5819
+ }
5820
+ const match = fuzzyMatch(token.value, text);
5821
+ if (!match.matches) return { matches: false, score: 0 };
5822
+ totalScore += match.score;
5823
+ }
5824
+ return { matches: true, score: totalScore };
5825
+ }
5826
+ function searchPiSessions(sessions, query, options = {}) {
5827
+ const { sortMode = "fuzzy", limit } = options;
5828
+ const trimmed = query.trim();
5829
+ const source = [...sessions];
5830
+ if (!trimmed) return limit == null ? source : source.slice(0, limit);
5831
+ const parsed = parsePiSessionSearchQuery(trimmed);
5832
+ if (parsed.error) return [];
5833
+ if (sortMode === "recent") {
5834
+ const filtered = source.filter((session) => matchPiSessionSearch(session, parsed).matches);
5835
+ return limit == null ? filtered : filtered.slice(0, limit);
5836
+ }
5837
+ const scored = [];
5838
+ for (const session of source) {
5839
+ const result = matchPiSessionSearch(session, parsed);
5840
+ if (result.matches) scored.push({ session, score: result.score });
5841
+ }
5842
+ scored.sort((a, b) => {
5843
+ if (a.score !== b.score) return a.score - b.score;
5844
+ return sessionModifiedMs(b.session) - sessionModifiedMs(a.session);
5845
+ });
5846
+ const results = scored.map((result) => result.session);
5847
+ return limit == null ? results : results.slice(0, limit);
5848
+ }
5849
+
5666
5850
  // src/front/chat/components/PiConversationSurface.tsx
5667
5851
  import { Loader2 as Loader22 } from "lucide-react";
5668
5852
  import { useCallback as useCallback14, useEffect as useEffect14, useLayoutEffect, useRef as useRef10, useState as useState15 } from "react";
@@ -10358,11 +10542,14 @@ export {
10358
10542
  defaultChatSuggestions,
10359
10543
  defaultToolRenderers,
10360
10544
  getAgentCommands,
10545
+ matchPiSessionSearch,
10361
10546
  mergeShadcnToolRenderers,
10362
10547
  mergeToolRenderers,
10548
+ parsePiSessionSearchQuery,
10363
10549
  parseSlashCommand,
10364
10550
  readActiveSessionId,
10365
10551
  resolveToolRenderer,
10552
+ searchPiSessions,
10366
10553
  uploadFile,
10367
10554
  useOpenArtifact,
10368
10555
  usePiSessions,
@@ -2619,6 +2619,37 @@ function buildFileRecordsResult(args) {
2619
2619
  return result;
2620
2620
  }
2621
2621
 
2622
+ // src/server/http/readonlySkillFiles.ts
2623
+ import { readFile as readFile4, stat as stat4 } from "fs/promises";
2624
+ function isReadonlySkillFilePath(path4) {
2625
+ if (!path4.startsWith("/")) return false;
2626
+ if (path4.includes("\0")) return false;
2627
+ if (!path4.endsWith("/SKILL.md")) return false;
2628
+ return path4.includes("/.pi/agent/") && path4.includes("/skills/");
2629
+ }
2630
+ async function readReadonlySkillFile(path4) {
2631
+ const [content, fsStat2] = await Promise.all([
2632
+ readFile4(path4, "utf-8"),
2633
+ stat4(path4)
2634
+ ]);
2635
+ return {
2636
+ content,
2637
+ stat: {
2638
+ kind: fsStat2.isDirectory() ? "directory" : "file",
2639
+ size: fsStat2.size,
2640
+ mtimeMs: fsStat2.mtimeMs
2641
+ }
2642
+ };
2643
+ }
2644
+ async function statReadonlySkillFile(path4) {
2645
+ const fsStat2 = await stat4(path4);
2646
+ return {
2647
+ kind: fsStat2.isDirectory() ? "directory" : "file",
2648
+ size: fsStat2.size,
2649
+ mtimeMs: fsStat2.mtimeMs
2650
+ };
2651
+ }
2652
+
2622
2653
  // src/server/http/routes/file.ts
2623
2654
  var log2 = createLogger("boring/workspace-settings");
2624
2655
  var BORING_SETTINGS_PATH = ".boring/settings";
@@ -2828,8 +2859,8 @@ function fileRoutes(app, opts, done) {
2828
2859
  error: { code: ERROR_CODE_INTERNAL, message: "workspace does not support binary reads" }
2829
2860
  });
2830
2861
  }
2831
- const stat11 = await workspace.stat(path4);
2832
- if (stat11.kind !== "file") {
2862
+ const stat12 = await workspace.stat(path4);
2863
+ if (stat12.kind !== "file") {
2833
2864
  return reply.code(400).send({
2834
2865
  error: { code: ERROR_CODE_VALIDATION_ERROR, message: "path is not a file", field: "path" }
2835
2866
  });
@@ -2844,11 +2875,11 @@ function fileRoutes(app, opts, done) {
2844
2875
  try {
2845
2876
  const parsed = parseFileRecordsRequest(request.query);
2846
2877
  const workspace = await resolveWorkspace(request);
2847
- const stat11 = await workspace.stat(parsed.path);
2848
- if (stat11.kind !== "file") {
2878
+ const stat12 = await workspace.stat(parsed.path);
2879
+ if (stat12.kind !== "file") {
2849
2880
  return sendValidationError(reply, "path is not a file", "path");
2850
2881
  }
2851
- if (stat11.size > MAX_RECORD_FILE_BYTES) {
2882
+ if (stat12.size > MAX_RECORD_FILE_BYTES) {
2852
2883
  return sendValidationError(reply, "records file is too large", "path");
2853
2884
  }
2854
2885
  const content = await workspace.readFile(parsed.path);
@@ -2858,7 +2889,7 @@ function fileRoutes(app, opts, done) {
2858
2889
  return buildFileRecordsResult({
2859
2890
  path: parsed.path,
2860
2891
  content,
2861
- mtimeMs: stat11.mtimeMs,
2892
+ mtimeMs: stat12.mtimeMs,
2862
2893
  offset: parsed.offset,
2863
2894
  limit: parsed.limit,
2864
2895
  q: parsed.q
@@ -2875,14 +2906,23 @@ function fileRoutes(app, opts, done) {
2875
2906
  const path4 = requireStringParam(query.path, "path", reply);
2876
2907
  if (path4 === null) return;
2877
2908
  try {
2909
+ if (isReadonlySkillFilePath(path4)) {
2910
+ const { content: content2, stat: stat13 } = await readReadonlySkillFile(path4);
2911
+ if (stat13.kind !== "file") {
2912
+ return reply.code(400).send({
2913
+ error: { code: ERROR_CODE_VALIDATION_ERROR, message: "path is not a file", field: "path" }
2914
+ });
2915
+ }
2916
+ return { content: content2, mtimeMs: stat13.mtimeMs };
2917
+ }
2878
2918
  const workspace = await resolveWorkspace(request);
2879
2919
  if (workspace.readFileWithStat) {
2880
- const { content: content2, stat: stat12 } = await workspace.readFileWithStat(path4);
2881
- return { content: content2, mtimeMs: stat12.kind === "file" ? stat12.mtimeMs : void 0 };
2920
+ const { content: content2, stat: stat13 } = await workspace.readFileWithStat(path4);
2921
+ return { content: content2, mtimeMs: stat13.kind === "file" ? stat13.mtimeMs : void 0 };
2882
2922
  }
2883
2923
  const content = await workspace.readFile(path4);
2884
- const stat11 = await workspace.stat(path4);
2885
- return { content, mtimeMs: stat11.kind === "file" ? stat11.mtimeMs : void 0 };
2924
+ const stat12 = await workspace.stat(path4);
2925
+ return { content, mtimeMs: stat12.kind === "file" ? stat12.mtimeMs : void 0 };
2886
2926
  } catch (err) {
2887
2927
  return classifyError(err, reply, "file");
2888
2928
  }
@@ -2936,11 +2976,11 @@ function fileRoutes(app, opts, done) {
2936
2976
  await workspace.writeFile(path4, content);
2937
2977
  return { ok: true };
2938
2978
  }
2939
- const stat11 = workspace.writeFileWithStat ? await workspace.writeFileWithStat(path4, content) : await (async () => {
2979
+ const stat12 = workspace.writeFileWithStat ? await workspace.writeFileWithStat(path4, content) : await (async () => {
2940
2980
  await workspace.writeFile(path4, content);
2941
2981
  return await workspace.stat(path4);
2942
2982
  })();
2943
- return { ok: true, mtimeMs: stat11.kind === "file" ? stat11.mtimeMs : void 0 };
2983
+ return { ok: true, mtimeMs: stat12.kind === "file" ? stat12.mtimeMs : void 0 };
2944
2984
  } catch (err) {
2945
2985
  return classifyError(err, reply, "file");
2946
2986
  }
@@ -2969,7 +3009,7 @@ function fileRoutes(app, opts, done) {
2969
3009
  }
2970
3010
  const bytes = Buffer.from(contentBase64, "base64");
2971
3011
  await workspace.mkdir(dir, { recursive: true });
2972
- const stat11 = workspace.writeBinaryFileWithStat ? await workspace.writeBinaryFileWithStat(path4, bytes) : await (async () => {
3012
+ const stat12 = workspace.writeBinaryFileWithStat ? await workspace.writeBinaryFileWithStat(path4, bytes) : await (async () => {
2973
3013
  if (!workspace.writeBinaryFile) {
2974
3014
  throw new Error("workspace does not support binary uploads");
2975
3015
  }
@@ -2983,7 +3023,7 @@ function fileRoutes(app, opts, done) {
2983
3023
  ok: true,
2984
3024
  path: path4,
2985
3025
  markdownUrl: markdownUrlFor(sourcePath, path4),
2986
- mtimeMs: stat11.kind === "file" ? stat11.mtimeMs : void 0
3026
+ mtimeMs: stat12.kind === "file" ? stat12.mtimeMs : void 0
2987
3027
  };
2988
3028
  } catch (err) {
2989
3029
  return classifyError(err, reply, "upload");
@@ -3069,9 +3109,12 @@ function fileRoutes(app, opts, done) {
3069
3109
  const path4 = requireStringParam(query.path, "path", reply);
3070
3110
  if (path4 === null) return;
3071
3111
  try {
3112
+ if (isReadonlySkillFilePath(path4)) {
3113
+ return await statReadonlySkillFile(path4);
3114
+ }
3072
3115
  const workspace = await resolveWorkspace(request);
3073
- const stat11 = await workspace.stat(path4);
3074
- return stat11;
3116
+ const stat12 = await workspace.stat(path4);
3117
+ return stat12;
3075
3118
  } catch (err) {
3076
3119
  return classifyError(err, reply, "path");
3077
3120
  }
@@ -3082,7 +3125,7 @@ function fileRoutes(app, opts, done) {
3082
3125
  // src/server/workspace/provisionRuntime.ts
3083
3126
  import { createHash as createHash3 } from "crypto";
3084
3127
  import { constants as constants2 } from "fs";
3085
- import { access as access2, chmod as chmod3, cp, mkdir as mkdir4, readdir as readdir3, readFile as readFile4, realpath as realpath2, stat as stat4, writeFile as writeFile4 } from "fs/promises";
3128
+ import { access as access2, chmod as chmod3, cp, mkdir as mkdir4, readdir as readdir3, readFile as readFile5, realpath as realpath2, stat as stat5, writeFile as writeFile4 } from "fs/promises";
3086
3129
  import { dirname as dirname6, isAbsolute as isAbsolute4, join as join4, relative as relative5, resolve as resolve5 } from "path";
3087
3130
  import { fileURLToPath } from "url";
3088
3131
  import { execFile } from "child_process";
@@ -3117,7 +3160,7 @@ async function commandExists(cmd) {
3117
3160
  }
3118
3161
  }
3119
3162
  async function hashPath(path4, hash) {
3120
- const info = await stat4(path4);
3163
+ const info = await stat5(path4);
3121
3164
  if (info.isDirectory()) {
3122
3165
  const entries = (await readdir3(path4)).filter((entry) => entry !== "__pycache__" && entry !== "build" && !entry.endsWith(".egg-info")).sort();
3123
3166
  for (const entry of entries) {
@@ -3130,7 +3173,7 @@ async function hashPath(path4, hash) {
3130
3173
  if (!info.isFile()) return;
3131
3174
  hash.update(`file:${path4}
3132
3175
  `);
3133
- hash.update(await readFile4(path4));
3176
+ hash.update(await readFile5(path4));
3134
3177
  }
3135
3178
  async function fingerprint(contributions) {
3136
3179
  const hash = createHash3("sha256");
@@ -3323,7 +3366,7 @@ VENV_BIN="$WORKSPACE_ROOT/.boring-agent/venv/bin"
3323
3366
  for (const entry of await readdir3(venvBin)) {
3324
3367
  if (["python", "python3", "pip", "pip3"].includes(entry)) continue;
3325
3368
  const full = join4(venvBin, entry);
3326
- const info = await stat4(full).catch(() => null);
3369
+ const info = await stat5(full).catch(() => null);
3327
3370
  if (!info?.isFile()) continue;
3328
3371
  await writeExecutable(join4(shimDir, entry), `${base}TARGET="$VENV_BIN"/${bashSingleQuote(entry)}
3329
3372
  SHEBANG="$(head -n 1 "$TARGET" 2>/dev/null || true)"
@@ -3352,7 +3395,7 @@ async function provisionRuntimeWorkspace({
3352
3395
  const binDir = join4(workspaceRoot, ".boring-agent", "bin");
3353
3396
  if (!force && await exists(markerPath)) {
3354
3397
  try {
3355
- const marker = JSON.parse(await readFile4(markerPath, "utf8"));
3398
+ const marker = JSON.parse(await readFile5(markerPath, "utf8"));
3356
3399
  if (marker.fingerprint === hash && await isRuntimeMaterialized(workspaceRoot, active)) {
3357
3400
  await writeShims(workspaceRoot, env);
3358
3401
  return { fingerprint: hash, changed: false, env, binDir };
@@ -3410,11 +3453,11 @@ function createTimedLruCache(ttlMs, maxEntries) {
3410
3453
  }
3411
3454
  };
3412
3455
  }
3413
- function cloneStat(stat11) {
3456
+ function cloneStat(stat12) {
3414
3457
  return {
3415
- size: stat11.size,
3416
- mtimeMs: stat11.mtimeMs,
3417
- kind: stat11.kind
3458
+ size: stat12.size,
3459
+ mtimeMs: stat12.mtimeMs,
3460
+ kind: stat12.kind
3418
3461
  };
3419
3462
  }
3420
3463
  function cloneEntries(entries) {
@@ -3622,15 +3665,15 @@ function createVercelSandboxWorkspace(sandbox, workspaceOpts = {}) {
3622
3665
  remote.fs.readFile(sandboxPath, "utf8"),
3623
3666
  remote.fs.stat(sandboxPath)
3624
3667
  ]);
3625
- const stat11 = {
3668
+ const stat12 = {
3626
3669
  size: fileStat.size,
3627
3670
  mtimeMs: fileStat.mtimeMs,
3628
3671
  kind: fileStat.isDirectory() ? "dir" : "file"
3629
3672
  };
3630
- statCache.set(sandboxPath, stat11);
3673
+ statCache.set(sandboxPath, stat12);
3631
3674
  return {
3632
3675
  content: typeof content === "string" ? content : Buffer.from(content).toString("utf-8"),
3633
- stat: cloneStat(stat11)
3676
+ stat: cloneStat(stat12)
3634
3677
  };
3635
3678
  }
3636
3679
  const version = metadataVersion;
@@ -3896,7 +3939,7 @@ function createVercelProvisioningAdapter(options) {
3896
3939
  // src/server/workspace/provisioning/fingerprint.ts
3897
3940
  import { createHash as createHash4 } from "crypto";
3898
3941
  import { constants as constants3 } from "fs";
3899
- import { access as access3, mkdir as mkdir6, open, readFile as readFile5, rename as rename5, rm as rm2 } from "fs/promises";
3942
+ import { access as access3, mkdir as mkdir6, open, readFile as readFile6, rename as rename5, rm as rm2 } from "fs/promises";
3900
3943
  import { dirname as dirname8 } from "path";
3901
3944
  var FINGERPRINT_RE = /^sha256:[a-f0-9]{64}$/;
3902
3945
  function isValidFingerprint(value) {
@@ -4278,7 +4321,7 @@ async function ensurePythonRuntime(options) {
4278
4321
  }
4279
4322
 
4280
4323
  // src/server/workspace/provisioning/skills.ts
4281
- import { stat as stat5 } from "fs/promises";
4324
+ import { stat as stat6 } from "fs/promises";
4282
4325
  import { join as join8 } from "path";
4283
4326
  import { fileURLToPath as fileURLToPath4 } from "url";
4284
4327
  var GENERATED_SKILLS_REL = ".boring-agent/skills";
@@ -4309,7 +4352,7 @@ async function mirrorPluginSkills(options) {
4309
4352
  }
4310
4353
  seen.add(key);
4311
4354
  const sourcePath = sourceToPath2(skill.source);
4312
- const sourceStat = await stat5(sourcePath);
4355
+ const sourceStat = await stat6(sourcePath);
4313
4356
  const skillTarget = `${GENERATED_SKILLS_REL}/${plugin.id}/${skill.name}`;
4314
4357
  const target = sourceStat.isDirectory() ? skillTarget : `${skillTarget}/SKILL.md`;
4315
4358
  await options.adapter.workspaceFs.copyFromHost(skill.source, target);
@@ -4325,7 +4368,7 @@ async function mirrorPluginSkills(options) {
4325
4368
  // src/server/workspace/provisioning/workspaceFiles.ts
4326
4369
  import { basename, join as joinPath } from "path";
4327
4370
  import { posix as posix2 } from "path";
4328
- import { readdir as readdir4, stat as stat6 } from "fs/promises";
4371
+ import { readdir as readdir4, stat as stat7 } from "fs/promises";
4329
4372
  import { fileURLToPath as fileURLToPath5, pathToFileURL } from "url";
4330
4373
  function sourceToPath3(source) {
4331
4374
  return source instanceof URL ? fileURLToPath5(source) : source;
@@ -4338,7 +4381,7 @@ function sourceForChild(rootSource, childPath) {
4338
4381
  }
4339
4382
  async function collectTemplateWorkItems(template) {
4340
4383
  const sourcePath = sourceToPath3(template.path);
4341
- const sourceStat = await stat6(sourcePath);
4384
+ const sourceStat = await stat7(sourcePath);
4342
4385
  const targetPrefix = template.target ?? "";
4343
4386
  if (!sourceStat.isDirectory()) {
4344
4387
  const targetRel = toWorkspaceRel3(targetPrefix || basename(sourcePath));
@@ -4645,7 +4688,7 @@ async function copyTemplate(templatePath, workspaceRoot) {
4645
4688
 
4646
4689
  // src/server/runtime/modes/provisioningAdapter.ts
4647
4690
  import { spawn as spawn3 } from "child_process";
4648
- import { cp as cp3, lstat as lstat3, mkdir as mkdir7, readFile as readFile6, realpath as realpath3, rm as rm3, stat as stat7, writeFile as writeFile5 } from "fs/promises";
4691
+ import { cp as cp3, lstat as lstat3, mkdir as mkdir7, readFile as readFile7, realpath as realpath3, rm as rm3, stat as stat8, writeFile as writeFile5 } from "fs/promises";
4649
4692
  import { dirname as dirname10, isAbsolute as isAbsolute6, relative as relative8, resolve as resolve6, sep as sep5 } from "path";
4650
4693
  import { fileURLToPath as fileURLToPath6 } from "url";
4651
4694
  var LOCAL_SANDBOX_WORKSPACE_ROOT = "/workspace";
@@ -4754,7 +4797,7 @@ function createWorkspaceFs(workspaceRoot, opts) {
4754
4797
  async exists(workspaceRelativePath) {
4755
4798
  const absPath = validatePath(workspaceRoot, workspaceRelativePath);
4756
4799
  try {
4757
- await stat7(absPath);
4800
+ await stat8(absPath);
4758
4801
  return true;
4759
4802
  } catch (error) {
4760
4803
  if (error.code === "ENOENT") return false;
@@ -4780,12 +4823,12 @@ function createWorkspaceFs(workspaceRoot, opts) {
4780
4823
  async readText(workspaceRelativePath) {
4781
4824
  const absPath = await assertExistingInsideWorkspace(workspaceRoot, workspaceRelativePath, enforceSymlinkBoundary);
4782
4825
  if (!absPath) return null;
4783
- return await readFile6(absPath, "utf8");
4826
+ return await readFile7(absPath, "utf8");
4784
4827
  },
4785
4828
  async copyFromHost(hostSourcePath, workspaceRelativeTarget) {
4786
4829
  const sourcePath = sourceToPath4(hostSourcePath);
4787
4830
  const absTarget = await prepareWritablePath(workspaceRoot, workspaceRelativeTarget, enforceSymlinkBoundary);
4788
- const sourceStat = await stat7(sourcePath);
4831
+ const sourceStat = await stat8(sourcePath);
4789
4832
  await cp3(sourcePath, absTarget, {
4790
4833
  recursive: sourceStat.isDirectory(),
4791
4834
  force: false,
@@ -4915,7 +4958,7 @@ var localModeAdapter = {
4915
4958
  };
4916
4959
 
4917
4960
  // src/server/runtime/modes/vercel-sandbox.ts
4918
- import { readFile as readFile8, readdir as readdir6, stat as stat8 } from "fs/promises";
4961
+ import { readFile as readFile9, readdir as readdir6, stat as stat9 } from "fs/promises";
4919
4962
  import { join as join9 } from "path";
4920
4963
  import { fileURLToPath as fileURLToPath7 } from "url";
4921
4964
  import { Sandbox as VercelSandbox } from "@vercel/sandbox";
@@ -5062,7 +5105,7 @@ function createVercelSandboxExec(sandbox, execOpts = {}) {
5062
5105
 
5063
5106
  // src/server/sandbox/vercel-sandbox/packageTemplate.ts
5064
5107
  import { createHash as createHash5 } from "crypto";
5065
- import { readFile as readFile7, readdir as readdir5 } from "fs/promises";
5108
+ import { readFile as readFile8, readdir as readdir5 } from "fs/promises";
5066
5109
  import path3 from "path";
5067
5110
  import { Readable } from "stream";
5068
5111
  import { createGzip } from "zlib";
@@ -5083,7 +5126,7 @@ async function collectFiles(dir, base = "") {
5083
5126
  if (entry.isDirectory()) {
5084
5127
  files.push(...await collectFiles(fullPath, relPath));
5085
5128
  } else if (entry.isFile()) {
5086
- files.push({ rel: relPath, content: await readFile7(fullPath) });
5129
+ files.push({ rel: relPath, content: await readFile8(fullPath) });
5087
5130
  }
5088
5131
  }
5089
5132
  return files.sort((a, b) => a.rel.localeCompare(b.rel));
@@ -5360,7 +5403,7 @@ function shellSingleQuote(value) {
5360
5403
  }
5361
5404
  async function copyHostPathToVercelWorkspace(options) {
5362
5405
  const sourcePath = provisioningSourceToPath(options.source);
5363
- const sourceStat = await stat8(sourcePath);
5406
+ const sourceStat = await stat9(sourcePath);
5364
5407
  if (sourceStat.isDirectory()) {
5365
5408
  await options.workspace.mkdir(options.targetRel, { recursive: true });
5366
5409
  for (const entry of await readdir6(sourcePath, { withFileTypes: true })) {
@@ -5373,7 +5416,7 @@ async function copyHostPathToVercelWorkspace(options) {
5373
5416
  return;
5374
5417
  }
5375
5418
  if (!sourceStat.isFile()) return;
5376
- const bytes = new Uint8Array(await readFile8(sourcePath));
5419
+ const bytes = new Uint8Array(await readFile9(sourcePath));
5377
5420
  if (options.workspace.writeBinaryFile) await options.workspace.writeBinaryFile(options.targetRel, bytes);
5378
5421
  else await options.workspace.writeFile(options.targetRel, Buffer.from(bytes).toString("utf8"));
5379
5422
  }
@@ -6159,7 +6202,7 @@ function createPiAgentSessionAdapter(session, options = {}) {
6159
6202
  import { randomUUID } from "crypto";
6160
6203
  import {
6161
6204
  readdir as readdir7,
6162
- readFile as readFile9,
6205
+ readFile as readFile10,
6163
6206
  stat as fsStat,
6164
6207
  rm as rm4,
6165
6208
  mkdir as mkdir11,
@@ -6322,7 +6365,7 @@ var PiSessionStore = class {
6322
6365
  const filepath = await this.resolveSessionFile(sessionId);
6323
6366
  let content;
6324
6367
  try {
6325
- content = await readFile9(filepath, "utf-8");
6368
+ content = await readFile10(filepath, "utf-8");
6326
6369
  } catch {
6327
6370
  throw new Error(`Session not found: ${sessionId}`);
6328
6371
  }
@@ -6403,7 +6446,7 @@ var PiSessionStore = class {
6403
6446
  let filepath = direct;
6404
6447
  let content;
6405
6448
  try {
6406
- content = await readFile9(direct, "utf-8");
6449
+ content = await readFile10(direct, "utf-8");
6407
6450
  } catch {
6408
6451
  const files = await readdir7(this.sessionDir).catch(() => []);
6409
6452
  const match = files.find(
@@ -6411,7 +6454,7 @@ var PiSessionStore = class {
6411
6454
  );
6412
6455
  if (!match) return null;
6413
6456
  filepath = join10(this.sessionDir, match);
6414
- content = await readFile9(filepath, "utf-8");
6457
+ content = await readFile10(filepath, "utf-8");
6415
6458
  }
6416
6459
  const entries = safeParseEntries(content);
6417
6460
  const linkedPiFile = extractPiSessionFilePath(entries);
@@ -6488,7 +6531,7 @@ var PiSessionStore = class {
6488
6531
  }
6489
6532
  async linkedPiFileFor(filepath) {
6490
6533
  try {
6491
- const content = await readFile9(filepath, "utf-8");
6534
+ const content = await readFile10(filepath, "utf-8");
6492
6535
  return extractPiSessionFilePath(safeParseEntries(content));
6493
6536
  } catch {
6494
6537
  return null;
@@ -6496,9 +6539,9 @@ var PiSessionStore = class {
6496
6539
  }
6497
6540
  async referencedPiFiles(files) {
6498
6541
  const referenced = /* @__PURE__ */ new Set();
6499
- await Promise.all(files.map(async ({ filepath, stat: stat11 }) => {
6542
+ await Promise.all(files.map(async ({ filepath, stat: stat12 }) => {
6500
6543
  try {
6501
- const piFilePath = (await this.readPrefixCache(filepath, stat11)).referencedPiFile;
6544
+ const piFilePath = (await this.readPrefixCache(filepath, stat12)).referencedPiFile;
6502
6545
  if (piFilePath && resolve7(piFilePath) !== resolve7(filepath)) {
6503
6546
  referenced.add(resolve7(piFilePath));
6504
6547
  }
@@ -6507,10 +6550,10 @@ var PiSessionStore = class {
6507
6550
  }));
6508
6551
  return referenced;
6509
6552
  }
6510
- async sessionSortMtimeMs({ filepath, stat: stat11 }) {
6511
- let sortMtimeMs = stat11.mtime.getTime();
6553
+ async sessionSortMtimeMs({ filepath, stat: stat12 }) {
6554
+ let sortMtimeMs = stat12.mtime.getTime();
6512
6555
  try {
6513
- const linkedPiFile = (await this.readPrefixCache(filepath, stat11)).referencedPiFile;
6556
+ const linkedPiFile = (await this.readPrefixCache(filepath, stat12)).referencedPiFile;
6514
6557
  if (linkedPiFile && resolve7(linkedPiFile) !== resolve7(filepath)) {
6515
6558
  const linkedStat = await fsStat(linkedPiFile);
6516
6559
  sortMtimeMs = Math.max(sortMtimeMs, linkedStat.mtime.getTime());
@@ -6604,7 +6647,7 @@ var PiSessionStore = class {
6604
6647
  const batch = visibleFiles.slice(index, index + batchSize);
6605
6648
  index += batch.length;
6606
6649
  const summaries = await Promise.all(
6607
- batch.map(({ filepath, stat: stat11 }) => this.summarizeFile(filepath, stat11))
6650
+ batch.map(({ filepath, stat: stat12 }) => this.summarizeFile(filepath, stat12))
6608
6651
  );
6609
6652
  for (const summary of summaries) {
6610
6653
  if (!summary) continue;
@@ -6706,7 +6749,7 @@ var PiSessionStore = class {
6706
6749
  try {
6707
6750
  const [fileStat, content] = await Promise.all([
6708
6751
  fsStat(filepath),
6709
- readFile9(filepath, "utf-8")
6752
+ readFile10(filepath, "utf-8")
6710
6753
  ]);
6711
6754
  return { entries: safeParseEntries(content), mtime: fileStat.mtime, size: Number(fileStat.size) };
6712
6755
  } catch {
@@ -7449,7 +7492,7 @@ ${entry.message}`;
7449
7492
  }
7450
7493
 
7451
7494
  // src/server/harness/pi-coding-agent/pluginLoader.ts
7452
- import { readdir as readdir8, stat as stat9, readFile as readFile10 } from "fs/promises";
7495
+ import { readdir as readdir8, stat as stat10, readFile as readFile11 } from "fs/promises";
7453
7496
  import { join as join12, extname as extname4, resolve as resolve8, sep as sep6, relative as relative9 } from "path";
7454
7497
  import { homedir as homedir4 } from "os";
7455
7498
  import { pathToFileURL as pathToFileURL2 } from "url";
@@ -7459,7 +7502,7 @@ var LOCAL_DIR = ".pi/extensions";
7459
7502
  var EXTENSIONS_JSON = ".pi/extensions.json";
7460
7503
  async function dirExists2(path4) {
7461
7504
  try {
7462
- const s = await stat9(path4);
7505
+ const s = await stat10(path4);
7463
7506
  return s.isDirectory();
7464
7507
  } catch {
7465
7508
  return false;
@@ -7467,7 +7510,7 @@ async function dirExists2(path4) {
7467
7510
  }
7468
7511
  async function fileExists(path4) {
7469
7512
  try {
7470
- const s = await stat9(path4);
7513
+ const s = await stat10(path4);
7471
7514
  return s.isFile();
7472
7515
  } catch {
7473
7516
  return false;
@@ -7518,7 +7561,7 @@ async function discoverNpmPlugins(cwd) {
7518
7561
  async function loadNpmPlugin(pkgDir, importFn) {
7519
7562
  const pkgJsonPath = join12(pkgDir, "package.json");
7520
7563
  if (!await fileExists(pkgJsonPath)) return [];
7521
- const pkgJson = JSON.parse(await readFile10(pkgJsonPath, "utf-8"));
7564
+ const pkgJson = JSON.parse(await readFile11(pkgJsonPath, "utf-8"));
7522
7565
  const main = pkgJson.main ?? "index.js";
7523
7566
  const resolvedMain = resolve8(pkgDir, main);
7524
7567
  const resolvedPkgDir = resolve8(pkgDir);
@@ -7535,7 +7578,7 @@ async function loadExtensionsJson(cwd) {
7535
7578
  const configPath = join12(cwd, EXTENSIONS_JSON);
7536
7579
  if (!await fileExists(configPath)) return null;
7537
7580
  try {
7538
- const raw = await readFile10(configPath, "utf-8");
7581
+ const raw = await readFile11(configPath, "utf-8");
7539
7582
  return JSON.parse(raw);
7540
7583
  } catch {
7541
7584
  return null;
@@ -7609,7 +7652,7 @@ import {
7609
7652
 
7610
7653
  // src/server/tools/operations/bound.ts
7611
7654
  import { constants as constants4 } from "fs";
7612
- import { access as access4, lstat as lstat4, mkdir as mkdir12, readFile as readFile11, readdir as readdir9, readlink, realpath as realpath4, stat as stat10, writeFile as writeFile7 } from "fs/promises";
7655
+ import { access as access4, lstat as lstat4, mkdir as mkdir12, readFile as readFile12, readdir as readdir9, readlink, realpath as realpath4, stat as stat11, writeFile as writeFile7 } from "fs/promises";
7613
7656
  import { dirname as dirname11, isAbsolute as isAbsolute7, join as join13, relative as relative10, resolve as resolve9 } from "path";
7614
7657
  function toPosixPath(value) {
7615
7658
  return value.split("\\").join("/");
@@ -7681,7 +7724,7 @@ async function findNearestExistingAncestor(absPath) {
7681
7724
  let current = absPath;
7682
7725
  for (; ; ) {
7683
7726
  try {
7684
- await stat10(current);
7727
+ await stat11(current);
7685
7728
  return current;
7686
7729
  } catch {
7687
7730
  const parent = dirname11(current);
@@ -7756,7 +7799,7 @@ function boundFs(workspaceRoot, opts = {}) {
7756
7799
  async readFile(absolutePath) {
7757
7800
  const storagePath = toStoragePath(absolutePath);
7758
7801
  await assertWithinWorkspace(workspaceRoot, storagePath);
7759
- return await readFile11(storagePath);
7802
+ return await readFile12(storagePath);
7760
7803
  },
7761
7804
  async access(absolutePath) {
7762
7805
  const storagePath = toStoragePath(absolutePath);
@@ -7781,7 +7824,7 @@ function boundFs(workspaceRoot, opts = {}) {
7781
7824
  async readFile(absolutePath) {
7782
7825
  const storagePath = toStoragePath(absolutePath);
7783
7826
  await assertWithinWorkspace(workspaceRoot, storagePath);
7784
- return await readFile11(storagePath);
7827
+ return await readFile12(storagePath);
7785
7828
  },
7786
7829
  async writeFile(absolutePath, content) {
7787
7830
  const storagePath = toStoragePath(absolutePath);
@@ -7799,7 +7842,7 @@ function boundFs(workspaceRoot, opts = {}) {
7799
7842
  const storagePath = toStoragePath(absolutePath);
7800
7843
  await assertWithinWorkspace(workspaceRoot, storagePath);
7801
7844
  try {
7802
- await stat10(storagePath);
7845
+ await stat11(storagePath);
7803
7846
  return true;
7804
7847
  } catch (err) {
7805
7848
  if (err.code === "ENOENT") return false;
@@ -7818,12 +7861,12 @@ function boundFs(workspaceRoot, opts = {}) {
7818
7861
  async isDirectory(absolutePath) {
7819
7862
  const storagePath = toStoragePath(absolutePath);
7820
7863
  await assertWithinWorkspace(workspaceRoot, storagePath);
7821
- return (await stat10(storagePath)).isDirectory();
7864
+ return (await stat11(storagePath)).isDirectory();
7822
7865
  },
7823
7866
  async readFile(absolutePath) {
7824
7867
  const storagePath = toStoragePath(absolutePath);
7825
7868
  await assertWithinWorkspace(workspaceRoot, storagePath);
7826
- return await readFile11(storagePath, "utf8");
7869
+ return await readFile12(storagePath, "utf8");
7827
7870
  }
7828
7871
  };
7829
7872
  const ls = {
@@ -7831,7 +7874,7 @@ function boundFs(workspaceRoot, opts = {}) {
7831
7874
  const storagePath = toStoragePath(absolutePath);
7832
7875
  try {
7833
7876
  await assertWithinWorkspace(workspaceRoot, storagePath);
7834
- await stat10(storagePath);
7877
+ await stat11(storagePath);
7835
7878
  return true;
7836
7879
  } catch {
7837
7880
  return false;
@@ -7840,7 +7883,7 @@ function boundFs(workspaceRoot, opts = {}) {
7840
7883
  async stat(absolutePath) {
7841
7884
  const storagePath = toStoragePath(absolutePath);
7842
7885
  await assertWithinWorkspace(workspaceRoot, storagePath);
7843
- return await stat10(storagePath);
7886
+ return await stat11(storagePath);
7844
7887
  },
7845
7888
  async readdir(absolutePath) {
7846
7889
  const storagePath = toStoragePath(absolutePath);
@@ -8023,8 +8066,8 @@ function remoteWorkspaceLsOps(workspace, opts = {}) {
8023
8066
  },
8024
8067
  async stat(absolutePath) {
8025
8068
  const rel = toRelPath(workspace, absolutePath, opts);
8026
- const stat11 = await workspace.stat(rel);
8027
- return { isDirectory: () => stat11.kind === "dir" };
8069
+ const stat12 = await workspace.stat(rel);
8070
+ return { isDirectory: () => stat12.kind === "dir" };
8028
8071
  },
8029
8072
  async readdir(absolutePath) {
8030
8073
  const rel = toRelPath(workspace, absolutePath, opts);
@@ -9092,49 +9135,53 @@ import {
9092
9135
  var CACHE_TTL_MS2 = 3e4;
9093
9136
  function skillsRoutes(app, opts, done) {
9094
9137
  const cached = /* @__PURE__ */ new Map();
9095
- app.get("/api/v1/agent/skills", async (request, reply) => {
9096
- try {
9097
- const workspaceRoot = opts.getWorkspaceRoot ? await opts.getWorkspaceRoot(request) : opts.workspaceRoot;
9098
- const additionalSkillPaths = opts.getAdditionalSkillPaths ? await opts.getAdditionalSkillPaths(request) : opts.additionalSkillPaths;
9099
- const piPackages = opts.getPiPackages ? await opts.getPiPackages(request) : opts.piPackages;
9100
- const noSkills = (opts.getNoSkills ? await opts.getNoSkills(request) : opts.noSkills) ?? withPiHarnessDefaults().noSkills;
9101
- const cacheKey = JSON.stringify([workspaceRoot, additionalSkillPaths ?? [], piPackages ?? [], noSkills]);
9102
- const now = Date.now();
9103
- for (const [key, entry] of cached) {
9104
- if (entry.expiresAt <= now) cached.delete(key);
9105
- }
9106
- const cachedEntry = cached.get(cacheKey);
9107
- const refresh = request.query.refresh === "1";
9108
- if (!refresh && cachedEntry && cachedEntry.expiresAt > now) {
9109
- return reply.code(200).send({ skills: cachedEntry.skills });
9110
- }
9111
- const agentDir = getAgentDir3();
9112
- const packageSkillPaths = noSkills ? [] : await (async () => {
9113
- const settingsManager = createResourceSettingsManager(
9114
- workspaceRoot,
9115
- agentDir,
9116
- piPackages ?? []
9117
- );
9118
- const packageManager = new DefaultPackageManager({
9119
- cwd: workspaceRoot,
9120
- agentDir,
9121
- settingsManager
9122
- });
9123
- const resolved = await packageManager.resolve();
9124
- return resolved.skills.filter((resource) => resource.enabled).map((resource) => resource.path);
9125
- })();
9126
- const result = loadSkills2({
9138
+ async function resolveSkillsForRequest(request, refresh = false) {
9139
+ const workspaceRoot = opts.getWorkspaceRoot ? await opts.getWorkspaceRoot(request) : opts.workspaceRoot;
9140
+ const additionalSkillPaths = opts.getAdditionalSkillPaths ? await opts.getAdditionalSkillPaths(request) : opts.additionalSkillPaths;
9141
+ const piPackages = opts.getPiPackages ? await opts.getPiPackages(request) : opts.piPackages;
9142
+ const noSkills = (opts.getNoSkills ? await opts.getNoSkills(request) : opts.noSkills) ?? withPiHarnessDefaults().noSkills;
9143
+ const cacheKey = JSON.stringify([workspaceRoot, additionalSkillPaths ?? [], piPackages ?? [], noSkills]);
9144
+ const now = Date.now();
9145
+ for (const [key, entry2] of cached) {
9146
+ if (entry2.expiresAt <= now) cached.delete(key);
9147
+ }
9148
+ const cachedEntry = cached.get(cacheKey);
9149
+ if (!refresh && cachedEntry && cachedEntry.expiresAt > now) return cachedEntry;
9150
+ const agentDir = getAgentDir3();
9151
+ const packageSkillPaths = noSkills ? [] : await (async () => {
9152
+ const settingsManager = createResourceSettingsManager(
9153
+ workspaceRoot,
9154
+ agentDir,
9155
+ piPackages ?? []
9156
+ );
9157
+ const packageManager = new DefaultPackageManager({
9127
9158
  cwd: workspaceRoot,
9128
9159
  agentDir,
9129
- skillPaths: [...packageSkillPaths, ...additionalSkillPaths ?? []],
9130
- includeDefaults: !noSkills
9160
+ settingsManager
9131
9161
  });
9132
- const skills = result.skills.map((s) => ({
9133
- name: s.name,
9134
- description: s.description
9135
- }));
9136
- cached.set(cacheKey, { skills, expiresAt: now + CACHE_TTL_MS2 });
9137
- return reply.code(200).send({ skills });
9162
+ const resolved = await packageManager.resolve();
9163
+ return resolved.skills.filter((resource) => resource.enabled).map((resource) => resource.path);
9164
+ })();
9165
+ const result = loadSkills2({
9166
+ cwd: workspaceRoot,
9167
+ agentDir,
9168
+ skillPaths: [...packageSkillPaths, ...additionalSkillPaths ?? []],
9169
+ includeDefaults: !noSkills
9170
+ });
9171
+ const skills = result.skills.map((s) => ({
9172
+ name: String(s.name),
9173
+ description: String(s.description ?? ""),
9174
+ ...typeof s.filePath === "string" ? { filePath: s.filePath } : {},
9175
+ ...typeof s.sourceInfo?.scope === "string" ? { source: s.sourceInfo.scope } : {}
9176
+ }));
9177
+ const entry = { skills, expiresAt: now + CACHE_TTL_MS2 };
9178
+ cached.set(cacheKey, entry);
9179
+ return entry;
9180
+ }
9181
+ app.get("/api/v1/agent/skills", async (request, reply) => {
9182
+ try {
9183
+ const entry = await resolveSkillsForRequest(request, request.query.refresh === "1");
9184
+ return reply.code(200).send({ skills: entry.skills });
9138
9185
  } catch (error) {
9139
9186
  const message = error instanceof Error ? error.message : String(error);
9140
9187
  request.log.warn({ err: error }, "[agent] failed to load skills");
@@ -12075,8 +12122,8 @@ async function promptImagesFromAttachments(attachments, workspace) {
12075
12122
  }
12076
12123
  if (!workspace?.readBinaryFile || !isWorkspaceImageAttachment(attachment)) continue;
12077
12124
  try {
12078
- const stat11 = await workspace.stat(attachment.path);
12079
- if (stat11.kind !== "file" || stat11.size <= 0 || stat11.size > MAX_PROMPT_IMAGE_BYTES) continue;
12125
+ const stat12 = await workspace.stat(attachment.path);
12126
+ if (stat12.kind !== "file" || stat12.size <= 0 || stat12.size > MAX_PROMPT_IMAGE_BYTES) continue;
12080
12127
  const bytes = await workspace.readBinaryFile(attachment.path);
12081
12128
  if (bytes.byteLength <= 0 || bytes.byteLength > MAX_PROMPT_IMAGE_BYTES) continue;
12082
12129
  const detectedMimeType = detectPromptImageMimeType(bytes);
@@ -12376,7 +12423,7 @@ function mergeTools(options) {
12376
12423
  }
12377
12424
 
12378
12425
  // src/server/tools/upload/index.ts
12379
- import { readFile as readFile12 } from "fs/promises";
12426
+ import { readFile as readFile13 } from "fs/promises";
12380
12427
  import { extname as extname5, join as join14 } from "path";
12381
12428
  var DEFAULT_UPLOAD_DIR = "assets/images";
12382
12429
  var MAX_UPLOAD_BYTES2 = 10 * 1024 * 1024;
@@ -12445,7 +12492,7 @@ function buildUploadAgentTools(bundle) {
12445
12492
  const rawDir = typeof input.directory === "string" ? input.directory.trim() : "";
12446
12493
  const dir = rawDir ? rawDir.replace(/^\.\/+/, "").replace(/\/+$/, "") : DEFAULT_UPLOAD_DIR;
12447
12494
  try {
12448
- const bytes = workspace.readBinaryFile ? Buffer.from(await workspace.readBinaryFile(filePath)) : await readFile12(join14(getRuntimeBundleStorageRoot(bundle), filePath));
12495
+ const bytes = workspace.readBinaryFile ? Buffer.from(await workspace.readBinaryFile(filePath)) : await readFile13(join14(getRuntimeBundleStorageRoot(bundle), filePath));
12449
12496
  if (bytes.byteLength === 0 || bytes.byteLength > MAX_UPLOAD_BYTES2) {
12450
12497
  return {
12451
12498
  content: [{ type: "text", text: `file must be between 1 byte and ${MAX_UPLOAD_BYTES2} bytes` }],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hachej/boring-agent",
3
- "version": "0.1.59",
3
+ "version": "0.1.60",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "description": "Pane-embeddable coding agent. Ships direct/local/vercel-sandbox execution modes behind one interface.",
@@ -74,7 +74,7 @@
74
74
  "use-stick-to-bottom": "^1.1.3",
75
75
  "yaml": "^2.8.3",
76
76
  "zod": "^3.25.76",
77
- "@hachej/boring-ui-kit": "0.1.59"
77
+ "@hachej/boring-ui-kit": "0.1.60"
78
78
  },
79
79
  "devDependencies": {
80
80
  "@antithesishq/bombadil": "0.5.0",