duclaw-cli 1.9.13 → 1.9.14

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.14" : "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";
@@ -41722,6 +41899,10 @@ var imageUnderstand = {
41722
41899
  type: `string`,
41723
41900
  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
41901
  },
41902
+ attachment_id: {
41903
+ type: `string`,
41904
+ 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`
41905
+ },
41725
41906
  prompt: {
41726
41907
  type: `string`,
41727
41908
  description: `prompt\u5B57\u6BB5\u7528\u6765\u63CF\u8FF0\u6587\u5B57\u6307\u4EE4,\u4F8B\u5982"\u63CF\u8FF0\u56FE\u7247\u5185\u5BB9"`
@@ -41730,7 +41911,8 @@ var imageUnderstand = {
41730
41911
  required: [`prompt`]
41731
41912
  },
41732
41913
  async execute(input, userRequest) {
41733
- const imageSource = userRequest?.metadata?.imageUrl || input.image_url;
41914
+ const attachmentId = typeof input.attachment_id === "string" ? input.attachment_id.trim() : "";
41915
+ const imageSource = userRequest?.metadata?.imageUrl || (attachmentId && userRequest?.userId ? resolveAttachmentPathOrUrl(userRequest.userId, attachmentId) : void 0) || input.image_url;
41734
41916
  if (!imageSource) {
41735
41917
  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
41918
  }
@@ -41783,7 +41965,7 @@ var imageUnderstand = {
41783
41965
 
41784
41966
  // src/tools/tools/ImageGenerate.ts
41785
41967
  var import_promises12 = require("node:fs/promises");
41786
- var import_node_path12 = __toESM(require("node:path"));
41968
+ var import_node_path13 = __toESM(require("node:path"));
41787
41969
  var DEFAULT_BASE_URL = "https://direct.shanyiapi.com";
41788
41970
  var DEFAULT_MODEL = "gpt-image-2";
41789
41971
  var RETRYABLE_STATUS_CODES = /* @__PURE__ */ new Set([403, 429, 500, 502, 503, 504, 524]);
@@ -41810,7 +41992,7 @@ function extensionFromContentType(contentType) {
41810
41992
  }
41811
41993
  function extensionFromUrl(url) {
41812
41994
  try {
41813
- const ext = import_node_path12.default.extname(new URL(url).pathname).toLowerCase();
41995
+ const ext = import_node_path13.default.extname(new URL(url).pathname).toLowerCase();
41814
41996
  if ([".png", ".jpg", ".jpeg", ".webp", ".gif"].includes(ext)) return ext;
41815
41997
  } catch {
41816
41998
  return "";
@@ -41943,7 +42125,7 @@ var imageGenerate = {
41943
42125
  requestId: generated.requestId
41944
42126
  }, null, 2);
41945
42127
  }
41946
- const outDir = import_node_path12.default.join(getEffectiveCwd(userRequest), "imagegen");
42128
+ const outDir = import_node_path13.default.join(getEffectiveCwd(userRequest), "imagegen");
41947
42129
  await (0, import_promises12.mkdir)(outDir, { recursive: true });
41948
42130
  const stem = sanitizeName(String(input.filenameStem || prompt));
41949
42131
  const savedFiles = [];
@@ -41952,7 +42134,7 @@ var imageGenerate = {
41952
42134
  const image = item.url ? await downloadImage(item.url, timeoutMs) : item.b64_json ? decodeBase64Image(item.b64_json) : null;
41953
42135
  if (!image) continue;
41954
42136
  const fileName = `${stem}-${index + 1}${image.extension}`;
41955
- const filePath = import_node_path12.default.join(outDir, fileName);
42137
+ const filePath = import_node_path13.default.join(outDir, fileName);
41956
42138
  await (0, import_promises12.writeFile)(filePath, image.buffer);
41957
42139
  savedFiles.push({ filePath, fileName });
41958
42140
  }
@@ -42397,10 +42579,10 @@ var goalDelete = {
42397
42579
  };
42398
42580
 
42399
42581
  // src/tools/tools/department/DepartmentCreate.ts
42400
- var import_node_crypto8 = require("node:crypto");
42582
+ var import_node_crypto9 = require("node:crypto");
42401
42583
 
42402
42584
  // src/department/mailbox/mailbox.ts
42403
- var import_node_crypto7 = require("node:crypto");
42585
+ var import_node_crypto8 = require("node:crypto");
42404
42586
 
42405
42587
  // src/agent/interruptRegistry.ts
42406
42588
  var registry = /* @__PURE__ */ new Map();
@@ -42451,7 +42633,7 @@ var drainInterrupts = (userId) => {
42451
42633
  };
42452
42634
 
42453
42635
  // src/agent/events.ts
42454
- var import_node_crypto4 = require("node:crypto");
42636
+ var import_node_crypto5 = require("node:crypto");
42455
42637
  var rowToEvent = (row) => ({
42456
42638
  id: row.id,
42457
42639
  userId: row.userId,
@@ -42468,7 +42650,7 @@ var rowToEvent = (row) => ({
42468
42650
  var recordAgentEvent = (input) => {
42469
42651
  const db3 = createSqliteDB();
42470
42652
  const now = Date.now();
42471
- const id = `evt_${(0, import_node_crypto4.randomUUID)().slice(0, 12)}`;
42653
+ const id = `evt_${(0, import_node_crypto5.randomUUID)().slice(0, 12)}`;
42472
42654
  const payloadJson = JSON.stringify(input.payload);
42473
42655
  db3.prepare(`
42474
42656
  INSERT INTO agent_events (
@@ -42599,7 +42781,7 @@ ${ceoFollowupInstruction}
42599
42781
  };
42600
42782
 
42601
42783
  // src/department/mailbox/events.ts
42602
- var import_node_crypto5 = require("node:crypto");
42784
+ var import_node_crypto6 = require("node:crypto");
42603
42785
  var parseDetail = (detailJson) => {
42604
42786
  if (!detailJson) return void 0;
42605
42787
  try {
@@ -42640,7 +42822,7 @@ var mapMailboxEventRow = (row) => {
42640
42822
  var recordMailboxEvent = (input) => {
42641
42823
  const db3 = createSqliteDB();
42642
42824
  const event = {
42643
- id: (0, import_node_crypto5.randomUUID)().slice(0, 12),
42825
+ id: (0, import_node_crypto6.randomUUID)().slice(0, 12),
42644
42826
  messageId: input.messageId,
42645
42827
  mailboxId: input.mailboxId,
42646
42828
  actorMailboxId: input.actorMailboxId,
@@ -42924,7 +43106,7 @@ var deleteDepartmentMemberById = (departmentName, memberId) => {
42924
43106
  };
42925
43107
 
42926
43108
  // src/department/mailbox/ceoFollowup.ts
42927
- var import_node_crypto6 = require("node:crypto");
43109
+ var import_node_crypto7 = require("node:crypto");
42928
43110
  var rowToFollowup = (row) => ({
42929
43111
  id: row.id,
42930
43112
  sourceMessageId: row.sourceMessageId,
@@ -42971,7 +43153,7 @@ var enqueueCeoFollowupFromMailbox = (message) => {
42971
43153
  if (!message.originUserId || !message.originPlatform) return null;
42972
43154
  const db3 = createSqliteDB();
42973
43155
  const now = Date.now();
42974
- const id = `cfu_${(0, import_node_crypto6.randomUUID)().slice(0, 12)}`;
43156
+ const id = `cfu_${(0, import_node_crypto7.randomUUID)().slice(0, 12)}`;
42975
43157
  db3.prepare(`
42976
43158
  INSERT INTO ceo_followups (
42977
43159
  id,
@@ -43301,7 +43483,7 @@ var recordMailboxReceivedAgentEvent = (msg) => {
43301
43483
  };
43302
43484
  var sendMessage2 = (fromMailboxId, toMailboxId, content, options) => {
43303
43485
  const db3 = createSqliteDB();
43304
- const id = (0, import_node_crypto7.randomUUID)().slice(0, 8);
43486
+ const id = (0, import_node_crypto8.randomUUID)().slice(0, 8);
43305
43487
  const threadId = options?.threadId || id;
43306
43488
  const workItemContext = resolveWorkItemContext(fromMailboxId, toMailboxId, id, options);
43307
43489
  const stmt = db3.prepare(`insert into mailbox (
@@ -43444,7 +43626,7 @@ var departmentCreate = {
43444
43626
  return `[departmentCreate] \u4E0D\u5B58\u5728 id=${sourceGoalId} \u7684\u76EE\u6807`;
43445
43627
  }
43446
43628
  let departmentDefinition = {
43447
- id: (0, import_node_crypto8.randomUUID)().slice(0, 8),
43629
+ id: (0, import_node_crypto9.randomUUID)().slice(0, 8),
43448
43630
  name,
43449
43631
  charter,
43450
43632
  sourceGoalId,
@@ -43711,7 +43893,7 @@ var departmentList = {
43711
43893
  };
43712
43894
 
43713
43895
  // src/tools/tools/department/DepartmentMemberCreate.ts
43714
- var import_node_crypto9 = require("node:crypto");
43896
+ var import_node_crypto10 = require("node:crypto");
43715
43897
  var DESCRIPTION25 = `
43716
43898
  \u521B\u5EFA\u90E8\u95E8\u6210\u5458\u3002
43717
43899
 
@@ -43777,7 +43959,7 @@ var departmentMemberCreate = {
43777
43959
  }
43778
43960
  }
43779
43961
  let departmentMember = {
43780
- id: (0, import_node_crypto9.randomUUID)().slice(0, 8),
43962
+ id: (0, import_node_crypto10.randomUUID)().slice(0, 8),
43781
43963
  name,
43782
43964
  departmentId: department.id,
43783
43965
  mailBoxId: getMailBoxId(department.name, name),
@@ -43967,13 +44149,13 @@ ${replies}`;
43967
44149
  };
43968
44150
 
43969
44151
  // 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");
44152
+ var import_node_fs5 = require("node:fs");
44153
+ var import_node_path15 = __toESM(require("node:path"));
44154
+ var import_node_crypto11 = require("node:crypto");
43973
44155
 
43974
44156
  // src/skill/SkillValidator.ts
43975
- var import_node_fs3 = require("node:fs");
43976
- var import_node_path13 = __toESM(require("node:path"));
44157
+ var import_node_fs4 = require("node:fs");
44158
+ var import_node_path14 = __toESM(require("node:path"));
43977
44159
  var SKILL_NAME_PATTERN = /^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$/;
43978
44160
  var REQUIRED_SECTIONS = [
43979
44161
  /(^|\n)##\s+(when to use|何时使用|trigger|triggers)\b/i,
@@ -43999,9 +44181,9 @@ var assertSafeSkillTarget = (rootDir, skillName) => {
43999
44181
  if (!isValidSkillName(skillName)) {
44000
44182
  throw new Error(`Invalid skill name "${skillName}". Use lowercase letters, digits, and single hyphens only.`);
44001
44183
  }
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)) {
44184
+ const root = import_node_path14.default.resolve(rootDir);
44185
+ const target = import_node_path14.default.resolve(root, skillName);
44186
+ if (target !== import_node_path14.default.join(root, skillName) || !target.startsWith(root + import_node_path14.default.sep)) {
44005
44187
  throw new Error(`Invalid skill install target for "${skillName}".`);
44006
44188
  }
44007
44189
  return target;
@@ -44074,14 +44256,14 @@ var validateSkillDocument = (input) => {
44074
44256
  }
44075
44257
  }
44076
44258
  if (input.baseDir) {
44077
- const normalizedBase = import_node_path13.default.resolve(input.baseDir);
44259
+ const normalizedBase = import_node_path14.default.resolve(input.baseDir);
44078
44260
  for (const match2 of parsed.body.matchAll(/\]\(([^)]+)\)/g)) {
44079
44261
  const href = match2[1].trim();
44080
44262
  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) {
44263
+ const target = import_node_path14.default.resolve(normalizedBase, href.split(`#`)[0]);
44264
+ if (!target.startsWith(normalizedBase + import_node_path14.default.sep) && target !== normalizedBase) {
44083
44265
  addError(errors, `unsafe_reference`, `Relative link "${href}" escapes the skill directory.`);
44084
- } else if (!(0, import_node_fs3.existsSync)(target)) {
44266
+ } else if (!(0, import_node_fs4.existsSync)(target)) {
44085
44267
  addError(errors, `missing_reference`, `Relative link "${href}" does not exist in the skill directory.`);
44086
44268
  }
44087
44269
  }
@@ -44098,9 +44280,9 @@ var mergeIssues = (target, source) => {
44098
44280
  target.warnings.push(...source.warnings);
44099
44281
  };
44100
44282
  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());
44283
+ const scriptsDir = import_node_path14.default.join(skillDir, `scripts`);
44284
+ if (!(0, import_node_fs4.existsSync)(scriptsDir)) return;
44285
+ const entries = (0, import_node_fs4.readdirSync)(scriptsDir, { withFileTypes: true }).filter((entry) => entry.isFile());
44104
44286
  if (entries.length === 0) {
44105
44287
  addWarning(warnings, `empty_scripts_dir`, `scripts/ exists but contains no files.`);
44106
44288
  }
@@ -44108,19 +44290,19 @@ var validateScriptsDirectory = (skillDir, warnings) => {
44108
44290
  var validateSkillDirectory = (skillDir, options = {}) => {
44109
44291
  const errors = [];
44110
44292
  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()) {
44293
+ const normalizedSkillDir = import_node_path14.default.resolve(skillDir);
44294
+ const skillMdPath = import_node_path14.default.join(normalizedSkillDir, `SKILL.md`);
44295
+ if (!(0, import_node_fs4.existsSync)(normalizedSkillDir) || !(0, import_node_fs4.statSync)(normalizedSkillDir).isDirectory()) {
44114
44296
  addError(errors, `missing_skill_dir`, `Skill directory does not exist: ${normalizedSkillDir}`);
44115
44297
  return { ok: false, errors, warnings, skillDir: normalizedSkillDir, skillMdPath };
44116
44298
  }
44117
- if (!(0, import_node_fs3.existsSync)(skillMdPath) || !(0, import_node_fs3.statSync)(skillMdPath).isFile()) {
44299
+ if (!(0, import_node_fs4.existsSync)(skillMdPath) || !(0, import_node_fs4.statSync)(skillMdPath).isFile()) {
44118
44300
  addError(errors, `missing_skill_md`, `Skill directory must contain SKILL.md.`);
44119
44301
  return { ok: false, errors, warnings, skillDir: normalizedSkillDir, skillMdPath };
44120
44302
  }
44121
- const raw2 = (0, import_node_fs3.readFileSync)(skillMdPath, `utf-8`);
44303
+ const raw2 = (0, import_node_fs4.readFileSync)(skillMdPath, `utf-8`);
44122
44304
  const documentResult = validateSkillDocument({
44123
- skillName: options.expectedName ?? import_node_path13.default.basename(normalizedSkillDir),
44305
+ skillName: options.expectedName ?? import_node_path14.default.basename(normalizedSkillDir),
44124
44306
  skillMd: raw2,
44125
44307
  baseDir: normalizedSkillDir
44126
44308
  });
@@ -44172,26 +44354,26 @@ var formatSkillValidationIssues = (result) => {
44172
44354
  };
44173
44355
 
44174
44356
  // src/department/learning.ts
44175
- var ensureDir = (dir) => (0, import_node_fs4.mkdirSync)(dir, { recursive: true });
44357
+ var ensureDir = (dir) => (0, import_node_fs5.mkdirSync)(dir, { recursive: true });
44176
44358
  var readJsonArray = (filePath) => {
44177
- if (!(0, import_node_fs4.existsSync)(filePath)) return [];
44178
- return JSON.parse((0, import_node_fs4.readFileSync)(filePath, "utf-8"));
44359
+ if (!(0, import_node_fs5.existsSync)(filePath)) return [];
44360
+ return JSON.parse((0, import_node_fs5.readFileSync)(filePath, "utf-8"));
44179
44361
  };
44180
44362
  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");
44363
+ ensureDir(import_node_path15.default.dirname(filePath));
44364
+ (0, import_node_fs5.writeFileSync)(filePath, JSON.stringify(records, null, " "), "utf-8");
44183
44365
  };
44184
44366
  var departmentMemoryPath = (departmentName) => {
44185
- return import_node_path14.default.join(getDepartmentWorkSpaceDir(departmentName), "department-memory.json");
44367
+ return import_node_path15.default.join(getDepartmentWorkSpaceDir(departmentName), "department-memory.json");
44186
44368
  };
44187
44369
  var departmentSkillPath = (departmentName) => {
44188
- return import_node_path14.default.join(getDepartmentWorkSpaceDir(departmentName), "department-skills.json");
44370
+ return import_node_path15.default.join(getDepartmentWorkSpaceDir(departmentName), "department-skills.json");
44189
44371
  };
44190
44372
  var departmentSkillDir = (departmentName, skillName) => {
44191
- return assertSafeSkillTarget(import_node_path14.default.join(getDepartmentWorkSpaceDir(departmentName), "skills"), skillName);
44373
+ return assertSafeSkillTarget(import_node_path15.default.join(getDepartmentWorkSpaceDir(departmentName), "skills"), skillName);
44192
44374
  };
44193
44375
  var proposalsPath = () => {
44194
- return import_node_path14.default.join(getDepartmentBaseDir(), "department-proposals.json");
44376
+ return import_node_path15.default.join(getDepartmentBaseDir(), "department-proposals.json");
44195
44377
  };
44196
44378
  var getDepartmentNameForHead = (request) => {
44197
44379
  const mailboxId = request?.departmentAgentId;
@@ -44207,7 +44389,7 @@ var listDepartmentMemories = (departmentName) => {
44207
44389
  var createDepartmentMemory = (departmentName, input) => {
44208
44390
  const now = Date.now();
44209
44391
  const memory = {
44210
- id: (0, import_node_crypto10.randomUUID)().slice(0, 8),
44392
+ id: (0, import_node_crypto11.randomUUID)().slice(0, 8),
44211
44393
  departmentName,
44212
44394
  title: input.title,
44213
44395
  content: input.content,
@@ -44258,7 +44440,7 @@ ${formatSkillValidationIssues(validation)}`);
44258
44440
  }
44259
44441
  const now = Date.now();
44260
44442
  const skill = {
44261
- id: (0, import_node_crypto10.randomUUID)().slice(0, 8),
44443
+ id: (0, import_node_crypto11.randomUUID)().slice(0, 8),
44262
44444
  departmentName,
44263
44445
  skillName: input.skillName,
44264
44446
  description: input.description,
@@ -44289,7 +44471,7 @@ var keepDepartmentSkill = (departmentName, id) => {
44289
44471
  ${formatSkillValidationIssues(validation)}`);
44290
44472
  }
44291
44473
  ensureDir(skillDir);
44292
- (0, import_node_fs4.writeFileSync)(import_node_path14.default.join(skillDir, "SKILL.md"), records[idx].skillMd, "utf-8");
44474
+ (0, import_node_fs5.writeFileSync)(import_node_path15.default.join(skillDir, "SKILL.md"), records[idx].skillMd, "utf-8");
44293
44475
  const smokeTest = smokeTestSkillDirectory(skillDir, { expectedName: records[idx].skillName });
44294
44476
  if (!smokeTest.ok) {
44295
44477
  throw new Error(`[departmentSkill] Skill smoke test \u5931\u8D25\uFF1A
@@ -44310,7 +44492,7 @@ var createDepartmentProposal = (input) => {
44310
44492
  const records = readJsonArray(proposalsPath());
44311
44493
  const proposal = {
44312
44494
  ...input,
44313
- id: (0, import_node_crypto10.randomUUID)().slice(0, 8),
44495
+ id: (0, import_node_crypto11.randomUUID)().slice(0, 8),
44314
44496
  status: "pending",
44315
44497
  createdAt: Date.now()
44316
44498
  };
@@ -44669,8 +44851,8 @@ var mailboxFollowup = {
44669
44851
 
44670
44852
  // src/tools/tools/Bash.ts
44671
44853
  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");
44854
+ var import_node_crypto12 = require("node:crypto");
44855
+ var import_node_fs6 = require("node:fs");
44674
44856
  var DESCRIPTION30 = `\u5728\u7CFB\u7EDF shell \u4E2D\u6267\u884C\u547D\u4EE4\u3002
44675
44857
 
44676
44858
  \u7528\u9014\uFF1A
@@ -44710,7 +44892,7 @@ var sessions = /* @__PURE__ */ new Map();
44710
44892
  function findExecutableShell2() {
44711
44893
  for (const shell of SHELL_CANDIDATES2) {
44712
44894
  try {
44713
- (0, import_node_fs5.accessSync)(shell, import_node_fs5.constants.X_OK);
44895
+ (0, import_node_fs6.accessSync)(shell, import_node_fs6.constants.X_OK);
44714
44896
  return shell;
44715
44897
  } catch {
44716
44898
  }
@@ -44719,11 +44901,11 @@ function findExecutableShell2() {
44719
44901
  }
44720
44902
  function validateCwd(cwd) {
44721
44903
  try {
44722
- const stat11 = (0, import_node_fs5.statSync)(cwd);
44904
+ const stat11 = (0, import_node_fs6.statSync)(cwd);
44723
44905
  if (!stat11.isDirectory()) {
44724
44906
  return `[bash] \u9519\u8BEF: cwd \u4E0D\u662F\u76EE\u5F55: ${cwd}`;
44725
44907
  }
44726
- (0, import_node_fs5.accessSync)(cwd, import_node_fs5.constants.R_OK | import_node_fs5.constants.X_OK);
44908
+ (0, import_node_fs6.accessSync)(cwd, import_node_fs6.constants.R_OK | import_node_fs6.constants.X_OK);
44727
44909
  return null;
44728
44910
  } catch (err) {
44729
44911
  const code = err.code;
@@ -44966,7 +45148,7 @@ var bashTool = {
44966
45148
  stdio: ["pipe", "pipe", "pipe"],
44967
45149
  detached: process.platform !== "win32"
44968
45150
  });
44969
- const id = (0, import_node_crypto11.randomUUID)().slice(0, 8);
45151
+ const id = (0, import_node_crypto12.randomUUID)().slice(0, 8);
44970
45152
  const session = {
44971
45153
  id,
44972
45154
  command,
@@ -45733,10 +45915,10 @@ var readDreamHistoryLimit = () => {
45733
45915
  };
45734
45916
 
45735
45917
  // src/skillForge/SkillForgeEngine.ts
45736
- var import_node_fs6 = require("node:fs");
45918
+ var import_node_fs7 = require("node:fs");
45737
45919
  var import_node_os2 = require("node:os");
45738
- var import_node_path15 = require("node:path");
45739
- var import_node_crypto12 = require("node:crypto");
45920
+ var import_node_path16 = require("node:path");
45921
+ var import_node_crypto13 = require("node:crypto");
45740
45922
  var SkillForgeEngine = class {
45741
45923
  proposalStorage;
45742
45924
  draftRoot;
@@ -45744,8 +45926,8 @@ var SkillForgeEngine = class {
45744
45926
  constructor(deps) {
45745
45927
  this.proposalStorage = deps.proposalStorage;
45746
45928
  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");
45929
+ this.draftRoot = opt.draftRoot ?? (0, import_node_path16.join)((0, import_node_os2.homedir)(), ".duclaw", "skill-proposals");
45930
+ this.skillsInstallDir = opt.skillsInstallDir ?? (0, import_node_path16.join)((0, import_node_os2.homedir)(), ".agents", "skills");
45749
45931
  }
45750
45932
  // ---------- 公开方法 ----------
45751
45933
  /**
@@ -45770,14 +45952,14 @@ ${formatSkillValidationIssues(validation)}`);
45770
45952
  if (pending.some((p) => p.skillName === skillName)) {
45771
45953
  return null;
45772
45954
  }
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");
45955
+ const id = (0, import_node_crypto13.randomBytes)(4).toString("hex");
45956
+ const draftDir = (0, import_node_path16.join)(this.draftRoot, userId, id);
45957
+ (0, import_node_fs7.mkdirSync)(draftDir, { recursive: true });
45958
+ (0, import_node_fs7.writeFileSync)((0, import_node_path16.join)(draftDir, "SKILL.md"), skillMd, "utf-8");
45777
45959
  const directoryValidation = validateSkillDirectory(draftDir, { expectedName: skillName });
45778
45960
  if (!directoryValidation.ok) {
45779
45961
  try {
45780
- (0, import_node_fs6.rmSync)(draftDir, { recursive: true, force: true });
45962
+ (0, import_node_fs7.rmSync)(draftDir, { recursive: true, force: true });
45781
45963
  } catch {
45782
45964
  }
45783
45965
  throw new Error(`[skillForge] Skill \u76EE\u5F55\u6821\u9A8C\u5931\u8D25\uFF1A
@@ -45812,26 +45994,26 @@ ${formatSkillValidationIssues(validation)}`);
45812
45994
  }
45813
45995
  const target = assertSafeSkillTarget(this.skillsInstallDir, proposal.skillName);
45814
45996
  let installedTarget = target;
45815
- if ((0, import_node_fs6.existsSync)(target)) {
45997
+ if ((0, import_node_fs7.existsSync)(target)) {
45816
45998
  const alt = target + "-" + proposalId;
45817
- (0, import_node_fs6.mkdirSync)(alt, { recursive: true });
45818
- (0, import_node_fs6.cpSync)(proposal.draftDir, alt, { recursive: true });
45999
+ (0, import_node_fs7.mkdirSync)(alt, { recursive: true });
46000
+ (0, import_node_fs7.cpSync)(proposal.draftDir, alt, { recursive: true });
45819
46001
  installedTarget = alt;
45820
46002
  } else {
45821
- (0, import_node_fs6.mkdirSync)(target, { recursive: true });
45822
- (0, import_node_fs6.cpSync)(proposal.draftDir, target, { recursive: true });
46003
+ (0, import_node_fs7.mkdirSync)(target, { recursive: true });
46004
+ (0, import_node_fs7.cpSync)(proposal.draftDir, target, { recursive: true });
45823
46005
  }
45824
46006
  const smokeTest = smokeTestSkillDirectory(installedTarget, { expectedName: proposal.skillName });
45825
46007
  if (!smokeTest.ok) {
45826
46008
  try {
45827
- (0, import_node_fs6.rmSync)(installedTarget, { recursive: true, force: true });
46009
+ (0, import_node_fs7.rmSync)(installedTarget, { recursive: true, force: true });
45828
46010
  } catch {
45829
46011
  }
45830
46012
  throw new Error(`[skillForge] Skill smoke test \u5931\u8D25\uFF0C\u5DF2\u56DE\u6EDA\u843D\u5730\u76EE\u5F55\uFF1A
45831
46013
  ${formatSkillValidationIssues(smokeTest)}`);
45832
46014
  }
45833
46015
  try {
45834
- (0, import_node_fs6.rmSync)(proposal.draftDir, { recursive: true, force: true });
46016
+ (0, import_node_fs7.rmSync)(proposal.draftDir, { recursive: true, force: true });
45835
46017
  } catch {
45836
46018
  }
45837
46019
  await this.removeProposal(userId, proposalId);
@@ -45843,7 +46025,7 @@ ${formatSkillValidationIssues(smokeTest)}`);
45843
46025
  const proposal = list.find((p) => p.id === proposalId);
45844
46026
  if (!proposal) return null;
45845
46027
  try {
45846
- (0, import_node_fs6.rmSync)(proposal.draftDir, { recursive: true, force: true });
46028
+ (0, import_node_fs7.rmSync)(proposal.draftDir, { recursive: true, force: true });
45847
46029
  } catch {
45848
46030
  }
45849
46031
  await this.removeProposal(userId, proposalId);
@@ -46052,7 +46234,7 @@ var skillForgeDrop = (engine) => ({
46052
46234
  });
46053
46235
 
46054
46236
  // src/memory/MemoryEngine.ts
46055
- var import_node_crypto13 = require("node:crypto");
46237
+ var import_node_crypto14 = require("node:crypto");
46056
46238
  var MemoryEngine = class {
46057
46239
  storage;
46058
46240
  recallIndexStorage;
@@ -46080,7 +46262,7 @@ var MemoryEngine = class {
46080
46262
  }
46081
46263
  const now = Date.now();
46082
46264
  const memory = {
46083
- id: (0, import_node_crypto13.randomBytes)(4).toString("hex"),
46265
+ id: (0, import_node_crypto14.randomBytes)(4).toString("hex"),
46084
46266
  userId,
46085
46267
  title,
46086
46268
  content,
@@ -46732,13 +46914,13 @@ var COMPANY_VALUES_PROMPT = `<\u516C\u53F8\u5171\u540C\u4FE1\u5FF5>
46732
46914
  </\u516C\u53F8\u5171\u540C\u4FE1\u5FF5>`;
46733
46915
 
46734
46916
  // src/agent/outboundDedup.ts
46735
- var import_node_crypto14 = require("node:crypto");
46917
+ var import_node_crypto15 = require("node:crypto");
46736
46918
  var DEFAULT_WINDOW_MS = 15e3;
46737
46919
  var recentSends = /* @__PURE__ */ new Map();
46738
46920
  var lastSweepAt = 0;
46739
46921
  var normalize3 = (text2) => text2.replace(/\s+/g, " ").trim();
46740
46922
  var keyFor = (userId, normalized) => {
46741
- const hash = (0, import_node_crypto14.createHash)("sha1").update(normalized).digest("hex");
46923
+ const hash = (0, import_node_crypto15.createHash)("sha1").update(normalized).digest("hex");
46742
46924
  return `${userId}::${hash}`;
46743
46925
  };
46744
46926
  var sweep = (now, windowMs) => {
@@ -46786,12 +46968,12 @@ var isAbortError2 = (error) => {
46786
46968
  return error.name === "AbortError" || error.message.includes("aborted") || error.message.includes("AbortError");
46787
46969
  };
46788
46970
  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);
46971
+ 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
46972
  return `dreq_${hash}`;
46791
46973
  };
46792
46974
  var getDefaultAgentConfig = (tools, systemPrompt) => {
46793
46975
  loadEnv();
46794
- (0, import_node_fs7.mkdirSync)(DEFAULT_WORKSPACE_PATH, { recursive: true });
46976
+ (0, import_node_fs8.mkdirSync)(DEFAULT_WORKSPACE_PATH, { recursive: true });
46795
46977
  let system = ``;
46796
46978
  if (!systemPrompt) {
46797
46979
  system = `
@@ -47024,7 +47206,8 @@ var createAgent = (config2 = getDefaultAgentConfig()) => {
47024
47206
  if (config2.workspacePath && !request.defaultWorkDir) {
47025
47207
  request.defaultWorkDir = config2.workspacePath;
47026
47208
  }
47027
- const { userId, content, job } = request;
47209
+ const { userId, job } = request;
47210
+ const content = attachRecentAttachmentContext(request, request.content);
47028
47211
  const internalOnly = request.metadata?.internalOnly === true;
47029
47212
  const injectedEventIds = /* @__PURE__ */ new Set();
47030
47213
  const interruptQueuedEventIds = /* @__PURE__ */ new Set();
@@ -51866,7 +52049,7 @@ var cors = (options) => {
51866
52049
 
51867
52050
  // src/server/index.ts
51868
52051
  var import_promises14 = require("node:fs/promises");
51869
- var import_node_path19 = __toESM(require("node:path"));
52052
+ var import_node_path20 = __toESM(require("node:path"));
51870
52053
 
51871
52054
  // src/git/worktree.ts
51872
52055
  var import_child_process2 = require("child_process");
@@ -53211,8 +53394,8 @@ mailboxRoutes.get("/mailbox/summary", (c) => {
53211
53394
 
53212
53395
  // src/server/routes/memory.ts
53213
53396
  var import_redis4 = __toESM(require_dist2());
53214
- var import_node_fs8 = require("node:fs");
53215
- var import_node_path16 = __toESM(require("node:path"));
53397
+ var import_node_fs9 = require("node:fs");
53398
+ var import_node_path17 = __toESM(require("node:path"));
53216
53399
  var memoryEngineSingleton = null;
53217
53400
  var dreamStorageSingleton = null;
53218
53401
  var dreamHistoryStorageSingleton = null;
@@ -53312,12 +53495,12 @@ var addPlausibleUserId = (set, userId) => {
53312
53495
  if (normalized && isPlausibleUserId(normalized)) set.add(normalized);
53313
53496
  };
53314
53497
  var readJsonFilesFromDir = (dir) => {
53315
- if (!(0, import_node_fs8.existsSync)(dir)) return [];
53498
+ if (!(0, import_node_fs9.existsSync)(dir)) return [];
53316
53499
  const result = [];
53317
- for (const file of (0, import_node_fs8.readdirSync)(dir)) {
53500
+ for (const file of (0, import_node_fs9.readdirSync)(dir)) {
53318
53501
  if (!file.endsWith(".json")) continue;
53319
53502
  try {
53320
- result.push(JSON.parse((0, import_node_fs8.readFileSync)(import_node_path16.default.join(dir, file), "utf-8")));
53503
+ result.push(JSON.parse((0, import_node_fs9.readFileSync)(import_node_path17.default.join(dir, file), "utf-8")));
53321
53504
  } catch (err) {
53322
53505
  console.warn(`[memoryRoutes] \u8DF3\u8FC7\u65E0\u6CD5\u89E3\u6790\u7684\u672C\u5730\u4E0A\u4E0B\u6587\u6587\u4EF6 ${file}: ${err.message}`);
53323
53506
  }
@@ -53325,13 +53508,13 @@ var readJsonFilesFromDir = (dir) => {
53325
53508
  return result;
53326
53509
  };
53327
53510
  var extractFileBackedStorageKeysForTest = (dataDir) => {
53328
- const kvRoot = import_node_path16.default.join(dataDir, "kv");
53329
- if (!(0, import_node_fs8.existsSync)(kvRoot)) return [];
53511
+ const kvRoot = import_node_path17.default.join(dataDir, "kv");
53512
+ if (!(0, import_node_fs9.existsSync)(kvRoot)) return [];
53330
53513
  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)) {
53514
+ for (const prefixDir of (0, import_node_fs9.readdirSync)(kvRoot)) {
53515
+ const absolutePrefixDir = import_node_path17.default.join(kvRoot, prefixDir);
53516
+ if (!(0, import_node_fs9.existsSync)(absolutePrefixDir)) continue;
53517
+ for (const file of (0, import_node_fs9.readdirSync)(absolutePrefixDir)) {
53335
53518
  if (!file.endsWith(".json")) continue;
53336
53519
  try {
53337
53520
  const logicalKey = Buffer.from(file.replace(/\.json$/, ""), "base64url").toString("utf8");
@@ -53349,12 +53532,12 @@ var collectLocalConversationUserIds = () => {
53349
53532
  };
53350
53533
  var extractLocalConversationUserIdsForTest = (homeDir) => {
53351
53534
  const userIds = /* @__PURE__ */ new Set();
53352
- for (const context of readJsonFilesFromDir(import_node_path16.default.join(homeDir, "goal-context"))) {
53535
+ for (const context of readJsonFilesFromDir(import_node_path17.default.join(homeDir, "goal-context"))) {
53353
53536
  addPlausibleUserId(userIds, context.threadId);
53354
53537
  addPlausibleUserId(userIds, context.originUserId);
53355
53538
  if (context.goalId) addPlausibleUserId(userIds, `kanban:goal:${context.goalId}`);
53356
53539
  }
53357
- for (const goal of readJsonFilesFromDir(import_node_path16.default.join(homeDir, "tasks"))) {
53540
+ for (const goal of readJsonFilesFromDir(import_node_path17.default.join(homeDir, "tasks"))) {
53358
53541
  if (goal.id) addPlausibleUserId(userIds, `kanban:goal:${goal.id}`);
53359
53542
  }
53360
53543
  return userIds;
@@ -53771,7 +53954,7 @@ ${item.dreamContent}` : item.dreamContent).join("\n\n");
53771
53954
  });
53772
53955
 
53773
53956
  // src/server/routes/tools.ts
53774
- var import_node_path17 = __toESM(require("node:path"));
53957
+ var import_node_path18 = __toESM(require("node:path"));
53775
53958
  var toolRoutes = new Hono2();
53776
53959
  var listActiveDepartmentSkills = () => {
53777
53960
  return listDepartments().flatMap(
@@ -53782,7 +53965,7 @@ var listActiveDepartmentSkills = () => {
53782
53965
  scope: "department",
53783
53966
  departmentName: department.name,
53784
53967
  detail: () => {
53785
- const skillDir = import_node_path17.default.join(getDepartmentWorkSpaceDir(department.name), "skills", skill.skillName);
53968
+ const skillDir = import_node_path18.default.join(getDepartmentWorkSpaceDir(department.name), "skills", skill.skillName);
53786
53969
  return `Base directory for this skill: ${skillDir}
53787
53970
 
53788
53971
  ${skill.skillMd}`;
@@ -53871,7 +54054,7 @@ var systemRoutes = new Hono2();
53871
54054
  var startTime = Date.now();
53872
54055
  systemRoutes.get("/system/info", (c) => {
53873
54056
  return c.json({
53874
- version: true ? "1.9.13" : "unknown",
54057
+ version: true ? "1.9.14" : "unknown",
53875
54058
  uptime: Math.floor((Date.now() - startTime) / 1e3),
53876
54059
  env: process.env.NODE_ENV || "development",
53877
54060
  nodeVersion: process.version
@@ -53879,9 +54062,9 @@ systemRoutes.get("/system/info", (c) => {
53879
54062
  });
53880
54063
 
53881
54064
  // src/server/routes/mobile.ts
53882
- var import_node_crypto16 = require("node:crypto");
54065
+ var import_node_crypto17 = require("node:crypto");
53883
54066
  var import_promises13 = require("node:fs/promises");
53884
- var import_node_path18 = __toESM(require("node:path"));
54067
+ var import_node_path19 = __toESM(require("node:path"));
53885
54068
  var mobileRoutes = new Hono2();
53886
54069
  var resolveMobileUserId = (body, headerUserId) => {
53887
54070
  return body.userId?.trim() || headerUserId?.trim() || "ios:local-user";
@@ -53923,12 +54106,12 @@ ${goal.tasks.map((task) => `- [${task.status}] ${task.subject}`).join("\n")}` :
53923
54106
  return lines.join("\n");
53924
54107
  };
53925
54108
  var sanitizeFileName = (fileName) => {
53926
- const cleaned = import_node_path18.default.basename(fileName).replace(/[^\w.\-()\u4e00-\u9fa5 ]+/g, "_").trim();
54109
+ const cleaned = import_node_path19.default.basename(fileName).replace(/[^\w.\-()\u4e00-\u9fa5 ]+/g, "_").trim();
53927
54110
  return cleaned || `attachment-${Date.now()}`;
53928
54111
  };
53929
54112
  var inferAttachmentType2 = (mimeType = "", fileName = "") => {
53930
54113
  if (mimeType.startsWith("image/")) return "image";
53931
- const ext = import_node_path18.default.extname(fileName).toLowerCase();
54114
+ const ext = import_node_path19.default.extname(fileName).toLowerCase();
53932
54115
  if ([".png", ".jpg", ".jpeg", ".gif", ".webp"].includes(ext)) return "image";
53933
54116
  return "file";
53934
54117
  };
@@ -53983,11 +54166,11 @@ mobileRoutes.post("/mobile/attachments", async (c) => {
53983
54166
  const fileName = sanitizeFileName(body.fileName || `attachment-${Date.now()}`);
53984
54167
  const mimeType = body.mimeType || "application/octet-stream";
53985
54168
  const type = inferAttachmentType2(mimeType, fileName);
53986
- const attachmentId = (0, import_node_crypto16.randomUUID)();
54169
+ const attachmentId = (0, import_node_crypto17.randomUUID)();
53987
54170
  const buffer = Buffer.from(dataBase64, "base64");
53988
- const dir = import_node_path18.default.join(getDuclawWorkspaceDir(), mobileUserId, "mobile", type === "image" ? "images" : "files");
54171
+ const dir = import_node_path19.default.join(getDuclawWorkspaceDir(), mobileUserId, "mobile", type === "image" ? "images" : "files");
53989
54172
  await (0, import_promises13.mkdir)(dir, { recursive: true });
53990
- const localPath = import_node_path18.default.join(dir, `${attachmentId}-${fileName}`);
54173
+ const localPath = import_node_path19.default.join(dir, `${attachmentId}-${fileName}`);
53991
54174
  await (0, import_promises13.writeFile)(localPath, buffer);
53992
54175
  const attachment = saveMobileAttachment({
53993
54176
  id: attachmentId,
@@ -54015,7 +54198,7 @@ mobileRoutes.post("/mobile/messages", async (c) => {
54015
54198
  }
54016
54199
  const mobileUserId = resolveMobileUserId(body, c.req.header("x-user-id"));
54017
54200
  const threadId = resolveThreadId(body.goalId, mobileUserId);
54018
- const requestId = body.clientMessageId?.trim() || (0, import_node_crypto16.randomUUID)();
54201
+ const requestId = body.clientMessageId?.trim() || (0, import_node_crypto17.randomUUID)();
54019
54202
  const agentText = body.contextText?.trim() || text2;
54020
54203
  const attachmentContent = buildContentWithAttachments(agentText, attachments);
54021
54204
  const content = buildGoalPrompt(body.goalId, attachmentContent.content);
@@ -54206,7 +54389,7 @@ function createServer() {
54206
54389
  app.route("/api", mobileRoutes);
54207
54390
  app.use("/*", serveStatic({ root: webDistRoot }));
54208
54391
  app.get("/*", async (c) => {
54209
- const indexHtml = await (0, import_promises14.readFile)(import_node_path19.default.join(webDistRoot, "index.html"), "utf8");
54392
+ const indexHtml = await (0, import_promises14.readFile)(import_node_path20.default.join(webDistRoot, "index.html"), "utf8");
54210
54393
  const tenantId = c.req.header("x-tenant-id");
54211
54394
  const assetBase = tenantId ? `/t/${tenantId}` : "";
54212
54395
  const html = indexHtml.replaceAll('"./', `"${assetBase}/`);
@@ -54233,10 +54416,10 @@ function shouldStartCoreChannelGateways(env = process.env) {
54233
54416
  }
54234
54417
 
54235
54418
  // src/runtime/saasAssets.ts
54236
- var import_node_fs9 = require("node:fs");
54419
+ var import_node_fs10 = require("node:fs");
54237
54420
  var import_promises15 = require("node:fs/promises");
54238
54421
  var import_node_os3 = require("node:os");
54239
- var import_node_path20 = __toESM(require("node:path"));
54422
+ var import_node_path21 = __toESM(require("node:path"));
54240
54423
  var MAX_CONTEXT_ASSETS = Number(process.env.DUCLAW_SAAS_ASSET_CONTEXT_LIMIT ?? 1e3);
54241
54424
  var MAX_SKILL_ASSETS = Number(process.env.DUCLAW_SAAS_ASSET_SKILL_LIMIT ?? 200);
54242
54425
  async function restoreSaasRuntimeAssets(reason) {
@@ -54266,8 +54449,8 @@ async function restoreSaasRuntimeAssets(reason) {
54266
54449
  async function restoreContextAsset(context, overwrite) {
54267
54450
  const target = contextPathForSourceKey(context.sourceKey);
54268
54451
  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 });
54452
+ if (!overwrite && (0, import_node_fs10.existsSync)(target)) return false;
54453
+ await (0, import_promises15.mkdir)(import_node_path21.default.dirname(target), { recursive: true });
54271
54454
  await (0, import_promises15.writeFile)(target, JSON.stringify(context.payload), "utf8");
54272
54455
  return true;
54273
54456
  }
@@ -54275,8 +54458,8 @@ async function restoreSkillAsset(skill, overwrite) {
54275
54458
  if (!skill.skillMd) return false;
54276
54459
  const target = safeSkillTargetPath(skill.sourcePath, skill.skillName);
54277
54460
  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 });
54461
+ if (!overwrite && (0, import_node_fs10.existsSync)(target)) return false;
54462
+ await (0, import_promises15.mkdir)(import_node_path21.default.dirname(target), { recursive: true });
54280
54463
  await (0, import_promises15.writeFile)(target, skill.skillMd, "utf8");
54281
54464
  return true;
54282
54465
  }
@@ -54309,30 +54492,30 @@ function runtimeAssetClient() {
54309
54492
  function contextPathForSourceKey(sourceKey) {
54310
54493
  if (sourceKey.startsWith("agent:")) {
54311
54494
  const logicalKey = sourceKey.slice("agent:".length);
54312
- return import_node_path20.default.join(getDuclawDataDir(), "kv", "agent", `${Buffer.from(logicalKey).toString("base64url")}.json`);
54495
+ return import_node_path21.default.join(getDuclawDataDir(), "kv", "agent", `${Buffer.from(logicalKey).toString("base64url")}.json`);
54313
54496
  }
54314
54497
  if (sourceKey.startsWith("goal-context:")) {
54315
- return import_node_path20.default.join(getDuclawHomeDir(), "goal-context", `${sourceKey.slice("goal-context:".length)}.json`);
54498
+ return import_node_path21.default.join(getDuclawHomeDir(), "goal-context", `${sourceKey.slice("goal-context:".length)}.json`);
54316
54499
  }
54317
54500
  if (sourceKey.startsWith("tasks:")) {
54318
- return import_node_path20.default.join(getDuclawHomeDir(), "tasks", `${sourceKey.slice("tasks:".length)}.json`);
54501
+ return import_node_path21.default.join(getDuclawHomeDir(), "tasks", `${sourceKey.slice("tasks:".length)}.json`);
54319
54502
  }
54320
54503
  return null;
54321
54504
  }
54322
54505
  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"))) {
54506
+ const allowedRoots = skillRoots().map((root) => import_node_path21.default.resolve(root));
54507
+ const normalized = import_node_path21.default.resolve(sourcePath);
54508
+ 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
54509
  return normalized;
54327
54510
  }
54328
54511
  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");
54512
+ return import_node_path21.default.join("/home/user/app/skills", safeName, "SKILL.md");
54330
54513
  }
54331
54514
  function skillRoots() {
54332
54515
  return [
54333
54516
  "/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")
54517
+ import_node_path21.default.join(getDuclawHomeDir(), "skills"),
54518
+ import_node_path21.default.join((0, import_node_os3.homedir)(), ".agents", "skills")
54336
54519
  ];
54337
54520
  }
54338
54521