@cydm/pie 1.0.48 → 1.0.49

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.
@@ -1,7 +1,7 @@
1
1
  import { createRequire as __createRequire } from "node:module"; const require = __createRequire(import.meta.url);
2
2
  import {
3
3
  createAskUserCapability
4
- } from "../../../chunks/chunk-IJMP3JTX.js";
4
+ } from "../../../chunks/chunk-7RYOIXA2.js";
5
5
  import "../../../chunks/chunk-FKAO25XH.js";
6
6
  import "../../../chunks/chunk-K6ZOCJ4V.js";
7
7
  import "../../../chunks/chunk-LZQXQOPJ.js";
@@ -7,7 +7,7 @@ import {
7
7
  isPlanModeSafeCommand,
8
8
  markCompletedPlanSteps,
9
9
  restoreExecutionState
10
- } from "../../../chunks/chunk-IJMP3JTX.js";
10
+ } from "../../../chunks/chunk-7RYOIXA2.js";
11
11
  import "../../../chunks/chunk-FKAO25XH.js";
12
12
  import {
13
13
  Type
@@ -1,11 +1,11 @@
1
1
  import { createRequire as __createRequire } from "node:module"; const require = __createRequire(import.meta.url);
2
2
  import {
3
3
  createCliHostCapabilities
4
- } from "../../../chunks/chunk-U4B6RJ6Z.js";
4
+ } from "../../../chunks/chunk-J6PEH6ST.js";
5
5
  import {
6
6
  createSharedFileSystemTools,
7
7
  createSubagentCapability
8
- } from "../../../chunks/chunk-IJMP3JTX.js";
8
+ } from "../../../chunks/chunk-7RYOIXA2.js";
9
9
  import "../../../chunks/chunk-FKAO25XH.js";
10
10
  import "../../../chunks/chunk-K6ZOCJ4V.js";
11
11
  import "../../../chunks/chunk-LZQXQOPJ.js";
@@ -7,7 +7,7 @@ import {
7
7
  executeManageTodoList,
8
8
  executionStateToTodos,
9
9
  restoreExecutionState
10
- } from "../../../chunks/chunk-IJMP3JTX.js";
10
+ } from "../../../chunks/chunk-7RYOIXA2.js";
11
11
  import "../../../chunks/chunk-FKAO25XH.js";
12
12
  import "../../../chunks/chunk-K6ZOCJ4V.js";
13
13
  import "../../../chunks/chunk-LZQXQOPJ.js";
