duclaw-cli 1.9.13 → 1.9.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bundle.js CHANGED
@@ -30242,7 +30242,7 @@ function printHelp() {
30242
30242
  `);
30243
30243
  }
30244
30244
  function printVersion() {
30245
- console.log(`duclaw-cli v${true ? "1.9.13" : "unknown"}`);
30245
+ console.log(`duclaw-cli v${true ? "1.9.15" : "unknown"}`);
30246
30246
  }
30247
30247
  function getDuclawTemplate() {
30248
30248
  return {
@@ -32288,9 +32288,9 @@ var NodeFsHandler = class {
32288
32288
  if (this.fsw.closed) {
32289
32289
  return;
32290
32290
  }
32291
- const dirname7 = sp.dirname(file);
32291
+ const dirname8 = sp.dirname(file);
32292
32292
  const basename4 = sp.basename(file);
32293
- const parent = this.fsw._getWatchedDir(dirname7);
32293
+ const parent = this.fsw._getWatchedDir(dirname8);
32294
32294
  let prevStats = stats;
32295
32295
  if (parent.has(basename4))
32296
32296
  return;
@@ -32317,7 +32317,7 @@ var NodeFsHandler = class {
32317
32317
  prevStats = newStats2;
32318
32318
  }
32319
32319
  } catch (error) {
32320
- this.fsw._remove(dirname7, basename4);
32320
+ this.fsw._remove(dirname8, basename4);
32321
32321
  }
32322
32322
  } else if (parent.has(basename4)) {
32323
32323
  const at = newStats.atimeMs;
@@ -33279,8 +33279,8 @@ var chokidar_default = { watch, FSWatcher };
33279
33279
  var import_node_cron = __toESM(require_node_cron());
33280
33280
 
33281
33281
  // src/agent/createAgent.ts
33282
- var import_node_crypto15 = require("node:crypto");
33283
- var import_node_fs7 = require("node:fs");
33282
+ var import_node_crypto16 = require("node:crypto");
33283
+ var import_node_fs8 = require("node:fs");
33284
33284
 
33285
33285
  // src/background/BackgroundManager.ts
33286
33286
  var import_child_process = require("child_process");
@@ -41627,6 +41627,183 @@ async function parseResponseJson(response) {
41627
41627
  }
41628
41628
  }
41629
41629
 
41630
+ // src/attachments/attachmentContext.ts
41631
+ var import_node_crypto4 = require("node:crypto");
41632
+ var import_node_fs3 = require("node:fs");
41633
+ var import_node_path12 = require("node:path");
41634
+ var MAX_ATTACHMENT_RECORDS = 100;
41635
+ var RECENT_ATTACHMENT_LIMIT = 5;
41636
+ function makeAttachmentId(input) {
41637
+ const raw2 = [
41638
+ input.userId,
41639
+ input.platform,
41640
+ input.messageId,
41641
+ input.resourceKey,
41642
+ input.kind
41643
+ ].join(":");
41644
+ const digest = (0, import_node_crypto4.createHash)("sha256").update(raw2).digest("hex").slice(0, 16);
41645
+ return `att_${input.kind === "image" ? "img" : "file"}_${digest}`;
41646
+ }
41647
+ function indexPath(userId) {
41648
+ return (0, import_node_path12.join)(getDuclawWorkspaceDir(), userId, "attachments", "index.json");
41649
+ }
41650
+ function readIndex(userId) {
41651
+ const filePath = indexPath(userId);
41652
+ if (!(0, import_node_fs3.existsSync)(filePath)) return [];
41653
+ try {
41654
+ const parsed = JSON.parse((0, import_node_fs3.readFileSync)(filePath, "utf8"));
41655
+ return Array.isArray(parsed) ? parsed.filter(isAttachmentRecord) : [];
41656
+ } catch (error) {
41657
+ console.warn(`[attachments] failed to read attachment index: ${error.message}`);
41658
+ return [];
41659
+ }
41660
+ }
41661
+ function writeIndex(userId, records) {
41662
+ const filePath = indexPath(userId);
41663
+ (0, import_node_fs3.mkdirSync)((0, import_node_path12.dirname)(filePath), { recursive: true });
41664
+ (0, import_node_fs3.writeFileSync)(filePath, JSON.stringify(records.slice(0, MAX_ATTACHMENT_RECORDS), null, 2));
41665
+ }
41666
+ function isAttachmentRecord(value) {
41667
+ const record = value;
41668
+ return Boolean(record) && typeof record.id === "string" && (record.kind === "image" || record.kind === "file") && typeof record.userId === "string" && typeof record.pathOrUrl === "string";
41669
+ }
41670
+ function stringValue2(value) {
41671
+ return typeof value === "string" && value.trim() ? value.trim() : void 0;
41672
+ }
41673
+ function attachmentKind(value) {
41674
+ const type = stringValue2(value.type);
41675
+ const mimeType = stringValue2(value.mimeType);
41676
+ const name = stringValue2(value.name) ?? stringValue2(value.path) ?? stringValue2(value.url) ?? "";
41677
+ if (type === "image" || mimeType?.startsWith("image/") || /\.(png|jpe?g|gif|webp)$/i.test(name)) {
41678
+ return "image";
41679
+ }
41680
+ return "file";
41681
+ }
41682
+ function xmlEscape(value) {
41683
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
41684
+ }
41685
+ function upsertAttachments(userId, records) {
41686
+ if (records.length === 0) return;
41687
+ const existing = readIndex(userId);
41688
+ const byId = /* @__PURE__ */ new Map();
41689
+ for (const record of [...records, ...existing]) {
41690
+ if (!byId.has(record.id)) byId.set(record.id, record);
41691
+ }
41692
+ writeIndex(userId, Array.from(byId.values()).sort((a, b) => b.receivedAt.localeCompare(a.receivedAt)));
41693
+ }
41694
+ function listRecentAttachments(userId, limit = RECENT_ATTACHMENT_LIMIT) {
41695
+ return readIndex(userId).sort((a, b) => b.receivedAt.localeCompare(a.receivedAt)).slice(0, limit);
41696
+ }
41697
+ function resolveAttachmentPathOrUrl(userId, attachmentId) {
41698
+ return readIndex(userId).find((record) => record.id === attachmentId)?.pathOrUrl;
41699
+ }
41700
+ function collectAttachmentsFromRequest(request) {
41701
+ const metadata = request.metadata ?? {};
41702
+ const platform = request.platform || "unknown";
41703
+ const messageId = request.requestId;
41704
+ const receivedAt = (/* @__PURE__ */ new Date()).toISOString();
41705
+ const records = [];
41706
+ const seen = /* @__PURE__ */ new Set();
41707
+ const addRecord = (input) => {
41708
+ if (!input.pathOrUrl) return;
41709
+ const id = makeAttachmentId({
41710
+ userId: request.userId,
41711
+ platform,
41712
+ messageId,
41713
+ resourceKey: input.resourceKey,
41714
+ kind: input.kind
41715
+ });
41716
+ if (seen.has(id)) return;
41717
+ seen.add(id);
41718
+ records.push({
41719
+ id,
41720
+ kind: input.kind,
41721
+ platform,
41722
+ userId: request.userId,
41723
+ messageId,
41724
+ resourceKey: input.resourceKey,
41725
+ pathOrUrl: input.pathOrUrl,
41726
+ name: input.name,
41727
+ mimeType: input.mimeType,
41728
+ source: input.source,
41729
+ receivedAt
41730
+ });
41731
+ };
41732
+ const imageUrl = stringValue2(metadata.imageUrl);
41733
+ const imageKey = stringValue2(metadata.imageKey);
41734
+ if (imageUrl && imageKey) {
41735
+ addRecord({
41736
+ kind: "image",
41737
+ resourceKey: imageKey,
41738
+ pathOrUrl: imageUrl,
41739
+ source: `${platform}_metadata`
41740
+ });
41741
+ }
41742
+ const fileUrl = stringValue2(metadata.fileUrl);
41743
+ const fileName = stringValue2(metadata.fileName);
41744
+ if (fileUrl && fileName) {
41745
+ addRecord({
41746
+ kind: "file",
41747
+ resourceKey: fileName,
41748
+ pathOrUrl: fileUrl,
41749
+ name: fileName,
41750
+ source: `${platform}_metadata`
41751
+ });
41752
+ }
41753
+ const attachments = Array.isArray(metadata.attachments) ? metadata.attachments : [];
41754
+ for (const attachment of attachments) {
41755
+ const resourceKey = stringValue2(attachment.id) ?? stringValue2(attachment.name);
41756
+ const pathOrUrl = stringValue2(attachment.url) ?? stringValue2(attachment.path);
41757
+ if (!resourceKey || !pathOrUrl) continue;
41758
+ addRecord({
41759
+ kind: attachmentKind(attachment),
41760
+ resourceKey,
41761
+ pathOrUrl,
41762
+ name: stringValue2(attachment.name),
41763
+ mimeType: stringValue2(attachment.mimeType),
41764
+ source: `${platform}_attachment`
41765
+ });
41766
+ }
41767
+ return records;
41768
+ }
41769
+ function buildAttachmentContextXml(records) {
41770
+ if (records.length === 0) return "";
41771
+ const lines = [
41772
+ `<recent-attachments>`,
41773
+ ` <system-note>Use attachment_id when referring to these attachments. Do not invent or reproduce long URLs; tools can resolve the real file source from attachment_id.</system-note>`
41774
+ ];
41775
+ for (const record of records) {
41776
+ lines.push(` <${record.kind} id="${xmlEscape(record.id)}">`);
41777
+ lines.push(` <source>${xmlEscape(record.source ?? record.platform)}</source>`);
41778
+ lines.push(` <message_id>${xmlEscape(record.messageId)}</message_id>`);
41779
+ lines.push(` <resource_key>${xmlEscape(record.resourceKey)}</resource_key>`);
41780
+ if (record.name) lines.push(` <name>${xmlEscape(record.name)}</name>`);
41781
+ if (record.mimeType) lines.push(` <mime_type>${xmlEscape(record.mimeType)}</mime_type>`);
41782
+ lines.push(` <received_at>${xmlEscape(record.receivedAt)}</received_at>`);
41783
+ lines.push(` </${record.kind}>`);
41784
+ }
41785
+ lines.push(`</recent-attachments>`);
41786
+ return lines.join("\n");
41787
+ }
41788
+ function attachRecentAttachmentContext(request, content) {
41789
+ const current = collectAttachmentsFromRequest(request);
41790
+ if (current.length > 0) {
41791
+ upsertAttachments(request.userId, current);
41792
+ request.metadata = {
41793
+ ...request.metadata ?? {},
41794
+ attachmentIds: current.map((record) => record.id),
41795
+ attachmentId: current[0]?.id
41796
+ };
41797
+ }
41798
+ const context = buildAttachmentContextXml(listRecentAttachments(request.userId));
41799
+ if (!context) return content;
41800
+ return `${content}
41801
+
41802
+ <system-reminder>
41803
+ ${context}
41804
+ </system-reminder>`;
41805
+ }
41806
+
41630
41807
  // src/tools/tools/ImageUnderstand.ts
41631
41808
  var guessMediaTypeFromData = (base64Data) => {
41632
41809
  if (base64Data.startsWith("/9j/")) return "image/jpeg";
@@ -41646,6 +41823,7 @@ var controlPlaneBaseUrl = () => {
41646
41823
  return process.env.DUCLAW_CONTROL_PLANE_BASE_URL?.replace(/\/$/, "");
41647
41824
  };
41648
41825
  var USER_IMAGE_REUPLOAD_MESSAGE = `\u8FD9\u5F20\u56FE\u7247\u6682\u65F6\u65E0\u6CD5\u6309\u56FE\u7247\u683C\u5F0F\u89E3\u6790\u3002\u8BF7\u91CD\u65B0\u53D1\u9001 JPG/PNG/WebP \u56FE\u7247\uFF0C\u6216\u8005\u5728\u624B\u673A\u4E0A\u622A\u56FE\u540E\u518D\u53D1\u4E00\u6B21\u3002`;
41826
+ var USER_IMAGE_UNDERSTAND_CREDIT_EXHAUSTED_MESSAGE = `Credit \u4F59\u989D\u4E0D\u8DB3\uFF0C\u65E0\u6CD5\u5B8C\u6210\u672C\u6B21\u56FE\u7247\u7406\u89E3\u3002\u8BF7\u5148\u5145\u503C\u6216\u4F7F\u7528\u6FC0\u6D3B\u7801\u589E\u52A0 Credit\u3002`;
41649
41827
  var normalizeRuntimeAttachmentUrl = (input) => {
41650
41828
  return input.replace("/api/mobile/attachments/", "/internal/runtime/mobile/attachments/");
41651
41829
  };
@@ -41722,6 +41900,10 @@ var imageUnderstand = {
41722
41900
  type: `string`,
41723
41901
  description: `\u56FE\u7247\u7684\u8BBF\u95EEurl\u6216base64\u7F16\u7801\u5185\u5BB9\u3002\u5F53\u7528\u6237\u76F4\u63A5\u53D1\u9001\u56FE\u7247\u65F6\u6B64\u53C2\u6570\u53EF\u7701\u7565,\u5DE5\u5177\u4F1A\u81EA\u52A8\u4ECE\u4E0A\u4E0B\u6587\u83B7\u53D6\u56FE\u7247\u3002`
41724
41902
  },
41903
+ attachment_id: {
41904
+ type: `string`,
41905
+ description: `\u6700\u8FD1\u9644\u4EF6\u4E0A\u4E0B\u6587\u4E2D\u7684\u77ED\u9644\u4EF6 ID\uFF0C\u4F8B\u5982 att_img_xxx\u3002\u4F18\u5148\u4F7F\u7528\u5B83\u5F15\u7528\u5386\u53F2\u56FE\u7247\uFF0C\u4E0D\u8981\u624B\u5199\u957F URL\u3002`
41906
+ },
41725
41907
  prompt: {
41726
41908
  type: `string`,
41727
41909
  description: `prompt\u5B57\u6BB5\u7528\u6765\u63CF\u8FF0\u6587\u5B57\u6307\u4EE4,\u4F8B\u5982"\u63CF\u8FF0\u56FE\u7247\u5185\u5BB9"`
@@ -41730,7 +41912,8 @@ var imageUnderstand = {
41730
41912
  required: [`prompt`]
41731
41913
  },
41732
41914
  async execute(input, userRequest) {
41733
- const imageSource = userRequest?.metadata?.imageUrl || input.image_url;
41915
+ const attachmentId = typeof input.attachment_id === "string" ? input.attachment_id.trim() : "";
41916
+ const imageSource = userRequest?.metadata?.imageUrl || (attachmentId && userRequest?.userId ? resolveAttachmentPathOrUrl(userRequest.userId, attachmentId) : void 0) || input.image_url;
41734
41917
  if (!imageSource) {
41735
41918
  throw new Error(`[ImageUnderstand] \u672A\u63D0\u4F9B\u56FE\u7247: \u8BF7\u4F20\u5165 image_url \u53C2\u6570\u6216\u901A\u8FC7\u6D88\u606F\u53D1\u9001\u56FE\u7247`);
41736
41919
  }
@@ -41776,6 +41959,15 @@ var imageUnderstand = {
41776
41959
  providerResponseId: response.providerResponseId,
41777
41960
  authStyle,
41778
41961
  userRequest
41962
+ }).catch((error) => {
41963
+ if (error instanceof ImageUnderstandMeteringError && (error.meteringStatus === "credit_exhausted" || error.statusCode === 402)) {
41964
+ throw new UserRecoverableToolError(
41965
+ "credit_exhausted",
41966
+ USER_IMAGE_UNDERSTAND_CREDIT_EXHAUSTED_MESSAGE,
41967
+ error.message
41968
+ );
41969
+ }
41970
+ throw error;
41779
41971
  });
41780
41972
  return resultText;
41781
41973
  }
@@ -41783,7 +41975,7 @@ var imageUnderstand = {
41783
41975
 
41784
41976
  // src/tools/tools/ImageGenerate.ts
41785
41977
  var import_promises12 = require("node:fs/promises");
41786
- var import_node_path12 = __toESM(require("node:path"));
41978
+ var import_node_path13 = __toESM(require("node:path"));
41787
41979
  var DEFAULT_BASE_URL = "https://direct.shanyiapi.com";
41788
41980
  var DEFAULT_MODEL = "gpt-image-2";
41789
41981
  var RETRYABLE_STATUS_CODES = /* @__PURE__ */ new Set([403, 429, 500, 502, 503, 504, 524]);
@@ -41810,7 +42002,7 @@ function extensionFromContentType(contentType) {
41810
42002
  }
41811
42003
  function extensionFromUrl(url) {
41812
42004
  try {
41813
- const ext = import_node_path12.default.extname(new URL(url).pathname).toLowerCase();
42005
+ const ext = import_node_path13.default.extname(new URL(url).pathname).toLowerCase();
41814
42006
  if ([".png", ".jpg", ".jpeg", ".webp", ".gif"].includes(ext)) return ext;
41815
42007
  } catch {
41816
42008
  return "";
@@ -41943,7 +42135,7 @@ var imageGenerate = {
41943
42135
  requestId: generated.requestId
41944
42136
  }, null, 2);
41945
42137
  }
41946
- const outDir = import_node_path12.default.join(getEffectiveCwd(userRequest), "imagegen");
42138
+ const outDir = import_node_path13.default.join(getEffectiveCwd(userRequest), "imagegen");
41947
42139
  await (0, import_promises12.mkdir)(outDir, { recursive: true });
41948
42140
  const stem = sanitizeName(String(input.filenameStem || prompt));
41949
42141
  const savedFiles = [];
@@ -41952,7 +42144,7 @@ var imageGenerate = {
41952
42144
  const image = item.url ? await downloadImage(item.url, timeoutMs) : item.b64_json ? decodeBase64Image(item.b64_json) : null;
41953
42145
  if (!image) continue;
41954
42146
  const fileName = `${stem}-${index + 1}${image.extension}`;
41955
- const filePath = import_node_path12.default.join(outDir, fileName);
42147
+ const filePath = import_node_path13.default.join(outDir, fileName);
41956
42148
  await (0, import_promises12.writeFile)(filePath, image.buffer);
41957
42149
  savedFiles.push({ filePath, fileName });
41958
42150
  }
@@ -42397,10 +42589,10 @@ var goalDelete = {
42397
42589
  };
42398
42590
 
42399
42591
  // src/tools/tools/department/DepartmentCreate.ts
42400
- var import_node_crypto8 = require("node:crypto");
42592
+ var import_node_crypto9 = require("node:crypto");
42401
42593
 
42402
42594
  // src/department/mailbox/mailbox.ts
42403
- var import_node_crypto7 = require("node:crypto");
42595
+ var import_node_crypto8 = require("node:crypto");
42404
42596
 
42405
42597
  // src/agent/interruptRegistry.ts
42406
42598
  var registry = /* @__PURE__ */ new Map();
@@ -42451,7 +42643,7 @@ var drainInterrupts = (userId) => {
42451
42643
  };
42452
42644
 
42453
42645
  // src/agent/events.ts
42454
- var import_node_crypto4 = require("node:crypto");
42646
+ var import_node_crypto5 = require("node:crypto");
42455
42647
  var rowToEvent = (row) => ({
42456
42648
  id: row.id,
42457
42649
  userId: row.userId,
@@ -42468,7 +42660,7 @@ var rowToEvent = (row) => ({
42468
42660
  var recordAgentEvent = (input) => {
42469
42661
  const db3 = createSqliteDB();
42470
42662
  const now = Date.now();
42471
- const id = `evt_${(0, import_node_crypto4.randomUUID)().slice(0, 12)}`;
42663
+ const id = `evt_${(0, import_node_crypto5.randomUUID)().slice(0, 12)}`;
42472
42664
  const payloadJson = JSON.stringify(input.payload);
42473
42665
  db3.prepare(`
42474
42666
  INSERT INTO agent_events (
@@ -42599,7 +42791,7 @@ ${ceoFollowupInstruction}
42599
42791
  };
42600
42792
 
42601
42793
  // src/department/mailbox/events.ts
42602
- var import_node_crypto5 = require("node:crypto");
42794
+ var import_node_crypto6 = require("node:crypto");
42603
42795
  var parseDetail = (detailJson) => {
42604
42796
  if (!detailJson) return void 0;
42605
42797
  try {
@@ -42640,7 +42832,7 @@ var mapMailboxEventRow = (row) => {
42640
42832
  var recordMailboxEvent = (input) => {
42641
42833
  const db3 = createSqliteDB();
42642
42834
  const event = {
42643
- id: (0, import_node_crypto5.randomUUID)().slice(0, 12),
42835
+ id: (0, import_node_crypto6.randomUUID)().slice(0, 12),
42644
42836
  messageId: input.messageId,
42645
42837
  mailboxId: input.mailboxId,
42646
42838
  actorMailboxId: input.actorMailboxId,
@@ -42924,7 +43116,7 @@ var deleteDepartmentMemberById = (departmentName, memberId) => {
42924
43116
  };
42925
43117
 
42926
43118
  // src/department/mailbox/ceoFollowup.ts
42927
- var import_node_crypto6 = require("node:crypto");
43119
+ var import_node_crypto7 = require("node:crypto");
42928
43120
  var rowToFollowup = (row) => ({
42929
43121
  id: row.id,
42930
43122
  sourceMessageId: row.sourceMessageId,
@@ -42971,7 +43163,7 @@ var enqueueCeoFollowupFromMailbox = (message) => {
42971
43163
  if (!message.originUserId || !message.originPlatform) return null;
42972
43164
  const db3 = createSqliteDB();
42973
43165
  const now = Date.now();
42974
- const id = `cfu_${(0, import_node_crypto6.randomUUID)().slice(0, 12)}`;
43166
+ const id = `cfu_${(0, import_node_crypto7.randomUUID)().slice(0, 12)}`;
42975
43167
  db3.prepare(`
42976
43168
  INSERT INTO ceo_followups (
42977
43169
  id,
@@ -43301,7 +43493,7 @@ var recordMailboxReceivedAgentEvent = (msg) => {
43301
43493
  };
43302
43494
  var sendMessage2 = (fromMailboxId, toMailboxId, content, options) => {
43303
43495
  const db3 = createSqliteDB();
43304
- const id = (0, import_node_crypto7.randomUUID)().slice(0, 8);
43496
+ const id = (0, import_node_crypto8.randomUUID)().slice(0, 8);
43305
43497
  const threadId = options?.threadId || id;
43306
43498
  const workItemContext = resolveWorkItemContext(fromMailboxId, toMailboxId, id, options);
43307
43499
  const stmt = db3.prepare(`insert into mailbox (
@@ -43444,7 +43636,7 @@ var departmentCreate = {
43444
43636
  return `[departmentCreate] \u4E0D\u5B58\u5728 id=${sourceGoalId} \u7684\u76EE\u6807`;
43445
43637
  }
43446
43638
  let departmentDefinition = {
43447
- id: (0, import_node_crypto8.randomUUID)().slice(0, 8),
43639
+ id: (0, import_node_crypto9.randomUUID)().slice(0, 8),
43448
43640
  name,
43449
43641
  charter,
43450
43642
  sourceGoalId,
@@ -43711,7 +43903,7 @@ var departmentList = {
43711
43903
  };
43712
43904
 
43713
43905
  // src/tools/tools/department/DepartmentMemberCreate.ts
43714
- var import_node_crypto9 = require("node:crypto");
43906
+ var import_node_crypto10 = require("node:crypto");
43715
43907
  var DESCRIPTION25 = `
43716
43908
  \u521B\u5EFA\u90E8\u95E8\u6210\u5458\u3002
43717
43909
 
@@ -43777,7 +43969,7 @@ var departmentMemberCreate = {
43777
43969
  }
43778
43970
  }
43779
43971
  let departmentMember = {
43780
- id: (0, import_node_crypto9.randomUUID)().slice(0, 8),
43972
+ id: (0, import_node_crypto10.randomUUID)().slice(0, 8),
43781
43973
  name,
43782
43974
  departmentId: department.id,
43783
43975
  mailBoxId: getMailBoxId(department.name, name),
@@ -43967,13 +44159,13 @@ ${replies}`;
43967
44159
  };
43968
44160
 
43969
44161
  // src/department/learning.ts
43970
- var import_node_fs4 = require("node:fs");
43971
- var import_node_path14 = __toESM(require("node:path"));
43972
- var import_node_crypto10 = require("node:crypto");
44162
+ var import_node_fs5 = require("node:fs");
44163
+ var import_node_path15 = __toESM(require("node:path"));
44164
+ var import_node_crypto11 = require("node:crypto");
43973
44165
 
43974
44166
  // src/skill/SkillValidator.ts
43975
- var import_node_fs3 = require("node:fs");
43976
- var import_node_path13 = __toESM(require("node:path"));
44167
+ var import_node_fs4 = require("node:fs");
44168
+ var import_node_path14 = __toESM(require("node:path"));
43977
44169
  var SKILL_NAME_PATTERN = /^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$/;
43978
44170
  var REQUIRED_SECTIONS = [
43979
44171
  /(^|\n)##\s+(when to use|何时使用|trigger|triggers)\b/i,
@@ -43999,9 +44191,9 @@ var assertSafeSkillTarget = (rootDir, skillName) => {
43999
44191
  if (!isValidSkillName(skillName)) {
44000
44192
  throw new Error(`Invalid skill name "${skillName}". Use lowercase letters, digits, and single hyphens only.`);
44001
44193
  }
44002
- const root = import_node_path13.default.resolve(rootDir);
44003
- const target = import_node_path13.default.resolve(root, skillName);
44004
- if (target !== import_node_path13.default.join(root, skillName) || !target.startsWith(root + import_node_path13.default.sep)) {
44194
+ const root = import_node_path14.default.resolve(rootDir);
44195
+ const target = import_node_path14.default.resolve(root, skillName);
44196
+ if (target !== import_node_path14.default.join(root, skillName) || !target.startsWith(root + import_node_path14.default.sep)) {
44005
44197
  throw new Error(`Invalid skill install target for "${skillName}".`);
44006
44198
  }
44007
44199
  return target;
@@ -44074,14 +44266,14 @@ var validateSkillDocument = (input) => {
44074
44266
  }
44075
44267
  }
44076
44268
  if (input.baseDir) {
44077
- const normalizedBase = import_node_path13.default.resolve(input.baseDir);
44269
+ const normalizedBase = import_node_path14.default.resolve(input.baseDir);
44078
44270
  for (const match2 of parsed.body.matchAll(/\]\(([^)]+)\)/g)) {
44079
44271
  const href = match2[1].trim();
44080
44272
  if (!href || /^[a-z][a-z0-9+.-]*:/i.test(href) || href.startsWith(`#`)) continue;
44081
- const target = import_node_path13.default.resolve(normalizedBase, href.split(`#`)[0]);
44082
- if (!target.startsWith(normalizedBase + import_node_path13.default.sep) && target !== normalizedBase) {
44273
+ const target = import_node_path14.default.resolve(normalizedBase, href.split(`#`)[0]);
44274
+ if (!target.startsWith(normalizedBase + import_node_path14.default.sep) && target !== normalizedBase) {
44083
44275
  addError(errors, `unsafe_reference`, `Relative link "${href}" escapes the skill directory.`);
44084
- } else if (!(0, import_node_fs3.existsSync)(target)) {
44276
+ } else if (!(0, import_node_fs4.existsSync)(target)) {
44085
44277
  addError(errors, `missing_reference`, `Relative link "${href}" does not exist in the skill directory.`);
44086
44278
  }
44087
44279
  }
@@ -44098,9 +44290,9 @@ var mergeIssues = (target, source) => {
44098
44290
  target.warnings.push(...source.warnings);
44099
44291
  };
44100
44292
  var validateScriptsDirectory = (skillDir, warnings) => {
44101
- const scriptsDir = import_node_path13.default.join(skillDir, `scripts`);
44102
- if (!(0, import_node_fs3.existsSync)(scriptsDir)) return;
44103
- const entries = (0, import_node_fs3.readdirSync)(scriptsDir, { withFileTypes: true }).filter((entry) => entry.isFile());
44293
+ const scriptsDir = import_node_path14.default.join(skillDir, `scripts`);
44294
+ if (!(0, import_node_fs4.existsSync)(scriptsDir)) return;
44295
+ const entries = (0, import_node_fs4.readdirSync)(scriptsDir, { withFileTypes: true }).filter((entry) => entry.isFile());
44104
44296
  if (entries.length === 0) {
44105
44297
  addWarning(warnings, `empty_scripts_dir`, `scripts/ exists but contains no files.`);
44106
44298
  }
@@ -44108,19 +44300,19 @@ var validateScriptsDirectory = (skillDir, warnings) => {
44108
44300
  var validateSkillDirectory = (skillDir, options = {}) => {
44109
44301
  const errors = [];
44110
44302
  const warnings = [];
44111
- const normalizedSkillDir = import_node_path13.default.resolve(skillDir);
44112
- const skillMdPath = import_node_path13.default.join(normalizedSkillDir, `SKILL.md`);
44113
- if (!(0, import_node_fs3.existsSync)(normalizedSkillDir) || !(0, import_node_fs3.statSync)(normalizedSkillDir).isDirectory()) {
44303
+ const normalizedSkillDir = import_node_path14.default.resolve(skillDir);
44304
+ const skillMdPath = import_node_path14.default.join(normalizedSkillDir, `SKILL.md`);
44305
+ if (!(0, import_node_fs4.existsSync)(normalizedSkillDir) || !(0, import_node_fs4.statSync)(normalizedSkillDir).isDirectory()) {
44114
44306
  addError(errors, `missing_skill_dir`, `Skill directory does not exist: ${normalizedSkillDir}`);
44115
44307
  return { ok: false, errors, warnings, skillDir: normalizedSkillDir, skillMdPath };
44116
44308
  }
44117
- if (!(0, import_node_fs3.existsSync)(skillMdPath) || !(0, import_node_fs3.statSync)(skillMdPath).isFile()) {
44309
+ if (!(0, import_node_fs4.existsSync)(skillMdPath) || !(0, import_node_fs4.statSync)(skillMdPath).isFile()) {
44118
44310
  addError(errors, `missing_skill_md`, `Skill directory must contain SKILL.md.`);
44119
44311
  return { ok: false, errors, warnings, skillDir: normalizedSkillDir, skillMdPath };
44120
44312
  }
44121
- const raw2 = (0, import_node_fs3.readFileSync)(skillMdPath, `utf-8`);
44313
+ const raw2 = (0, import_node_fs4.readFileSync)(skillMdPath, `utf-8`);
44122
44314
  const documentResult = validateSkillDocument({
44123
- skillName: options.expectedName ?? import_node_path13.default.basename(normalizedSkillDir),
44315
+ skillName: options.expectedName ?? import_node_path14.default.basename(normalizedSkillDir),
44124
44316
  skillMd: raw2,
44125
44317
  baseDir: normalizedSkillDir
44126
44318
  });
@@ -44172,26 +44364,26 @@ var formatSkillValidationIssues = (result) => {
44172
44364
  };
44173
44365
 
44174
44366
  // src/department/learning.ts
44175
- var ensureDir = (dir) => (0, import_node_fs4.mkdirSync)(dir, { recursive: true });
44367
+ var ensureDir = (dir) => (0, import_node_fs5.mkdirSync)(dir, { recursive: true });
44176
44368
  var readJsonArray = (filePath) => {
44177
- if (!(0, import_node_fs4.existsSync)(filePath)) return [];
44178
- return JSON.parse((0, import_node_fs4.readFileSync)(filePath, "utf-8"));
44369
+ if (!(0, import_node_fs5.existsSync)(filePath)) return [];
44370
+ return JSON.parse((0, import_node_fs5.readFileSync)(filePath, "utf-8"));
44179
44371
  };
44180
44372
  var writeJsonArray = (filePath, records) => {
44181
- ensureDir(import_node_path14.default.dirname(filePath));
44182
- (0, import_node_fs4.writeFileSync)(filePath, JSON.stringify(records, null, " "), "utf-8");
44373
+ ensureDir(import_node_path15.default.dirname(filePath));
44374
+ (0, import_node_fs5.writeFileSync)(filePath, JSON.stringify(records, null, " "), "utf-8");
44183
44375
  };
44184
44376
  var departmentMemoryPath = (departmentName) => {
44185
- return import_node_path14.default.join(getDepartmentWorkSpaceDir(departmentName), "department-memory.json");
44377
+ return import_node_path15.default.join(getDepartmentWorkSpaceDir(departmentName), "department-memory.json");
44186
44378
  };
44187
44379
  var departmentSkillPath = (departmentName) => {
44188
- return import_node_path14.default.join(getDepartmentWorkSpaceDir(departmentName), "department-skills.json");
44380
+ return import_node_path15.default.join(getDepartmentWorkSpaceDir(departmentName), "department-skills.json");
44189
44381
  };
44190
44382
  var departmentSkillDir = (departmentName, skillName) => {
44191
- return assertSafeSkillTarget(import_node_path14.default.join(getDepartmentWorkSpaceDir(departmentName), "skills"), skillName);
44383
+ return assertSafeSkillTarget(import_node_path15.default.join(getDepartmentWorkSpaceDir(departmentName), "skills"), skillName);
44192
44384
  };
44193
44385
  var proposalsPath = () => {
44194
- return import_node_path14.default.join(getDepartmentBaseDir(), "department-proposals.json");
44386
+ return import_node_path15.default.join(getDepartmentBaseDir(), "department-proposals.json");
44195
44387
  };
44196
44388
  var getDepartmentNameForHead = (request) => {
44197
44389
  const mailboxId = request?.departmentAgentId;
@@ -44207,7 +44399,7 @@ var listDepartmentMemories = (departmentName) => {
44207
44399
  var createDepartmentMemory = (departmentName, input) => {
44208
44400
  const now = Date.now();
44209
44401
  const memory = {
44210
- id: (0, import_node_crypto10.randomUUID)().slice(0, 8),
44402
+ id: (0, import_node_crypto11.randomUUID)().slice(0, 8),
44211
44403
  departmentName,
44212
44404
  title: input.title,
44213
44405
  content: input.content,
@@ -44258,7 +44450,7 @@ ${formatSkillValidationIssues(validation)}`);
44258
44450
  }
44259
44451
  const now = Date.now();
44260
44452
  const skill = {
44261
- id: (0, import_node_crypto10.randomUUID)().slice(0, 8),
44453
+ id: (0, import_node_crypto11.randomUUID)().slice(0, 8),
44262
44454
  departmentName,
44263
44455
  skillName: input.skillName,
44264
44456
  description: input.description,
@@ -44289,7 +44481,7 @@ var keepDepartmentSkill = (departmentName, id) => {
44289
44481
  ${formatSkillValidationIssues(validation)}`);
44290
44482
  }
44291
44483
  ensureDir(skillDir);
44292
- (0, import_node_fs4.writeFileSync)(import_node_path14.default.join(skillDir, "SKILL.md"), records[idx].skillMd, "utf-8");
44484
+ (0, import_node_fs5.writeFileSync)(import_node_path15.default.join(skillDir, "SKILL.md"), records[idx].skillMd, "utf-8");
44293
44485
  const smokeTest = smokeTestSkillDirectory(skillDir, { expectedName: records[idx].skillName });
44294
44486
  if (!smokeTest.ok) {
44295
44487
  throw new Error(`[departmentSkill] Skill smoke test \u5931\u8D25\uFF1A
@@ -44310,7 +44502,7 @@ var createDepartmentProposal = (input) => {
44310
44502
  const records = readJsonArray(proposalsPath());
44311
44503
  const proposal = {
44312
44504
  ...input,
44313
- id: (0, import_node_crypto10.randomUUID)().slice(0, 8),
44505
+ id: (0, import_node_crypto11.randomUUID)().slice(0, 8),
44314
44506
  status: "pending",
44315
44507
  createdAt: Date.now()
44316
44508
  };
@@ -44669,8 +44861,8 @@ var mailboxFollowup = {
44669
44861
 
44670
44862
  // src/tools/tools/Bash.ts
44671
44863
  var import_node_child_process = require("node:child_process");
44672
- var import_node_crypto11 = require("node:crypto");
44673
- var import_node_fs5 = require("node:fs");
44864
+ var import_node_crypto12 = require("node:crypto");
44865
+ var import_node_fs6 = require("node:fs");
44674
44866
  var DESCRIPTION30 = `\u5728\u7CFB\u7EDF shell \u4E2D\u6267\u884C\u547D\u4EE4\u3002
44675
44867
 
44676
44868
  \u7528\u9014\uFF1A
@@ -44710,7 +44902,7 @@ var sessions = /* @__PURE__ */ new Map();
44710
44902
  function findExecutableShell2() {
44711
44903
  for (const shell of SHELL_CANDIDATES2) {
44712
44904
  try {
44713
- (0, import_node_fs5.accessSync)(shell, import_node_fs5.constants.X_OK);
44905
+ (0, import_node_fs6.accessSync)(shell, import_node_fs6.constants.X_OK);
44714
44906
  return shell;
44715
44907
  } catch {
44716
44908
  }
@@ -44719,11 +44911,11 @@ function findExecutableShell2() {
44719
44911
  }
44720
44912
  function validateCwd(cwd) {
44721
44913
  try {
44722
- const stat11 = (0, import_node_fs5.statSync)(cwd);
44914
+ const stat11 = (0, import_node_fs6.statSync)(cwd);
44723
44915
  if (!stat11.isDirectory()) {
44724
44916
  return `[bash] \u9519\u8BEF: cwd \u4E0D\u662F\u76EE\u5F55: ${cwd}`;
44725
44917
  }
44726
- (0, import_node_fs5.accessSync)(cwd, import_node_fs5.constants.R_OK | import_node_fs5.constants.X_OK);
44918
+ (0, import_node_fs6.accessSync)(cwd, import_node_fs6.constants.R_OK | import_node_fs6.constants.X_OK);
44727
44919
  return null;
44728
44920
  } catch (err) {
44729
44921
  const code = err.code;
@@ -44966,7 +45158,7 @@ var bashTool = {
44966
45158
  stdio: ["pipe", "pipe", "pipe"],
44967
45159
  detached: process.platform !== "win32"
44968
45160
  });
44969
- const id = (0, import_node_crypto11.randomUUID)().slice(0, 8);
45161
+ const id = (0, import_node_crypto12.randomUUID)().slice(0, 8);
44970
45162
  const session = {
44971
45163
  id,
44972
45164
  command,
@@ -45184,12 +45376,11 @@ var createSaasCreditToolHookPlugin = () => ({
45184
45376
  toolCallId: ctx.toolCallId,
45185
45377
  requestId: ctx.userRequest?.requestId ?? null,
45186
45378
  occurredAt: (/* @__PURE__ */ new Date()).toISOString(),
45187
- ...usage,
45188
- metadata: {
45189
- platform: ctx.userRequest?.platform ?? null,
45190
- departmentAgentId: ctx.userRequest?.departmentAgentId ?? null,
45191
- input: sanitizedWebSearchInput(ctx.input)
45192
- }
45379
+ meterType: usage.meterType,
45380
+ provider: usage.provider,
45381
+ unit: usage.unit,
45382
+ quantity: usage.quantity,
45383
+ metadata: usage.metadata
45193
45384
  })
45194
45385
  });
45195
45386
  const body = await parseJson2(response);
@@ -45197,7 +45388,7 @@ var createSaasCreditToolHookPlugin = () => ({
45197
45388
  if (response.status === 402 || status === "credit_exhausted") {
45198
45389
  throw new UserRecoverableToolError(
45199
45390
  "credit_exhausted",
45200
- "Credit \u4F59\u989D\u4E0D\u8DB3\uFF0C\u65E0\u6CD5\u5B8C\u6210\u672C\u6B21\u7F51\u9875\u641C\u7D22\u3002\u8BF7\u5148\u5145\u503C\u6216\u4F7F\u7528\u6FC0\u6D3B\u7801\u589E\u52A0 Credit\u3002",
45391
+ usage.userMessage,
45201
45392
  `[saas-credit] ${ctx.toolName} credit exhausted`
45202
45393
  );
45203
45394
  }
@@ -45207,14 +45398,27 @@ var createSaasCreditToolHookPlugin = () => ({
45207
45398
  }
45208
45399
  });
45209
45400
  function meteredToolUsage(ctx) {
45210
- if (ctx.toolName !== "websearch") return null;
45211
- if (isWebSearchErrorResult(ctx.resultText)) return null;
45212
- return {
45213
- meterType: "web.content_retrieval",
45214
- provider: "exa",
45215
- unit: "content",
45216
- quantity: normalizeNumResults(ctx.input.numResults)
45217
- };
45401
+ if (ctx.toolName === "websearch") {
45402
+ if (isWebSearchErrorResult(ctx.resultText)) return null;
45403
+ return {
45404
+ meterType: "web.content_retrieval",
45405
+ provider: "exa",
45406
+ unit: "content",
45407
+ quantity: normalizeNumResults(ctx.input.numResults),
45408
+ userMessage: "Credit \u4F59\u989D\u4E0D\u8DB3\uFF0C\u65E0\u6CD5\u5B8C\u6210\u672C\u6B21\u7F51\u9875\u641C\u7D22\u3002\u8BF7\u5148\u5145\u503C\u6216\u4F7F\u7528\u6FC0\u6D3B\u7801\u589E\u52A0 Credit\u3002",
45409
+ metadata: {
45410
+ platform: ctx.userRequest?.platform ?? null,
45411
+ departmentAgentId: ctx.userRequest?.departmentAgentId ?? null,
45412
+ input: sanitizedWebSearchInput(ctx.input)
45413
+ }
45414
+ };
45415
+ }
45416
+ if (ctx.toolName === "image_generate") {
45417
+ const usage = imageGenerateUsage(ctx);
45418
+ if (!usage) return null;
45419
+ return usage;
45420
+ }
45421
+ return null;
45218
45422
  }
45219
45423
  function normalizeNumResults(value) {
45220
45424
  if (typeof value !== "number" || !Number.isFinite(value)) return DEFAULT_WEB_SEARCH_NUM_RESULTS;
@@ -45233,6 +45437,37 @@ function sanitizedWebSearchInput(input) {
45233
45437
  contextMaxCharacters: typeof input.contextMaxCharacters === "number" ? input.contextMaxCharacters : null
45234
45438
  };
45235
45439
  }
45440
+ function imageGenerateUsage(ctx) {
45441
+ const result = parseResultJson(ctx.resultText);
45442
+ if (!result || result.ok !== true) return null;
45443
+ const savedFiles = Array.isArray(result.savedFiles) ? result.savedFiles : [];
45444
+ const quantity = savedFiles.length > 0 ? savedFiles.length : 1;
45445
+ return {
45446
+ meterType: "image.generate",
45447
+ provider: "shanyi",
45448
+ unit: "image",
45449
+ quantity,
45450
+ userMessage: "Credit \u4F59\u989D\u4E0D\u8DB3\uFF0C\u65E0\u6CD5\u751F\u6210\u56FE\u7247\u3002\u751F\u6210\u56FE\u7247\u6BCF\u5F20\u9700\u8981 20 Credit\uFF08\u7EA6 0.2 \u5143\uFF09\uFF0C\u8BF7\u5148\u5145\u503C\u6216\u4F7F\u7528\u6FC0\u6D3B\u7801\u589E\u52A0 Credit\u3002",
45451
+ metadata: {
45452
+ platform: ctx.userRequest?.platform ?? null,
45453
+ departmentAgentId: ctx.userRequest?.departmentAgentId ?? null,
45454
+ model: typeof result.model === "string" ? result.model : null,
45455
+ size: typeof result.size === "string" ? result.size : null,
45456
+ resolution: typeof result.resolution === "string" ? result.resolution : null,
45457
+ requestId: typeof result.requestId === "string" ? result.requestId : null,
45458
+ prompt: typeof ctx.input.prompt === "string" ? ctx.input.prompt.slice(0, 500) : null
45459
+ }
45460
+ };
45461
+ }
45462
+ function parseResultJson(value) {
45463
+ if (!value) return null;
45464
+ try {
45465
+ const parsed = JSON.parse(value);
45466
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
45467
+ } catch {
45468
+ return null;
45469
+ }
45470
+ }
45236
45471
  async function parseJson2(response) {
45237
45472
  try {
45238
45473
  return await response.json();
@@ -45733,10 +45968,10 @@ var readDreamHistoryLimit = () => {
45733
45968
  };
45734
45969
 
45735
45970
  // src/skillForge/SkillForgeEngine.ts
45736
- var import_node_fs6 = require("node:fs");
45971
+ var import_node_fs7 = require("node:fs");
45737
45972
  var import_node_os2 = require("node:os");
45738
- var import_node_path15 = require("node:path");
45739
- var import_node_crypto12 = require("node:crypto");
45973
+ var import_node_path16 = require("node:path");
45974
+ var import_node_crypto13 = require("node:crypto");
45740
45975
  var SkillForgeEngine = class {
45741
45976
  proposalStorage;
45742
45977
  draftRoot;
@@ -45744,8 +45979,8 @@ var SkillForgeEngine = class {
45744
45979
  constructor(deps) {
45745
45980
  this.proposalStorage = deps.proposalStorage;
45746
45981
  const opt = deps.options ?? {};
45747
- this.draftRoot = opt.draftRoot ?? (0, import_node_path15.join)((0, import_node_os2.homedir)(), ".duclaw", "skill-proposals");
45748
- this.skillsInstallDir = opt.skillsInstallDir ?? (0, import_node_path15.join)((0, import_node_os2.homedir)(), ".agents", "skills");
45982
+ this.draftRoot = opt.draftRoot ?? (0, import_node_path16.join)((0, import_node_os2.homedir)(), ".duclaw", "skill-proposals");
45983
+ this.skillsInstallDir = opt.skillsInstallDir ?? (0, import_node_path16.join)((0, import_node_os2.homedir)(), ".agents", "skills");
45749
45984
  }
45750
45985
  // ---------- 公开方法 ----------
45751
45986
  /**
@@ -45770,14 +46005,14 @@ ${formatSkillValidationIssues(validation)}`);
45770
46005
  if (pending.some((p) => p.skillName === skillName)) {
45771
46006
  return null;
45772
46007
  }
45773
- const id = (0, import_node_crypto12.randomBytes)(4).toString("hex");
45774
- const draftDir = (0, import_node_path15.join)(this.draftRoot, userId, id);
45775
- (0, import_node_fs6.mkdirSync)(draftDir, { recursive: true });
45776
- (0, import_node_fs6.writeFileSync)((0, import_node_path15.join)(draftDir, "SKILL.md"), skillMd, "utf-8");
46008
+ const id = (0, import_node_crypto13.randomBytes)(4).toString("hex");
46009
+ const draftDir = (0, import_node_path16.join)(this.draftRoot, userId, id);
46010
+ (0, import_node_fs7.mkdirSync)(draftDir, { recursive: true });
46011
+ (0, import_node_fs7.writeFileSync)((0, import_node_path16.join)(draftDir, "SKILL.md"), skillMd, "utf-8");
45777
46012
  const directoryValidation = validateSkillDirectory(draftDir, { expectedName: skillName });
45778
46013
  if (!directoryValidation.ok) {
45779
46014
  try {
45780
- (0, import_node_fs6.rmSync)(draftDir, { recursive: true, force: true });
46015
+ (0, import_node_fs7.rmSync)(draftDir, { recursive: true, force: true });
45781
46016
  } catch {
45782
46017
  }
45783
46018
  throw new Error(`[skillForge] Skill \u76EE\u5F55\u6821\u9A8C\u5931\u8D25\uFF1A
@@ -45812,26 +46047,26 @@ ${formatSkillValidationIssues(validation)}`);
45812
46047
  }
45813
46048
  const target = assertSafeSkillTarget(this.skillsInstallDir, proposal.skillName);
45814
46049
  let installedTarget = target;
45815
- if ((0, import_node_fs6.existsSync)(target)) {
46050
+ if ((0, import_node_fs7.existsSync)(target)) {
45816
46051
  const alt = target + "-" + proposalId;
45817
- (0, import_node_fs6.mkdirSync)(alt, { recursive: true });
45818
- (0, import_node_fs6.cpSync)(proposal.draftDir, alt, { recursive: true });
46052
+ (0, import_node_fs7.mkdirSync)(alt, { recursive: true });
46053
+ (0, import_node_fs7.cpSync)(proposal.draftDir, alt, { recursive: true });
45819
46054
  installedTarget = alt;
45820
46055
  } else {
45821
- (0, import_node_fs6.mkdirSync)(target, { recursive: true });
45822
- (0, import_node_fs6.cpSync)(proposal.draftDir, target, { recursive: true });
46056
+ (0, import_node_fs7.mkdirSync)(target, { recursive: true });
46057
+ (0, import_node_fs7.cpSync)(proposal.draftDir, target, { recursive: true });
45823
46058
  }
45824
46059
  const smokeTest = smokeTestSkillDirectory(installedTarget, { expectedName: proposal.skillName });
45825
46060
  if (!smokeTest.ok) {
45826
46061
  try {
45827
- (0, import_node_fs6.rmSync)(installedTarget, { recursive: true, force: true });
46062
+ (0, import_node_fs7.rmSync)(installedTarget, { recursive: true, force: true });
45828
46063
  } catch {
45829
46064
  }
45830
46065
  throw new Error(`[skillForge] Skill smoke test \u5931\u8D25\uFF0C\u5DF2\u56DE\u6EDA\u843D\u5730\u76EE\u5F55\uFF1A
45831
46066
  ${formatSkillValidationIssues(smokeTest)}`);
45832
46067
  }
45833
46068
  try {
45834
- (0, import_node_fs6.rmSync)(proposal.draftDir, { recursive: true, force: true });
46069
+ (0, import_node_fs7.rmSync)(proposal.draftDir, { recursive: true, force: true });
45835
46070
  } catch {
45836
46071
  }
45837
46072
  await this.removeProposal(userId, proposalId);
@@ -45843,7 +46078,7 @@ ${formatSkillValidationIssues(smokeTest)}`);
45843
46078
  const proposal = list.find((p) => p.id === proposalId);
45844
46079
  if (!proposal) return null;
45845
46080
  try {
45846
- (0, import_node_fs6.rmSync)(proposal.draftDir, { recursive: true, force: true });
46081
+ (0, import_node_fs7.rmSync)(proposal.draftDir, { recursive: true, force: true });
45847
46082
  } catch {
45848
46083
  }
45849
46084
  await this.removeProposal(userId, proposalId);
@@ -46052,7 +46287,7 @@ var skillForgeDrop = (engine) => ({
46052
46287
  });
46053
46288
 
46054
46289
  // src/memory/MemoryEngine.ts
46055
- var import_node_crypto13 = require("node:crypto");
46290
+ var import_node_crypto14 = require("node:crypto");
46056
46291
  var MemoryEngine = class {
46057
46292
  storage;
46058
46293
  recallIndexStorage;
@@ -46080,7 +46315,7 @@ var MemoryEngine = class {
46080
46315
  }
46081
46316
  const now = Date.now();
46082
46317
  const memory = {
46083
- id: (0, import_node_crypto13.randomBytes)(4).toString("hex"),
46318
+ id: (0, import_node_crypto14.randomBytes)(4).toString("hex"),
46084
46319
  userId,
46085
46320
  title,
46086
46321
  content,
@@ -46732,13 +46967,13 @@ var COMPANY_VALUES_PROMPT = `<\u516C\u53F8\u5171\u540C\u4FE1\u5FF5>
46732
46967
  </\u516C\u53F8\u5171\u540C\u4FE1\u5FF5>`;
46733
46968
 
46734
46969
  // src/agent/outboundDedup.ts
46735
- var import_node_crypto14 = require("node:crypto");
46970
+ var import_node_crypto15 = require("node:crypto");
46736
46971
  var DEFAULT_WINDOW_MS = 15e3;
46737
46972
  var recentSends = /* @__PURE__ */ new Map();
46738
46973
  var lastSweepAt = 0;
46739
46974
  var normalize3 = (text2) => text2.replace(/\s+/g, " ").trim();
46740
46975
  var keyFor = (userId, normalized) => {
46741
- const hash = (0, import_node_crypto14.createHash)("sha1").update(normalized).digest("hex");
46976
+ const hash = (0, import_node_crypto15.createHash)("sha1").update(normalized).digest("hex");
46742
46977
  return `${userId}::${hash}`;
46743
46978
  };
46744
46979
  var sweep = (now, windowMs) => {
@@ -46786,12 +47021,12 @@ var isAbortError2 = (error) => {
46786
47021
  return error.name === "AbortError" || error.message.includes("aborted") || error.message.includes("AbortError");
46787
47022
  };
46788
47023
  var llmRequestIdForTurn = (request, messages, system, tools) => {
46789
- const hash = (0, import_node_crypto15.createHash)("sha256").update(request.requestId).update("\0").update(system).update("\0").update(JSON.stringify(messages)).update("\0").update(JSON.stringify(tools.map((tool) => tool.name).sort())).digest("hex").slice(0, 40);
47024
+ const hash = (0, import_node_crypto16.createHash)("sha256").update(request.requestId).update("\0").update(system).update("\0").update(JSON.stringify(messages)).update("\0").update(JSON.stringify(tools.map((tool) => tool.name).sort())).digest("hex").slice(0, 40);
46790
47025
  return `dreq_${hash}`;
46791
47026
  };
46792
47027
  var getDefaultAgentConfig = (tools, systemPrompt) => {
46793
47028
  loadEnv();
46794
- (0, import_node_fs7.mkdirSync)(DEFAULT_WORKSPACE_PATH, { recursive: true });
47029
+ (0, import_node_fs8.mkdirSync)(DEFAULT_WORKSPACE_PATH, { recursive: true });
46795
47030
  let system = ``;
46796
47031
  if (!systemPrompt) {
46797
47032
  system = `
@@ -47024,7 +47259,8 @@ var createAgent = (config2 = getDefaultAgentConfig()) => {
47024
47259
  if (config2.workspacePath && !request.defaultWorkDir) {
47025
47260
  request.defaultWorkDir = config2.workspacePath;
47026
47261
  }
47027
- const { userId, content, job } = request;
47262
+ const { userId, job } = request;
47263
+ const content = attachRecentAttachmentContext(request, request.content);
47028
47264
  const internalOnly = request.metadata?.internalOnly === true;
47029
47265
  const injectedEventIds = /* @__PURE__ */ new Set();
47030
47266
  const interruptQueuedEventIds = /* @__PURE__ */ new Set();
@@ -51866,7 +52102,7 @@ var cors = (options) => {
51866
52102
 
51867
52103
  // src/server/index.ts
51868
52104
  var import_promises14 = require("node:fs/promises");
51869
- var import_node_path19 = __toESM(require("node:path"));
52105
+ var import_node_path20 = __toESM(require("node:path"));
51870
52106
 
51871
52107
  // src/git/worktree.ts
51872
52108
  var import_child_process2 = require("child_process");
@@ -53211,8 +53447,8 @@ mailboxRoutes.get("/mailbox/summary", (c) => {
53211
53447
 
53212
53448
  // src/server/routes/memory.ts
53213
53449
  var import_redis4 = __toESM(require_dist2());
53214
- var import_node_fs8 = require("node:fs");
53215
- var import_node_path16 = __toESM(require("node:path"));
53450
+ var import_node_fs9 = require("node:fs");
53451
+ var import_node_path17 = __toESM(require("node:path"));
53216
53452
  var memoryEngineSingleton = null;
53217
53453
  var dreamStorageSingleton = null;
53218
53454
  var dreamHistoryStorageSingleton = null;
@@ -53312,12 +53548,12 @@ var addPlausibleUserId = (set, userId) => {
53312
53548
  if (normalized && isPlausibleUserId(normalized)) set.add(normalized);
53313
53549
  };
53314
53550
  var readJsonFilesFromDir = (dir) => {
53315
- if (!(0, import_node_fs8.existsSync)(dir)) return [];
53551
+ if (!(0, import_node_fs9.existsSync)(dir)) return [];
53316
53552
  const result = [];
53317
- for (const file of (0, import_node_fs8.readdirSync)(dir)) {
53553
+ for (const file of (0, import_node_fs9.readdirSync)(dir)) {
53318
53554
  if (!file.endsWith(".json")) continue;
53319
53555
  try {
53320
- result.push(JSON.parse((0, import_node_fs8.readFileSync)(import_node_path16.default.join(dir, file), "utf-8")));
53556
+ result.push(JSON.parse((0, import_node_fs9.readFileSync)(import_node_path17.default.join(dir, file), "utf-8")));
53321
53557
  } catch (err) {
53322
53558
  console.warn(`[memoryRoutes] \u8DF3\u8FC7\u65E0\u6CD5\u89E3\u6790\u7684\u672C\u5730\u4E0A\u4E0B\u6587\u6587\u4EF6 ${file}: ${err.message}`);
53323
53559
  }
@@ -53325,13 +53561,13 @@ var readJsonFilesFromDir = (dir) => {
53325
53561
  return result;
53326
53562
  };
53327
53563
  var extractFileBackedStorageKeysForTest = (dataDir) => {
53328
- const kvRoot = import_node_path16.default.join(dataDir, "kv");
53329
- if (!(0, import_node_fs8.existsSync)(kvRoot)) return [];
53564
+ const kvRoot = import_node_path17.default.join(dataDir, "kv");
53565
+ if (!(0, import_node_fs9.existsSync)(kvRoot)) return [];
53330
53566
  const keys = [];
53331
- for (const prefixDir of (0, import_node_fs8.readdirSync)(kvRoot)) {
53332
- const absolutePrefixDir = import_node_path16.default.join(kvRoot, prefixDir);
53333
- if (!(0, import_node_fs8.existsSync)(absolutePrefixDir)) continue;
53334
- for (const file of (0, import_node_fs8.readdirSync)(absolutePrefixDir)) {
53567
+ for (const prefixDir of (0, import_node_fs9.readdirSync)(kvRoot)) {
53568
+ const absolutePrefixDir = import_node_path17.default.join(kvRoot, prefixDir);
53569
+ if (!(0, import_node_fs9.existsSync)(absolutePrefixDir)) continue;
53570
+ for (const file of (0, import_node_fs9.readdirSync)(absolutePrefixDir)) {
53335
53571
  if (!file.endsWith(".json")) continue;
53336
53572
  try {
53337
53573
  const logicalKey = Buffer.from(file.replace(/\.json$/, ""), "base64url").toString("utf8");
@@ -53349,12 +53585,12 @@ var collectLocalConversationUserIds = () => {
53349
53585
  };
53350
53586
  var extractLocalConversationUserIdsForTest = (homeDir) => {
53351
53587
  const userIds = /* @__PURE__ */ new Set();
53352
- for (const context of readJsonFilesFromDir(import_node_path16.default.join(homeDir, "goal-context"))) {
53588
+ for (const context of readJsonFilesFromDir(import_node_path17.default.join(homeDir, "goal-context"))) {
53353
53589
  addPlausibleUserId(userIds, context.threadId);
53354
53590
  addPlausibleUserId(userIds, context.originUserId);
53355
53591
  if (context.goalId) addPlausibleUserId(userIds, `kanban:goal:${context.goalId}`);
53356
53592
  }
53357
- for (const goal of readJsonFilesFromDir(import_node_path16.default.join(homeDir, "tasks"))) {
53593
+ for (const goal of readJsonFilesFromDir(import_node_path17.default.join(homeDir, "tasks"))) {
53358
53594
  if (goal.id) addPlausibleUserId(userIds, `kanban:goal:${goal.id}`);
53359
53595
  }
53360
53596
  return userIds;
@@ -53771,7 +54007,7 @@ ${item.dreamContent}` : item.dreamContent).join("\n\n");
53771
54007
  });
53772
54008
 
53773
54009
  // src/server/routes/tools.ts
53774
- var import_node_path17 = __toESM(require("node:path"));
54010
+ var import_node_path18 = __toESM(require("node:path"));
53775
54011
  var toolRoutes = new Hono2();
53776
54012
  var listActiveDepartmentSkills = () => {
53777
54013
  return listDepartments().flatMap(
@@ -53782,7 +54018,7 @@ var listActiveDepartmentSkills = () => {
53782
54018
  scope: "department",
53783
54019
  departmentName: department.name,
53784
54020
  detail: () => {
53785
- const skillDir = import_node_path17.default.join(getDepartmentWorkSpaceDir(department.name), "skills", skill.skillName);
54021
+ const skillDir = import_node_path18.default.join(getDepartmentWorkSpaceDir(department.name), "skills", skill.skillName);
53786
54022
  return `Base directory for this skill: ${skillDir}
53787
54023
 
53788
54024
  ${skill.skillMd}`;
@@ -53871,7 +54107,7 @@ var systemRoutes = new Hono2();
53871
54107
  var startTime = Date.now();
53872
54108
  systemRoutes.get("/system/info", (c) => {
53873
54109
  return c.json({
53874
- version: true ? "1.9.13" : "unknown",
54110
+ version: true ? "1.9.15" : "unknown",
53875
54111
  uptime: Math.floor((Date.now() - startTime) / 1e3),
53876
54112
  env: process.env.NODE_ENV || "development",
53877
54113
  nodeVersion: process.version
@@ -53879,9 +54115,9 @@ systemRoutes.get("/system/info", (c) => {
53879
54115
  });
53880
54116
 
53881
54117
  // src/server/routes/mobile.ts
53882
- var import_node_crypto16 = require("node:crypto");
54118
+ var import_node_crypto17 = require("node:crypto");
53883
54119
  var import_promises13 = require("node:fs/promises");
53884
- var import_node_path18 = __toESM(require("node:path"));
54120
+ var import_node_path19 = __toESM(require("node:path"));
53885
54121
  var mobileRoutes = new Hono2();
53886
54122
  var resolveMobileUserId = (body, headerUserId) => {
53887
54123
  return body.userId?.trim() || headerUserId?.trim() || "ios:local-user";
@@ -53923,12 +54159,12 @@ ${goal.tasks.map((task) => `- [${task.status}] ${task.subject}`).join("\n")}` :
53923
54159
  return lines.join("\n");
53924
54160
  };
53925
54161
  var sanitizeFileName = (fileName) => {
53926
- const cleaned = import_node_path18.default.basename(fileName).replace(/[^\w.\-()\u4e00-\u9fa5 ]+/g, "_").trim();
54162
+ const cleaned = import_node_path19.default.basename(fileName).replace(/[^\w.\-()\u4e00-\u9fa5 ]+/g, "_").trim();
53927
54163
  return cleaned || `attachment-${Date.now()}`;
53928
54164
  };
53929
54165
  var inferAttachmentType2 = (mimeType = "", fileName = "") => {
53930
54166
  if (mimeType.startsWith("image/")) return "image";
53931
- const ext = import_node_path18.default.extname(fileName).toLowerCase();
54167
+ const ext = import_node_path19.default.extname(fileName).toLowerCase();
53932
54168
  if ([".png", ".jpg", ".jpeg", ".gif", ".webp"].includes(ext)) return "image";
53933
54169
  return "file";
53934
54170
  };
@@ -53983,11 +54219,11 @@ mobileRoutes.post("/mobile/attachments", async (c) => {
53983
54219
  const fileName = sanitizeFileName(body.fileName || `attachment-${Date.now()}`);
53984
54220
  const mimeType = body.mimeType || "application/octet-stream";
53985
54221
  const type = inferAttachmentType2(mimeType, fileName);
53986
- const attachmentId = (0, import_node_crypto16.randomUUID)();
54222
+ const attachmentId = (0, import_node_crypto17.randomUUID)();
53987
54223
  const buffer = Buffer.from(dataBase64, "base64");
53988
- const dir = import_node_path18.default.join(getDuclawWorkspaceDir(), mobileUserId, "mobile", type === "image" ? "images" : "files");
54224
+ const dir = import_node_path19.default.join(getDuclawWorkspaceDir(), mobileUserId, "mobile", type === "image" ? "images" : "files");
53989
54225
  await (0, import_promises13.mkdir)(dir, { recursive: true });
53990
- const localPath = import_node_path18.default.join(dir, `${attachmentId}-${fileName}`);
54226
+ const localPath = import_node_path19.default.join(dir, `${attachmentId}-${fileName}`);
53991
54227
  await (0, import_promises13.writeFile)(localPath, buffer);
53992
54228
  const attachment = saveMobileAttachment({
53993
54229
  id: attachmentId,
@@ -54015,7 +54251,7 @@ mobileRoutes.post("/mobile/messages", async (c) => {
54015
54251
  }
54016
54252
  const mobileUserId = resolveMobileUserId(body, c.req.header("x-user-id"));
54017
54253
  const threadId = resolveThreadId(body.goalId, mobileUserId);
54018
- const requestId = body.clientMessageId?.trim() || (0, import_node_crypto16.randomUUID)();
54254
+ const requestId = body.clientMessageId?.trim() || (0, import_node_crypto17.randomUUID)();
54019
54255
  const agentText = body.contextText?.trim() || text2;
54020
54256
  const attachmentContent = buildContentWithAttachments(agentText, attachments);
54021
54257
  const content = buildGoalPrompt(body.goalId, attachmentContent.content);
@@ -54206,7 +54442,7 @@ function createServer() {
54206
54442
  app.route("/api", mobileRoutes);
54207
54443
  app.use("/*", serveStatic({ root: webDistRoot }));
54208
54444
  app.get("/*", async (c) => {
54209
- const indexHtml = await (0, import_promises14.readFile)(import_node_path19.default.join(webDistRoot, "index.html"), "utf8");
54445
+ const indexHtml = await (0, import_promises14.readFile)(import_node_path20.default.join(webDistRoot, "index.html"), "utf8");
54210
54446
  const tenantId = c.req.header("x-tenant-id");
54211
54447
  const assetBase = tenantId ? `/t/${tenantId}` : "";
54212
54448
  const html = indexHtml.replaceAll('"./', `"${assetBase}/`);
@@ -54233,10 +54469,10 @@ function shouldStartCoreChannelGateways(env = process.env) {
54233
54469
  }
54234
54470
 
54235
54471
  // src/runtime/saasAssets.ts
54236
- var import_node_fs9 = require("node:fs");
54472
+ var import_node_fs10 = require("node:fs");
54237
54473
  var import_promises15 = require("node:fs/promises");
54238
54474
  var import_node_os3 = require("node:os");
54239
- var import_node_path20 = __toESM(require("node:path"));
54475
+ var import_node_path21 = __toESM(require("node:path"));
54240
54476
  var MAX_CONTEXT_ASSETS = Number(process.env.DUCLAW_SAAS_ASSET_CONTEXT_LIMIT ?? 1e3);
54241
54477
  var MAX_SKILL_ASSETS = Number(process.env.DUCLAW_SAAS_ASSET_SKILL_LIMIT ?? 200);
54242
54478
  async function restoreSaasRuntimeAssets(reason) {
@@ -54266,8 +54502,8 @@ async function restoreSaasRuntimeAssets(reason) {
54266
54502
  async function restoreContextAsset(context, overwrite) {
54267
54503
  const target = contextPathForSourceKey(context.sourceKey);
54268
54504
  if (!target) return false;
54269
- if (!overwrite && (0, import_node_fs9.existsSync)(target)) return false;
54270
- await (0, import_promises15.mkdir)(import_node_path20.default.dirname(target), { recursive: true });
54505
+ if (!overwrite && (0, import_node_fs10.existsSync)(target)) return false;
54506
+ await (0, import_promises15.mkdir)(import_node_path21.default.dirname(target), { recursive: true });
54271
54507
  await (0, import_promises15.writeFile)(target, JSON.stringify(context.payload), "utf8");
54272
54508
  return true;
54273
54509
  }
@@ -54275,8 +54511,8 @@ async function restoreSkillAsset(skill, overwrite) {
54275
54511
  if (!skill.skillMd) return false;
54276
54512
  const target = safeSkillTargetPath(skill.sourcePath, skill.skillName);
54277
54513
  if (!target) return false;
54278
- if (!overwrite && (0, import_node_fs9.existsSync)(target)) return false;
54279
- await (0, import_promises15.mkdir)(import_node_path20.default.dirname(target), { recursive: true });
54514
+ if (!overwrite && (0, import_node_fs10.existsSync)(target)) return false;
54515
+ await (0, import_promises15.mkdir)(import_node_path21.default.dirname(target), { recursive: true });
54280
54516
  await (0, import_promises15.writeFile)(target, skill.skillMd, "utf8");
54281
54517
  return true;
54282
54518
  }
@@ -54309,30 +54545,30 @@ function runtimeAssetClient() {
54309
54545
  function contextPathForSourceKey(sourceKey) {
54310
54546
  if (sourceKey.startsWith("agent:")) {
54311
54547
  const logicalKey = sourceKey.slice("agent:".length);
54312
- return import_node_path20.default.join(getDuclawDataDir(), "kv", "agent", `${Buffer.from(logicalKey).toString("base64url")}.json`);
54548
+ return import_node_path21.default.join(getDuclawDataDir(), "kv", "agent", `${Buffer.from(logicalKey).toString("base64url")}.json`);
54313
54549
  }
54314
54550
  if (sourceKey.startsWith("goal-context:")) {
54315
- return import_node_path20.default.join(getDuclawHomeDir(), "goal-context", `${sourceKey.slice("goal-context:".length)}.json`);
54551
+ return import_node_path21.default.join(getDuclawHomeDir(), "goal-context", `${sourceKey.slice("goal-context:".length)}.json`);
54316
54552
  }
54317
54553
  if (sourceKey.startsWith("tasks:")) {
54318
- return import_node_path20.default.join(getDuclawHomeDir(), "tasks", `${sourceKey.slice("tasks:".length)}.json`);
54554
+ return import_node_path21.default.join(getDuclawHomeDir(), "tasks", `${sourceKey.slice("tasks:".length)}.json`);
54319
54555
  }
54320
54556
  return null;
54321
54557
  }
54322
54558
  function safeSkillTargetPath(sourcePath, skillName) {
54323
- const allowedRoots = skillRoots().map((root) => import_node_path20.default.resolve(root));
54324
- const normalized = import_node_path20.default.resolve(sourcePath);
54325
- if (allowedRoots.some((root) => normalized === import_node_path20.default.join(root, import_node_path20.default.basename(import_node_path20.default.dirname(normalized)), "SKILL.md"))) {
54559
+ const allowedRoots = skillRoots().map((root) => import_node_path21.default.resolve(root));
54560
+ const normalized = import_node_path21.default.resolve(sourcePath);
54561
+ if (allowedRoots.some((root) => normalized === import_node_path21.default.join(root, import_node_path21.default.basename(import_node_path21.default.dirname(normalized)), "SKILL.md"))) {
54326
54562
  return normalized;
54327
54563
  }
54328
54564
  const safeName = skillName.replace(/[^a-zA-Z0-9._-]/g, "-").slice(0, 120) || "imported-skill";
54329
- return import_node_path20.default.join("/home/user/app/skills", safeName, "SKILL.md");
54565
+ return import_node_path21.default.join("/home/user/app/skills", safeName, "SKILL.md");
54330
54566
  }
54331
54567
  function skillRoots() {
54332
54568
  return [
54333
54569
  "/home/user/app/skills",
54334
- import_node_path20.default.join(getDuclawHomeDir(), "skills"),
54335
- import_node_path20.default.join((0, import_node_os3.homedir)(), ".agents", "skills")
54570
+ import_node_path21.default.join(getDuclawHomeDir(), "skills"),
54571
+ import_node_path21.default.join((0, import_node_os3.homedir)(), ".agents", "skills")
54336
54572
  ];
54337
54573
  }
54338
54574