@@ -7869,6 +7869,193 @@ function fuzzyFindText(content, oldText) {
7869
7869
  function stripBom(content) {
7870
7870
  return content.startsWith("\uFEFF") ? { bom: "\uFEFF", text: content.slice(1) } : { bom: "", text: content };
7871
7871
  }
7872
+ function findEditMatch(content, oldText, newText) {
7873
+ const direct = fuzzyFindText(content, oldText);
7874
+ if (direct.found) {
7875
+ return { found: true, occurrences: direct.occurrences, matches: direct.matches, matchKind: direct.matchKind };
7876
+ }
7877
+ return findWhitespaceRelaxedMatch(content, oldText, newText);
7878
+ }
7879
+ function leadingWhitespace(line) {
7880
+ const match2 = line.match(/^[ \t]*/);
7881
+ return match2 ? match2[0] : "";
7882
+ }
7883
+ function findRelaxedWindows(fileLines, qLines) {
7884
+ const firstNonBlank = qLines.findIndex((line) => line.trim() !== "");
7885
+ if (firstNonBlank === -1) return [];
7886
+ const anchor = qLines[firstNonBlank].trim();
7887
+ if (!anchor) return [];
7888
+ const windows = [];
7889
+ for (let anchorFileLine = 0; anchorFileLine < fileLines.length; anchorFileLine++) {
7890
+ if (fileLines[anchorFileLine].trim() !== anchor) continue;
7891
+ const startLine = anchorFileLine - firstNonBlank;
7892
+ if (startLine < 0) continue;
7893
+ let fileLine = startLine;
7894
+ let ok = true;
7895
+ let lastPaired = -1;
7896
+ const qLineToFileLine = /* @__PURE__ */ new Map();
7897
+ for (let qLine = 0; qLine < qLines.length; qLine++) {
7898
+ if (fileLine >= fileLines.length) {
7899
+ ok = false;
7900
+ break;
7901
+ }
7902
+ const q = qLines[qLine];
7903
+ const f = fileLines[fileLine];
7904
+ if (q.trim() === "") {
7905
+ if (f.trim() !== "") {
7906
+ ok = false;
7907
+ break;
7908
+ }
7909
+ } else {
7910
+ if (f.trim() !== q.trim()) {
7911
+ ok = false;
7912
+ break;
7913
+ }
7914
+ qLineToFileLine.set(qLine, fileLine);
7915
+ lastPaired = fileLine;
7916
+ }
7917
+ fileLine++;
7918
+ }
7919
+ if (ok && lastPaired !== -1) windows.push({ startLine, endLine: lastPaired, qLineToFileLine });
7920
+ }
7921
+ return windows;
7922
+ }
7923
+ function lineSpanToCharSpan(fileLines, startLine, endLine) {
7924
+ let index = 0;
7925
+ for (let i = 0; i < startLine; i++) index += fileLines[i].length + 1;
7926
+ let matchLength = 0;
7927
+ for (let i = startLine; i <= endLine; i++) matchLength += fileLines[i].length + (i < endLine ? 1 : 0);
7928
+ return { index, matchLength };
7929
+ }
7930
+ function reindentNewText(newText, qLines, fileLines, window) {
7931
+ const newLines = newText.split("\n");
7932
+ const qIndent = (qLine) => {
7933
+ const fileLine = window.qLineToFileLine.get(qLine);
7934
+ return fileLine === void 0 ? "" : leadingWhitespace(fileLines[fileLine]);
7935
+ };
7936
+ const qIdx = [];
7937
+ for (let i = 0; i < qLines.length; i++) if (qLines[i].trim() !== "") qIdx.push(i);
7938
+ const nIdx = [];
7939
+ const nPosOfLine = /* @__PURE__ */ new Map();
7940
+ for (let i = 0; i < newLines.length; i++) {
7941
+ if (newLines[i].trim() === "") continue;
7942
+ nPosOfLine.set(i, nIdx.length);
7943
+ nIdx.push(i);
7944
+ }
7945
+ if (nIdx.length === 0) return newText;
7946
+ const counterpart = /* @__PURE__ */ new Map();
7947
+ let p = 0;
7948
+ while (p < qIdx.length && p < nIdx.length && qLines[qIdx[p]].trim() === newLines[nIdx[p]].trim()) {
7949
+ counterpart.set(p, p);
7950
+ p++;
7951
+ }
7952
+ let s = 0;
7953
+ while (s < qIdx.length - p && s < nIdx.length - p && qLines[qIdx[qIdx.length - 1 - s]].trim() === newLines[nIdx[nIdx.length - 1 - s]].trim()) {
7954
+ counterpart.set(nIdx.length - 1 - s, qIdx.length - 1 - s);
7955
+ s++;
7956
+ }
7957
+ const midQ = [];
7958
+ for (let pos = p; pos < qIdx.length - s; pos++) midQ.push(pos);
7959
+ const midN = [];
7960
+ for (let pos = p; pos < nIdx.length - s; pos++) midN.push(pos);
7961
+ const pairRegion = (qLo, qHi, nLo, nHi) => {
7962
+ const qCount = qHi - qLo;
7963
+ const nCount = nHi - nLo;
7964
+ const paired = Math.min(qCount, nCount);
7965
+ for (let k = 0; k < paired; k++) counterpart.set(nLo + k, qLo + k);
7966
+ const fallbackQ = qCount > 0 ? qHi - 1 : Math.max(qLo - 1, 0);
7967
+ for (let k = paired; k < nCount; k++) counterpart.set(nLo + k, fallbackQ);
7968
+ };
7969
+ const qTextCounts = /* @__PURE__ */ new Map();
7970
+ for (const qPos of midQ) {
7971
+ const t = qLines[qIdx[qPos]].trim();
7972
+ qTextCounts.set(t, (qTextCounts.get(t) ?? 0) + 1);
7973
+ }
7974
+ const qPosByText = /* @__PURE__ */ new Map();
7975
+ for (const qPos of midQ) {
7976
+ const t = qLines[qIdx[qPos]].trim();
7977
+ if (qTextCounts.get(t) === 1) qPosByText.set(t, qPos);
7978
+ }
7979
+ const nTextCounts = /* @__PURE__ */ new Map();
7980
+ for (const nPos of midN) {
7981
+ const t = newLines[nIdx[nPos]].trim();
7982
+ nTextCounts.set(t, (nTextCounts.get(t) ?? 0) + 1);
7983
+ }
7984
+ let cursorQ = p;
7985
+ let cursorN = p;
7986
+ for (const nPos of midN) {
7987
+ const t = newLines[nIdx[nPos]].trim();
7988
+ const qPos = qPosByText.get(t);
7989
+ if (nTextCounts.get(t) !== 1 || qPos === void 0 || qPos < cursorQ) continue;
7990
+ pairRegion(cursorQ, qPos, cursorN, nPos);
7991
+ counterpart.set(nPos, qPos);
7992
+ cursorQ = qPos + 1;
7993
+ cursorN = nPos + 1;
7994
+ }
7995
+ pairRegion(cursorQ, qIdx.length - s, cursorN, nIdx.length - s);
7996
+ const result = [];
7997
+ for (let i = 0; i < newLines.length; i++) {
7998
+ if (newLines[i].trim() === "") {
7999
+ result.push("");
8000
+ continue;
8001
+ }
8002
+ const nPos = nPosOfLine.get(i) ?? 0;
8003
+ const qPos = counterpart.get(nPos) ?? 0;
8004
+ result.push(qIndent(qIdx[qPos] ?? qIdx[0]) + newLines[i].trim());
8005
+ }
8006
+ return result.join("\n");
8007
+ }
8008
+ function findWhitespaceRelaxedMatch(content, oldText, newText) {
8009
+ const fileLines = content.split("\n");
8010
+ const qLines = oldText.split("\n");
8011
+ const windows = findRelaxedWindows(fileLines, qLines);
8012
+ if (windows.length === 0) {
8013
+ return { found: false, occurrences: 0, matches: [], matchKind: "none" };
8014
+ }
8015
+ const matches = windows.map((window) => {
8016
+ const span = lineSpanToCharSpan(fileLines, window.startLine, window.endLine);
8017
+ return { ...span, replacementText: reindentNewText(newText, qLines, fileLines, window) };
8018
+ });
8019
+ return { found: true, occurrences: matches.length, matches, matchKind: "whitespace-relaxed" };
8020
+ }
8021
+ function describeClosestRegion(content, oldText) {
8022
+ const qLines = oldText.split("\n");
8023
+ const firstNonBlank = qLines.findIndex((line) => line.trim() !== "");
8024
+ if (firstNonBlank === -1) return void 0;
8025
+ const anchor = qLines[firstNonBlank].trim();
8026
+ const fileLines = content.split("\n");
8027
+ const clip = (text) => text.length > 90 ? `${text.slice(0, 90)}\u2026` : text;
8028
+ let best;
8029
+ for (let i = 0; i < fileLines.length; i++) {
8030
+ if (fileLines[i].trim() !== anchor) continue;
8031
+ let matched = 0;
8032
+ let fi = i;
8033
+ let firstDiff;
8034
+ for (let qi = firstNonBlank; qi < qLines.length; qi++) {
8035
+ const q = qLines[qi].trim();
8036
+ if (!q) continue;
8037
+ while (fi < fileLines.length && !fileLines[fi].trim()) fi++;
8038
+ if (fi >= fileLines.length || fileLines[fi].trim() !== q) {
8039
+ firstDiff = { fileNo: fi + 1, fileLine: fileLines[fi] ?? "<end of file>", qLine: qLines[qi] };
8040
+ break;
8041
+ }
8042
+ matched++;
8043
+ fi++;
8044
+ }
8045
+ if (!best || matched > best.matched) best = { start: i, matched, firstDiff };
8046
+ }
8047
+ const total = qLines.filter((line) => line.trim() !== "").length;
8048
+ if (!best) {
8049
+ return `No line matching ${JSON.stringify(clip(anchor))} exists in this file \u2014 the text may live in a different file, or the file changed since it was read.`;
8050
+ }
8051
+ const parts = [`Closest match: file lines ${best.start + 1}\u2013${best.start + total} (${best.matched}/${total} lines matched by trimmed content).`];
8052
+ if (best.firstDiff) {
8053
+ parts.push(`First difference at line ${best.firstDiff.fileNo}: file has ${JSON.stringify(clip(best.firstDiff.fileLine.trim()))}, edit expected ${JSON.stringify(clip(best.firstDiff.qLine.trim()))}.`);
8054
+ } else {
8055
+ parts.push("All non-empty lines match by trimmed content \u2014 the mismatch is leading whitespace or blank-line placement.");
8056
+ }
8057
+ return parts.join(" ");
8058
+ }
7872
8059
  function generateDiffString(oldContent, newContent, contextLines = 4) {
7873
8060
  const oldLines = normalizeToLF(stripBom(oldContent).text).split("\n");
7874
8061
  const newLines = normalizeToLF(stripBom(newContent).text).split("\n");
@@ -7982,7 +8169,8 @@ function createEditErrorResult(params) {
7982
8169
  const detailLines = [
7983
8170
  params.message,
7984
8171
  params.recoveryHint,
7985
- params.alreadyAppliedCandidate ? "Diagnostic: newText already appears in the current file, but edit_file did not treat that as success because it may be an unrelated occurrence." : ""
8172
+ params.alreadyAppliedCandidate ? "Diagnostic: newText already appears in the current file, but edit_file did not treat that as success because it may be an unrelated occurrence." : "",
8173
+ params.closestMatch ? `Diagnostic: ${params.closestMatch}` : ""
7986
8174
  ].filter(Boolean);
7987
8175
  return {
7988
8176
  content: [{ type: "text", text: detailLines.join("\n") }],
@@ -8034,7 +8222,7 @@ function createEditTool(cwd, options) {
8034
8222
  newText: Type.String({ description: "Replacement text", maxLength: MAX_EDIT_INPUT_CHARS }),
8035
8223
  replaceAll: Type.Optional(Type.Boolean({ description: "Replace every occurrence" }))
8036
8224
  }), {
8037
- description: "One or more independent replacements matched against the original file",
8225
+ description: "Replacements applied in order: each edit matches the file as left by the previous edit, so later edits can build on earlier ones. Whole-line indentation drift is tolerated \u2014 matches locate by trimmed content and the replacement adopts the file's indentation.",
8038
8226
  minItems: 1,
8039
8227
  maxItems: MAX_EDIT_BLOCKS
8040
8228
  })
@@ -8043,7 +8231,7 @@ function createEditTool(cwd, options) {
8043
8231
  return {
8044
8232
  name: "edit",
8045
8233
  label: "edit",
8046
- description: `Make precise exact-text replacements in one file. For multiple changes in the same file, put all independent replacements in one edits[] call. ${pathResolutionDescription}`,
8234
+ description: `Make precise exact-text replacements in one file. For multiple changes in the same file, put all replacements in one edits[] call \u2014 they apply in order, so later edits can target what earlier ones just changed. ${pathResolutionDescription}`,
8047
8235
  parameters: editSchema,
8048
8236
  execute: async (_toolCallId, params, signal) => {
8049
8237
  const { path: path4, root } = params;
@@ -8087,7 +8275,9 @@ function createEditTool(cwd, options) {
8087
8275
  const { bom, text: content } = stripBom(rawContent);
8088
8276
  const originalEnding = detectLineEnding(content);
8089
8277
  const normalizedContent = normalizeToLF(content);
8090
- const planned = [];
8278
+ let workingContent = normalizedContent;
8279
+ const plannedKinds = /* @__PURE__ */ new Set();
8280
+ let replacedOccurrences = 0;
8091
8281
  let totalOldOccurrences = 0;
8092
8282
  let totalNewOccurrences = 0;
8093
8283
  let anyReplaceAll = false;
@@ -8095,8 +8285,8 @@ function createEditTool(cwd, options) {
8095
8285
  throwIfAborted();
8096
8286
  const normalizedOldText = normalizeToLF(replacement.oldText);
8097
8287
  const normalizedNewText = normalizeToLF(replacement.newText);
8098
- const newTextOccurrences = countOccurrences(normalizedContent, normalizedNewText);
8099
- const matchResult = fuzzyFindText(normalizedContent, normalizedOldText);
8288
+ const newTextOccurrences = countOccurrences(workingContent, normalizedNewText);
8289
+ const matchResult = findEditMatch(workingContent, normalizedOldText, normalizedNewText);
8100
8290
  totalOldOccurrences += matchResult.occurrences;
8101
8291
  totalNewOccurrences += newTextOccurrences;
8102
8292
  anyReplaceAll ||= replacement.replaceAll;
@@ -8111,7 +8301,8 @@ function createEditTool(cwd, options) {
8111
8301
  replaceAll: replacement.replaceAll,
8112
8302
  staleBaseLikely: newTextOccurrences > 0,
8113
8303
  alreadyAppliedCandidate: newTextOccurrences > 0,
8114
- recoveryHint: newTextOccurrences > 0 ? "The replacement may already be present. Read the current file before retrying." : "Read the latest file and retry with exact text."
8304
+ recoveryHint: newTextOccurrences > 0 ? "The replacement may already be present. Read the current file before retrying." : "Read the latest file and retry with exact text.",
8305
+ closestMatch: describeClosestRegion(workingContent, normalizedOldText)
8115
8306
  }));
8116
8307
  return;
8117
8308
  }
@@ -8128,9 +8319,9 @@ function createEditTool(cwd, options) {
8128
8319
  }));
8129
8320
  return;
8130
8321
  }
8131
- const matches = replacement.replaceAll ? matchResult.matches : matchResult.matches.slice(0, 1);
8132
- const changingMatches = matches.filter(
8133
- (match2) => normalizedContent.slice(match2.index, match2.index + match2.matchLength) !== normalizedNewText
8322
+ const selected = replacement.replaceAll ? matchResult.matches : matchResult.matches.slice(0, 1);
8323
+ const changingMatches = selected.filter(
8324
+ (match2) => workingContent.slice(match2.index, match2.index + match2.matchLength) !== (match2.replacementText ?? normalizedNewText)
8134
8325
  );
8135
8326
  if (changingMatches.length === 0) {
8136
8327
  if (signal) signal.removeEventListener("abort", onAbort);
@@ -8146,42 +8337,20 @@ function createEditTool(cwd, options) {
8146
8337
  }));
8147
8338
  return;
8148
8339
  }
8149
- for (const match2 of changingMatches) {
8150
- planned.push({ ...match2, newText: normalizedNewText, matchKind: matchResult.matchKind });
8151
- }
8152
- }
8153
- const ordered = [...planned].sort((left, right) => left.index - right.index);
8154
- for (let index = 1; index < ordered.length; index++) {
8155
- throwIfAborted();
8156
- const previous = ordered[index - 1];
8157
- const current = ordered[index];
8158
- if (current.index < previous.index + previous.matchLength) {
8159
- if (signal) signal.removeEventListener("abort", onAbort);
8160
- resolve3(createEditErrorResult({
8161
- message: `Edits overlap in ${path4}; no changes were made.`,
8162
- matchKind: "mixed",
8163
- errorReason: "overlapping_edits",
8164
- oldTextOccurrences: totalOldOccurrences,
8165
- newTextOccurrences: totalNewOccurrences,
8166
- replaceAll: anyReplaceAll,
8167
- recoveryHint: "Merge overlapping replacements into one exact edit."
8168
- }));
8169
- return;
8340
+ for (const match2 of [...changingMatches].sort((left, right) => right.index - left.index)) {
8341
+ throwIfAborted();
8342
+ workingContent = workingContent.substring(0, match2.index) + (match2.replacementText ?? normalizedNewText) + workingContent.substring(match2.index + match2.matchLength);
8343
+ replacedOccurrences++;
8344
+ if (matchResult.matchKind !== "none") plannedKinds.add(matchResult.matchKind);
8170
8345
  }
8171
8346
  }
8172
8347
  throwIfAborted();
8173
- const baseContent = normalizedContent;
8174
- let newContent = baseContent;
8175
- for (const match2 of [...planned].sort((left, right) => right.index - left.index)) {
8176
- throwIfAborted();
8177
- newContent = newContent.substring(0, match2.index) + match2.newText + newContent.substring(match2.index + match2.matchLength);
8178
- }
8179
- if (baseContent === newContent) {
8348
+ if (workingContent === normalizedContent) {
8180
8349
  if (signal) signal.removeEventListener("abort", onAbort);
8181
8350
  resolve3(
8182
8351
  createEditErrorResult({
8183
8352
  message: `No changes made to ${path4}. The replacement produced identical content.`,
8184
- matchKind: planned.every((match2) => match2.matchKind === planned[0]?.matchKind) ? planned[0]?.matchKind ?? "none" : "mixed",
8353
+ matchKind: plannedKinds.size === 1 ? [...plannedKinds][0] : "mixed",
8185
8354
  errorReason: "replacement_no_change",
8186
8355
  oldTextOccurrences: totalOldOccurrences,
8187
8356
  newTextOccurrences: totalNewOccurrences,
@@ -8192,6 +8361,7 @@ function createEditTool(cwd, options) {
8192
8361
  );
8193
8362
  return;
8194
8363
  }
8364
+ const newContent = workingContent;
8195
8365
  const finalContent = bom + restoreLineEndings(newContent, originalEnding);
8196
8366
  throwIfAborted();
8197
8367
  await fs2.writeFile(absolutePath, finalContent, "utf-8");
@@ -8201,13 +8371,12 @@ function createEditTool(cwd, options) {
8201
8371
  signal.removeEventListener("abort", onAbort);
8202
8372
  }
8203
8373
  const diffResult = generateDiffString(content, restoreLineEndings(newContent, originalEnding));
8204
- const matchKinds = new Set(planned.map((match2) => match2.matchKind));
8205
- const matchKind = matchKinds.size === 1 ? planned[0]?.matchKind ?? "none" : "mixed";
8374
+ const matchKind = plannedKinds.size === 1 ? [...plannedKinds][0] : "mixed";
8206
8375
  resolve3({
8207
8376
  content: [
8208
8377
  {
8209
8378
  type: "text",
8210
- text: `Changed ${path4}: ${replacements.length} edit block${replacements.length === 1 ? "" : "s"}, ${planned.length} replacement${planned.length === 1 ? "" : "s"}. First changed line: ${diffResult.firstChangedLine}.`
8379
+ text: `Changed ${path4}: ${replacements.length} edit block${replacements.length === 1 ? "" : "s"}, ${replacedOccurrences} replacement${replacedOccurrences === 1 ? "" : "s"}. First changed line: ${diffResult.firstChangedLine}.`
8211
8380
  },
8212
8381
  { type: "text", text: boundedDiff(diffResult.diff) }
8213
8382
  ],
@@ -8221,7 +8390,7 @@ function createEditTool(cwd, options) {
8221
8390
  oldTextOccurrences: totalOldOccurrences,
8222
8391
  newTextOccurrences: totalNewOccurrences,
8223
8392
  replaceAll: anyReplaceAll,
8224
- replacedOccurrences: planned.length,
8393
+ replacedOccurrences,
8225
8394
  replacedBlocks: replacements.length
8226
8395
  }
8227
8396
  });
@@ -8,7 +8,7 @@ import {
8
8
  createSharedWebSearchTool,
9
9
  interpretShellExit,
10
10
  requestInteraction
11
- } from "./chunk-IJMP3JTX.js";
11
+ } from "./chunk-7RYOIXA2.js";
12
12
  import {
13
13
  Type
14
14
  } from "./chunk-K6ZOCJ4V.js";
package/dist/cli.js CHANGED
@@ -19,7 +19,10 @@ import {
19
19
  getThemesDir,
20
20
  migrateConfigFromAgentDir,
21
21
  truncateHeadTail
22
- } from "./chunks/chunk-U4B6RJ6Z.js";
22
+ } from "./chunks/chunk-J6PEH6ST.js";
23
+ import {
24
+ resolveCliProjectRoot
25
+ } from "./chunks/chunk-WLFOBECN.js";
23
26
  import {
24
27
  AGENTS_CONTEXT_FILE_NAME,
25
28
  AgentSessionController,
@@ -70,7 +73,7 @@ import {
70
73
  setExtensionEnabledInSettings,
71
74
  shouldPreserveExecutionStateForUserText,
72
75
  supersedeExecutionState
73
- } from "./chunks/chunk-IJMP3JTX.js";
76
+ } from "./chunks/chunk-7RYOIXA2.js";
74
77
  import "./chunks/chunk-FKAO25XH.js";
75
78
  import {
76
79
  Deref,
@@ -108,9 +111,6 @@ import {
108
111
  sortToolModelCandidatesByCapability,
109
112
  type_exports
110
113
  } from "./chunks/chunk-K6ZOCJ4V.js";
111
- import {
112
- resolveCliProjectRoot
113
- } from "./chunks/chunk-WLFOBECN.js";
114
114
  import {
115
115
  __commonJS,
116
116
  __require,
@@ -51855,13 +51855,6 @@ var Editor = class {
51855
51855
  autocompleteState = null;
51856
51856
  autocompletePrefix = "";
51857
51857
  autocompleteMaxVisible = 5;
51858
- /**
51859
- * Whether the user moved the autocomplete selection (arrows/paging).
51860
- * A bare "/" + Enter still submits the slash itself (root command menu),
51861
- * but once the user has actively highlighted an item, Enter must accept
51862
- * that selection instead of re-opening the same menu.
51863
- */
51864
- autocompleteListTouched = false;
51865
51858
  // Paste tracking for large pastes
51866
51859
  pastes = /* @__PURE__ */ new Map();
51867
51860
  pasteCounter = 0;
@@ -52098,12 +52091,10 @@ var Editor = class {
52098
52091
  return;
52099
52092
  }
52100
52093
  if (kb.matches(data, "selectUp") || kb.matches(data, "selectDown")) {
52101
- this.autocompleteListTouched = true;
52102
52094
  this.autocompleteList.handleInput(data);
52103
52095
  return;
52104
52096
  }
52105
52097
  if (kb.matches(data, "cursorLeft") || kb.matches(data, "cursorRight") || kb.matches(data, "pageUp") || kb.matches(data, "pageDown")) {
52106
- this.autocompleteListTouched = true;
52107
52098
  this.autocompleteList.handleInput(data);
52108
52099
  return;
52109
52100
  }
@@ -52128,32 +52119,26 @@ var Editor = class {
52128
52119
  return;
52129
52120
  }
52130
52121
  if (kb.matches(data, "selectConfirm")) {
52131
- const currentLine = this.state.lines[this.state.cursorLine] || "";
52132
- const isBareSlashSubmit = !this.autocompleteListTouched && this.state.lines.length === 1 && currentLine === "/" && this.state.cursorCol === 1;
52133
- if (isBareSlashSubmit) {
52134
- this.cancelAutocomplete();
52135
- } else {
52136
- const selected = this.autocompleteList.getSelectedItem();
52137
- if (selected && this.autocompleteProvider) {
52138
- this.pushUndoSnapshot();
52139
- this.lastAction = null;
52140
- const result = this.autocompleteProvider.applyCompletion(
52141
- this.state.lines,
52142
- this.state.cursorLine,
52143
- this.state.cursorCol,
52144
- selected,
52145
- this.autocompletePrefix
52146
- );
52147
- this.state.lines = result.lines;
52148
- this.state.cursorLine = result.cursorLine;
52149
- this.setCursorCol(result.cursorCol);
52150
- if (this.autocompletePrefix.startsWith("/")) {
52151
- this.cancelAutocomplete();
52152
- } else {
52153
- this.cancelAutocomplete();
52154
- if (this.onChange) this.onChange(this.getText());
52155
- return;
52156
- }
52122
+ const selected = this.autocompleteList.getSelectedItem();
52123
+ if (selected && this.autocompleteProvider) {
52124
+ this.pushUndoSnapshot();
52125
+ this.lastAction = null;
52126
+ const result = this.autocompleteProvider.applyCompletion(
52127
+ this.state.lines,
52128
+ this.state.cursorLine,
52129
+ this.state.cursorCol,
52130
+ selected,
52131
+ this.autocompletePrefix
52132
+ );
52133
+ this.state.lines = result.lines;
52134
+ this.state.cursorLine = result.cursorLine;
52135
+ this.setCursorCol(result.cursorCol);
52136
+ if (this.autocompletePrefix.startsWith("/")) {
52137
+ this.cancelAutocomplete();
52138
+ } else {
52139
+ this.cancelAutocomplete();
52140
+ if (this.onChange) this.onChange(this.getText());
52141
+ return;
52157
52142
  }
52158
52143
  }
52159
52144
  }
@@ -53149,7 +53134,6 @@ var Editor = class {
53149
53134
  );
53150
53135
  if (suggestions && suggestions.items.length > 0) {
53151
53136
  this.autocompletePrefix = suggestions.prefix;
53152
- this.autocompleteListTouched = false;
53153
53137
  this.autocompleteList = new SelectList(
53154
53138
  suggestions.items,
53155
53139
  this.computeAutocompleteMaxVisible(suggestions.items.length),
@@ -53221,7 +53205,6 @@ var Editor = class {
53221
53205
  return;
53222
53206
  }
53223
53207
  this.autocompletePrefix = suggestions.prefix;
53224
- this.autocompleteListTouched = false;
53225
53208
  this.autocompleteList = new SelectList(
53226
53209
  suggestions.items,
53227
53210
  this.computeAutocompleteMaxVisible(suggestions.items.length),
@@ -53236,7 +53219,6 @@ var Editor = class {
53236
53219
  this.autocompleteState = null;
53237
53220
  this.autocompleteList = void 0;
53238
53221
  this.autocompletePrefix = "";
53239
- this.autocompleteListTouched = false;
53240
53222
  }
53241
53223
  isShowingAutocomplete() {
53242
53224
  return this.autocompleteState !== null;
@@ -62729,7 +62711,12 @@ var BUILTIN_SLASH_COMMANDS = [
62729
62711
  { name: "tools/subagent", description: "Spawn subagents in parallel to handle tasks" },
62730
62712
  { name: "files/upload", description: "Attach a file to the conversation" },
62731
62713
  { name: "files/upload-pic", description: "Attach an image to the conversation" },
62732
- { name: "files/upload-video", description: "Attach a video to the conversation" }
62714
+ { name: "files/upload-video", description: "Attach a video to the conversation" },
62715
+ { name: "files/list", description: "List uploaded files" },
62716
+ { name: "files/status", description: "Check file status" },
62717
+ { name: "files/refresh", description: "Refresh expired files" },
62718
+ { name: "files/delete", description: "Delete a file" },
62719
+ { name: "files/stats", description: "Show file statistics" }
62733
62720
  ];
62734
62721
 
62735
62722
  // src/commands/interactive-debug-commands.ts
@@ -65305,6 +65292,7 @@ function handleSkillsCommand(host) {
65305
65292
  host.showSelector((done) => {
65306
65293
  const settingsManager = host.options.settingsManager;
65307
65294
  const discovered = listDiscoveredSkills(host);
65295
+ const lastPersisted = /* @__PURE__ */ new Map();
65308
65296
  const items = discovered.map((skill) => ({
65309
65297
  id: `skill:${skill.name}`,
65310
65298
  label: skill.name,
@@ -65312,25 +65300,23 @@ function handleSkillsCommand(host) {
65312
65300
  currentValue: settingsManager.isSkillEnabled(skill.name) ? "on" : "off",
65313
65301
  values: ["on", "off"]
65314
65302
  }));
65303
+ for (const item of items) lastPersisted.set(item.id, item.currentValue);
65315
65304
  const settingsList = new SettingsList(
65316
65305
  items,
65317
65306
  12,
65318
- {
65319
- label: (text, selected) => selected ? theme.fg("accent", text) : theme.fg("text", text),
65320
- value: (text, selected) => selected ? theme.fg("accent", text) : theme.fg("accent", text),
65321
- description: (text) => theme.fg("muted", text),
65322
- cursor: "> ",
65323
- hint: (text) => theme.fg("dim", text)
65324
- },
65307
+ getSettingsListTheme(),
65325
65308
  (id, newValue) => {
65326
65309
  const name = id.slice("skill:".length);
65327
65310
  const enabled = newValue === "on";
65311
+ const previous = lastPersisted.get(id) ?? "on";
65328
65312
  try {
65329
65313
  settingsManager.setSkillEnabled(name, enabled);
65330
65314
  } catch (error) {
65315
+ settingsList.updateValue(id, previous);
65331
65316
  host.showError(`Failed to update skill "${name}": ${errorMessage2(error)}`);
65332
65317
  return;
65333
65318
  }
65319
+ lastPersisted.set(id, newValue);
65334
65320
  applyReloadedSkills(host, discoverSkills(host));
65335
65321
  host.showStatus(`Skill "${name}" ${enabled ? "enabled" : "disabled"} (reloaded).`);
65336
65322
  },
@@ -65628,23 +65614,8 @@ async function handleSkillsReload(host) {
65628
65614
  host.chatContainer.addChild(new Text(theme.fg("accent", "Reloading skills..."), 1, 0));
65629
65615
  host.ui.requestRender();
65630
65616
  try {
65631
- const builtinDir = path25.join(getBuiltinDir(), "skills");
65632
- const userDir = path25.join(os7.homedir(), ".pie", "skills");
65633
- const projectDir = path25.join(process.cwd(), ".pie", "skills");
65634
- const result = loadSkills({ dirs: buildSkillDirs({ builtinDir, userDir, projectDir, cwd: process.cwd() }) });
65635
- host.skills = result.skills.filter((skill) => host.options.settingsManager.isSkillEnabled(skill.name));
65636
- host.skillsSection = formatSkillsForPrompt(host.skills);
65637
- host.applyRuntimeToolMode(host.allTools.length > 0 ? host.allTools : host.agent.state.tools);
65638
- host.updateSystemPromptTools();
65639
- for (const skill of host.skills) {
65640
- const skillPath = ["skills", `skill:${skill.name}`];
65641
- if (!commandTree.find(skillPath)) {
65642
- commandTree.register(skillPath, {
65643
- description: skill.description,
65644
- handler: (_2, args) => host.handleSkillInvoke(skill.name, args || "")
65645
- });
65646
- }
65647
- }
65617
+ const result = discoverSkills(host);
65618
+ applyReloadedSkills(host, result);
65648
65619
  const builtinCount = host.skills.filter((s) => s.source === "builtin").length;
65649
65620
  const userCount = host.skills.filter((s) => s.source === "user" || s.source === "user-agents").length;
65650
65621
  const projectCount = host.skills.filter((s) => s.source === "project" || s.source === "project-agents").length;
@@ -65677,10 +65648,18 @@ function collectKnownToolNames(host) {
65677
65648
  const disabled = disabledNames.filter((name) => !liveNames.includes(name));
65678
65649
  return { active: [...liveNames].sort(), disabled: [...disabled].sort() };
65679
65650
  }
65651
+ function describeToggleEffect(host, name, enabled) {
65652
+ const loaded = collectKnownToolNames(host).active.includes(name);
65653
+ if (enabled) {
65654
+ return loaded ? `Tool "${name}" enabled (active now).` : `Tool "${name}" enabled in ~/.pie/settings.json (loads on next start \u2014 it was not loaded this session).`;
65655
+ }
65656
+ return loaded ? `Tool "${name}" disabled (removed from this session).` : `Tool "${name}" disabled in ~/.pie/settings.json.`;
65657
+ }
65680
65658
  function showToolsManager(host) {
65681
65659
  host.showSelector((done) => {
65682
65660
  const { active, disabled } = collectKnownToolNames(host);
65683
65661
  const settingsManager = host.options.settingsManager;
65662
+ const lastPersisted = /* @__PURE__ */ new Map();
65684
65663
  const items = [
65685
65664
  ...active.map((name) => ({
65686
65665
  id: `tool:${name}`,
@@ -65692,28 +65671,30 @@ function showToolsManager(host) {
65692
65671
  ...disabled.map((name) => ({
65693
65672
  id: `tool:${name}`,
65694
65673
  label: name,
65695
- description: "Not loaded this session (start with it enabled to use it)",
65674
+ description: "Not loaded this session (starts enabled next launch)",
65696
65675
  currentValue: "off",
65697
65676
  values: ["on", "off"]
65698
65677
  }))
65699
65678
  ];
65679
+ for (const item of items) lastPersisted.set(item.id, item.currentValue);
65700
65680
  const settingsList = new SettingsList(
65701
65681
  items,
65702
65682
  14,
65703
65683
  getSettingsListTheme(),
65704
- (_id, newValue) => {
65705
- const name = _id.slice("tool:".length);
65684
+ (id, newValue) => {
65685
+ const name = id.slice("tool:".length);
65706
65686
  const enabled = newValue === "on";
65687
+ const previous = lastPersisted.get(id) ?? "off";
65707
65688
  try {
65708
65689
  settingsManager.setToolEnabled(name, enabled);
65709
65690
  } catch (error) {
65691
+ settingsList.updateValue(id, previous);
65710
65692
  host.showError(`Failed to update tool "${name}": ${errorMessage3(error)}`);
65711
65693
  return;
65712
65694
  }
65695
+ lastPersisted.set(id, newValue);
65713
65696
  host.applyRuntimeToolMode();
65714
- host.showStatus(
65715
- enabled ? `Tool "${name}" enabled (active now).` : `Tool "${name}" disabled (removed from this session).`
65716
- );
65697
+ host.showStatus(describeToggleEffect(host, name, enabled));
65717
65698
  },
65718
65699
  () => done(),
65719
65700
  { enableSearch: true }
@@ -65733,7 +65714,7 @@ function renderHeader(width) {
65733
65714
  const lines = [];
65734
65715
  lines.push(theme.bold(theme.fg("accent", "Tools")));
65735
65716
  lines.push("");
65736
- lines.push(theme.fg("muted", "Enter/Space toggles \xB7 changes apply immediately \xB7 Esc to close"));
65717
+ lines.push(theme.fg("muted", "Enter/Space toggles \xB7 live tools apply immediately \xB7 Esc to close"));
65737
65718
  const hint = "Usage: /tools/enable <name> | /tools/disable <name>";
65738
65719
  if (width > 20) lines.push(theme.fg("dim", hint));
65739
65720
  return lines;
@@ -65762,7 +65743,7 @@ function handleToolsCommand(host, args) {
65762
65743
  host.applyRuntimeToolMode();
65763
65744
  host.chatContainer.addChild(new Spacer(1));
65764
65745
  host.chatContainer.addChild(new Text(
65765
- theme.fg("accent", `Tool "${name}" ${enabled ? "enabled" : "disabled"} in ~/.pie/settings.json (applied now).`),
65746
+ theme.fg("accent", describeToggleEffect(host, name, enabled)),
65766
65747
  1,
65767
65748
  0
65768
65749
  ));
@@ -66471,7 +66452,7 @@ function registerBuiltinCommands(host) {
66471
66452
  handler: (_2, args) => host.handleToolsCommand(args ? `list ${args}` : "list")
66472
66453
  });
66473
66454
  commandTree.register(["tools", "enable"], {
66474
- description: "Enable a tool by name (writes settings; applies now)",
66455
+ description: "Enable a tool by name (writes settings; live tools apply now)",
66475
66456
  handler: (_2, args) => host.handleToolsCommand(`enable ${args || ""}`)
66476
66457
  });
66477
66458
  commandTree.register(["tools", "disable"], {
@@ -66633,6 +66614,7 @@ function ringTerminalBell(options = {}) {
66633
66614
  function ringTurnSettledBell(host) {
66634
66615
  if ((host.agentSessionController?.pendingMessageCount ?? 0) > 0) return;
66635
66616
  if (host.executionState.read().awaitingContinuation) return;
66617
+ if (host.peekTodoAutoContinuePrompt?.()) return;
66636
66618
  host.ringAttentionBell?.("turn-settled");
66637
66619
  }
66638
66620
  function attentionMessage(reason) {
@@ -70275,12 +70257,19 @@ function clearContinuationTimeout(host, reason) {
70275
70257
  function isWaitingForUserInteraction(host) {
70276
70258
  return host.activeInteractionCount > 0;
70277
70259
  }
70260
+ function peekTodoAutoContinuePrompt(host) {
70261
+ if (isWaitingForUserInteraction(host)) return void 0;
70262
+ if (host.submitLock || host.isProcessing || host.isInterrupting || host.isCompacting) return void 0;
70263
+ const hasTodoTool = (host.agent?.state?.tools ?? []).some((tool) => tool.name === "manage_todo_list");
70264
+ if (!hasTodoTool) return void 0;
70265
+ return host.executionState.takeTodoClosureFollowUp() || void 0;
70266
+ }
70278
70267
  function getTodoAutoContinueMessage(host) {
70268
+ const prompt = peekTodoAutoContinuePrompt(host);
70279
70269
  if (isWaitingForUserInteraction(host)) return { reason: "waiting_for_user_interaction" };
70280
70270
  if (host.submitLock || host.isProcessing || host.isInterrupting || host.isCompacting) return { reason: "host_busy" };
70281
70271
  const hasTodoTool = (host.agent?.state?.tools ?? []).some((tool) => tool.name === "manage_todo_list");
70282
70272
  if (!hasTodoTool) return { reason: "todo_tool_unavailable" };
70283
- const prompt = host.executionState.takeTodoClosureFollowUp();
70284
70273
  if (!prompt) return { reason: "no_active_todo_follow_up" };
70285
70274
  host.logDebug("todo_closure:follow_up", {
70286
70275
  textPreview: prompt.slice(0, 120),
@@ -71317,11 +71306,14 @@ async function restoreExtensionProviderCredentials(modelRegistry) {
71317
71306
  }
71318
71307
 
71319
71308
  // src/attachments/resolver.ts
71309
+ var PROVIDER_SOURCE = "(provider)";
71320
71310
  var AttachmentResolver = class {
71321
- constructor(extensions) {
71311
+ constructor(extensions, builtInHandlers = []) {
71322
71312
  this.extensions = extensions;
71313
+ this.builtInHandlers = builtInHandlers;
71323
71314
  }
71324
71315
  extensions;
71316
+ builtInHandlers;
71325
71317
  findHandler(model, attachment) {
71326
71318
  const candidate = this.collectCandidates(model, attachment)[0];
71327
71319
  if (!candidate) return void 0;
@@ -71346,6 +71338,15 @@ var AttachmentResolver = class {
71346
71338
  }
71347
71339
  collectCandidates(model, attachment) {
71348
71340
  const matches = [];
71341
+ for (const handler of this.builtInHandlers) {
71342
+ if (!handler.matches(model, attachment)) continue;
71343
+ matches.push({
71344
+ handlerName: handler.name,
71345
+ extensionPath: PROVIDER_SOURCE,
71346
+ priority: handler.priority ?? 0,
71347
+ prepare: handler.prepare.bind(handler)
71348
+ });
71349
+ }
71349
71350
  for (const extension of this.extensions) {
71350
71351
  for (const handler of extension.attachmentHandlers.values()) {
71351
71352
  if (!handler.matches(model, attachment)) continue;
@@ -71368,6 +71369,48 @@ var AttachmentResolver = class {
71368
71369
  }
71369
71370
  };
71370
71371
 
71372
+ // src/attachments/provider-handlers.ts
71373
+ function getModality(mimeType) {
71374
+ if (mimeType.startsWith("image/")) return "image";
71375
+ if (mimeType.startsWith("video/")) return "video";
71376
+ return "file";
71377
+ }
71378
+ function isKimiAttachmentPlatform(model) {
71379
+ return model.attachmentPlatform === "kimi" || model.filePlatform === "kimi";
71380
+ }
71381
+ var kimiNativeAttachmentHandler = {
71382
+ name: "kimi-native-attachments",
71383
+ priority: 100,
71384
+ matches(model, attachment) {
71385
+ return isKimiAttachmentPlatform(model) && Boolean(attachment.fileId) && getModality(attachment.mimeType) !== "file";
71386
+ },
71387
+ async prepare(_model, attachment, ctx) {
71388
+ const fileId = attachment.fileId;
71389
+ if (!fileId) {
71390
+ return {
71391
+ items: [{ type: "text", text: `[${attachment.id}]` }],
71392
+ warnings: ["\u9644\u4EF6\u7F3A\u5C11 fileId\uFF0C\u65E0\u6CD5\u8D70 Kimi \u539F\u751F\u5F15\u7528"]
71393
+ };
71394
+ }
71395
+ const modality = getModality(attachment.mimeType);
71396
+ const url = `ms://${fileId}`;
71397
+ ctx.log(`Kimi handler prepared ${modality} reference for ${attachment.id}`);
71398
+ return {
71399
+ items: [
71400
+ {
71401
+ type: "file-ref",
71402
+ mimeType: attachment.mimeType,
71403
+ url,
71404
+ modality
71405
+ }
71406
+ ]
71407
+ };
71408
+ }
71409
+ };
71410
+ var providerAttachmentHandlers = [
71411
+ kimiNativeAttachmentHandler
71412
+ ];
71413
+
71371
71414
  // src/app/runtime-composition-pipeline.ts
71372
71415
  var CLI_COMPOSITION_STEP_ORDER = [
71373
71416
  "model_config",
@@ -71518,7 +71561,7 @@ async function loadInteractiveExtensions(host) {
71518
71561
  buildExtensionLoadOptions(host.options.settingsManager, host.options.extensionFlags)
71519
71562
  );
71520
71563
  host.extensions = extensions;
71521
- host.attachmentResolver = new AttachmentResolver(extensions);
71564
+ host.attachmentResolver = new AttachmentResolver(extensions, providerAttachmentHandlers);
71522
71565
  host.extensionRunner = new ExtensionRunner(
71523
71566
  extensions,
71524
71567
  extensionActions,
@@ -71690,6 +71733,10 @@ var InteractiveMode = class {
71690
71733
  getTodoAutoContinueMessage() {
71691
71734
  return getTodoAutoContinueMessage(this.continuationHost);
71692
71735
  }
71736
+ /** Side-effect-free todo auto-continue prediction for the turn-settled bell. */
71737
+ peekTodoAutoContinuePrompt() {
71738
+ return peekTodoAutoContinuePrompt(this.continuationHost);
71739
+ }
71693
71740
  appendWarningSummary(message) {
71694
71741
  return appendWarningSummary(this.continuationHost, message);
71695
71742
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cydm/pie",
3
- "version": "1.0.48",
3
+ "version": "1.0.49",
4
4
  "description": "Pie AI Agent CLI",
5
5
  "type": "module",
6
6
  "bin": {
@@ -18,7 +18,7 @@
18
18
  "test": "npm run test:offline",
19
19
  "pretest:offline": "npm run build --workspace @pie/agent-framework && npm run build --workspace @pie/shared-headless-capabilities && npm run build",
20
20
  "posttest:offline": "node --test --import tsx test/remote-model-catalog.test.ts",
21
- "test:offline": "node --test --import tsx test/core-services.test.ts test/cli.test.ts test/cli-auto-compaction.test.ts test/execution-state-manager.test.ts test/queued-steer-execution-state.test.ts test/architecture-boundaries.test.ts test/attachment-resolver.test.ts test/kimi-attachment-extension.test.ts test/document-attachment-extension.test.ts test/auto-compact-overflow.test.ts test/hierarchical-command-system.test.ts test/extensions-enablement.test.ts test/skill-tool-enablement.test.ts test/message-queue-unit.test.ts test/evals-harness.test.ts test/session-trace.test.ts test/runtime-mode-context.test.ts test/tool-policy.test.ts test/retry-policy.test.ts test/yolo-command.test.ts test/footer-lifecycle.test.ts test/files-utils.test.ts test/source-size-guard.test.ts test/runtime-logger.test.ts test/release-readiness.test.ts test/package-artifacts.test.ts test/code-intel-typescript-provider.test.ts test/init-extension-contract.test.ts test/skills-runtime.test.ts test/skills-agents-discovery.test.ts test/skills-symlink.test.ts test/model-overrides.test.ts test/auth-storage.test.ts test/extension-register-provider.test.ts test/extension-reload.test.ts test/extension-terminal-input.test.ts test/extension-session-before-events.test.ts test/extension-widgets.test.ts test/extension-render-hint.test.ts test/extension-oauth-restore.test.ts test/cross-host-parity.test.ts test/tool-name-rendering.test.ts test/session-selector.test.ts test/terminal-title.test.ts test/interactive-controller-title.test.ts test/todo-title.terminal.test.ts test/plan-mode-extension.test.ts test/tools-registry.test.ts test/system-prompt-contract.test.ts test/direct-bash.test.ts test/terminal-bell.test.ts test/calm-mode.test.ts test/project-root.test.ts test/guard-advisory-context.test.ts test/clear-command-usage.test.ts test/daily-agent-smoke.terminal.test.ts test/slash-menu-toggles.terminal.test.ts",
21
+ "test:offline": "node --test --import tsx test/core-services.test.ts test/cli.test.ts test/cli-auto-compaction.test.ts test/execution-state-manager.test.ts test/queued-steer-execution-state.test.ts test/architecture-boundaries.test.ts test/attachment-resolver.test.ts test/kimi-attachment-provider.test.ts test/document-attachment-extension.test.ts test/auto-compact-overflow.test.ts test/hierarchical-command-system.test.ts test/extensions-enablement.test.ts test/skill-tool-enablement.test.ts test/message-queue-unit.test.ts test/evals-harness.test.ts test/session-trace.test.ts test/runtime-mode-context.test.ts test/tool-policy.test.ts test/retry-policy.test.ts test/yolo-command.test.ts test/footer-lifecycle.test.ts test/files-utils.test.ts test/source-size-guard.test.ts test/runtime-logger.test.ts test/release-readiness.test.ts test/package-artifacts.test.ts test/code-intel-typescript-provider.test.ts test/init-extension-contract.test.ts test/skills-runtime.test.ts test/skills-agents-discovery.test.ts test/skills-symlink.test.ts test/model-overrides.test.ts test/auth-storage.test.ts test/extension-register-provider.test.ts test/extension-reload.test.ts test/extension-terminal-input.test.ts test/extension-session-before-events.test.ts test/extension-widgets.test.ts test/extension-render-hint.test.ts test/extension-oauth-restore.test.ts test/cross-host-parity.test.ts test/tool-name-rendering.test.ts test/session-selector.test.ts test/terminal-title.test.ts test/interactive-controller-title.test.ts test/todo-title.terminal.test.ts test/plan-mode-extension.test.ts test/tools-registry.test.ts test/system-prompt-contract.test.ts test/direct-bash.test.ts test/terminal-bell.test.ts test/calm-mode.test.ts test/project-root.test.ts test/guard-advisory-context.test.ts test/clear-command-usage.test.ts test/daily-agent-smoke.terminal.test.ts test/slash-menu-toggles.terminal.test.ts",
22
22
  "test:all": "npm run test:offline && npm run test:terminal:vitest",
23
23
  "test:terminal:vitest": "vitest run test/mention-file-select.terminal.test.ts test/tool-partial-display.terminal.test.ts test/viewport-position-final.test.ts test/resume-header.terminal.test.ts --no-file-parallelism",
24
24
  "test:todo-tui": "PIE_REAL_TODO_TUI=1 node --test --import tsx test/todo-closure.terminal.test.ts",
@@ -1,47 +0,0 @@
1
- import { createRequire as __createRequire } from "node:module"; const require = __createRequire(import.meta.url);
2
- import "../../../chunks/chunk-LZQXQOPJ.js";
3
-
4
- // builtin/extensions/kimi-attachments/index.ts
5
- function isKimiAttachmentPlatform(model) {
6
- return model.attachmentPlatform === "kimi" || model.filePlatform === "kimi";
7
- }
8
- function getModality(mimeType) {
9
- if (mimeType.startsWith("image/")) return "image";
10
- if (mimeType.startsWith("video/")) return "video";
11
- return "file";
12
- }
13
- function kimiAttachmentsExtension(ctx) {
14
- ctx.log("Kimi attachment extension loaded");
15
- ctx.registerAttachmentHandler({
16
- name: "kimi-native-attachments",
17
- priority: 100,
18
- matches(model, attachment) {
19
- return isKimiAttachmentPlatform(model) && Boolean(attachment.fileId) && getModality(attachment.mimeType) !== "file";
20
- },
21
- async prepare(_model, attachment, prepareCtx) {
22
- const fileId = attachment.fileId;
23
- if (!fileId) {
24
- return {
25
- items: [{ type: "text", text: `[${attachment.id}]` }],
26
- warnings: ["\u9644\u4EF6\u7F3A\u5C11 fileId\uFF0C\u65E0\u6CD5\u8D70 Kimi \u539F\u751F\u5F15\u7528"]
27
- };
28
- }
29
- const modality = getModality(attachment.mimeType);
30
- const url = `ms://${fileId}`;
31
- prepareCtx.log(`Kimi handler prepared ${modality} reference for ${attachment.id}`);
32
- return {
33
- items: [
34
- {
35
- type: "file-ref",
36
- mimeType: attachment.mimeType,
37
- url,
38
- modality
39
- }
40
- ]
41
- };
42
- }
43
- });
44
- }
45
- export {
46
- kimiAttachmentsExtension as default
47
- };
@@ -1,11 +0,0 @@
1
- {
2
- "name": "@pie/extension-kimi-attachments",
3
- "version": "1.0.0",
4
- "description": "Kimi file upload attachment handlers",
5
- "main": "index.ts",
6
- "type": "module",
7
- "pie": {
8
- "name": "kimi-attachments",
9
- "main": "index.ts"
10
- }
11
- }