duclaw-cli 1.8.1 → 1.8.3

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.8.1" : "unknown"}`);
30245
+ console.log(`duclaw-cli v${true ? "1.8.3" : "unknown"}`);
30246
30246
  }
30247
30247
  function getDuclawTemplate() {
30248
30248
  return {
@@ -37937,20 +37937,29 @@ var getMessages = async (storage, userId, limit = 300, date, cronTitle) => {
37937
37937
  const key = buildMessageKey(userId, date, cronTitle);
37938
37938
  const data = await storage.get(key);
37939
37939
  if (!data) return [];
37940
- let start = Math.max(0, data.length - limit);
37941
- if (data[start].role !== "user") {
37940
+ const repair = sanitizeMessagesWithReport(data, {
37941
+ mergeAdjacentNonProtocolMessages: false
37942
+ });
37943
+ const repairedData = repair.messages;
37944
+ if (repair.report.changed) {
37945
+ await storage.set(key, repairedData);
37946
+ logSanitizeRepair(repair.report);
37947
+ }
37948
+ if (repairedData.length === 0) return [];
37949
+ let start = Math.max(0, repairedData.length - limit);
37950
+ if (repairedData[start].role !== "user") {
37942
37951
  let earlier = start - 1;
37943
- while (earlier >= 0 && data[earlier].role !== "user") earlier--;
37952
+ while (earlier >= 0 && repairedData[earlier].role !== "user") earlier--;
37944
37953
  if (earlier >= 0) {
37945
37954
  start = earlier;
37946
37955
  } else {
37947
37956
  let later = start + 1;
37948
- while (later < data.length && data[later].role !== "user") later++;
37949
- if (later >= data.length) return [];
37957
+ while (later < repairedData.length && repairedData[later].role !== "user") later++;
37958
+ if (later >= repairedData.length) return [];
37950
37959
  start = later;
37951
37960
  }
37952
37961
  }
37953
- return sanitizeMessages(data.slice(start));
37962
+ return sanitizeMessages(repairedData.slice(start));
37954
37963
  };
37955
37964
  var isInjectedMemoryContextMessage = (msg) => {
37956
37965
  if (msg.role !== "user" || msg.content.length === 0) return false;
@@ -37961,44 +37970,113 @@ var isInjectedMemoryContextMessage = (msg) => {
37961
37970
  var hasProtocolBlock = (msg) => {
37962
37971
  return msg.content.some((block) => block.type === "tool_use" || block.type === "tool_result");
37963
37972
  };
37964
- var stripToolResultBlocks = (msg) => {
37973
+ var createSanitizeReport = () => ({
37974
+ changed: false,
37975
+ removedToolUses: 0,
37976
+ removedToolResults: 0,
37977
+ removedEmptyMessages: 0,
37978
+ removedMemoryContextMessages: 0,
37979
+ droppedMessages: 0,
37980
+ shiftedLeadingMessages: 0,
37981
+ mergedAdjacentMessages: 0
37982
+ });
37983
+ var markSanitizeChanged = (report, index) => {
37984
+ report.changed = true;
37985
+ if (report.firstChangedIndex === void 0 || index < report.firstChangedIndex) {
37986
+ report.firstChangedIndex = index;
37987
+ }
37988
+ };
37989
+ var mergeSanitizeReport = (target, source) => {
37990
+ if (!source.changed) return;
37991
+ target.changed = true;
37992
+ if (source.firstChangedIndex !== void 0 && (target.firstChangedIndex === void 0 || source.firstChangedIndex < target.firstChangedIndex)) {
37993
+ target.firstChangedIndex = source.firstChangedIndex;
37994
+ }
37995
+ target.removedToolUses += source.removedToolUses;
37996
+ target.removedToolResults += source.removedToolResults;
37997
+ target.removedEmptyMessages += source.removedEmptyMessages;
37998
+ target.removedMemoryContextMessages += source.removedMemoryContextMessages;
37999
+ target.droppedMessages += source.droppedMessages;
38000
+ target.shiftedLeadingMessages += source.shiftedLeadingMessages;
38001
+ target.mergedAdjacentMessages += source.mergedAdjacentMessages;
38002
+ };
38003
+ var stripToolResultBlocks = (msg, report, index) => {
38004
+ const removed = msg.content.filter((block) => block.type === "tool_result").length;
37965
38005
  const content = msg.content.filter((block) => block.type !== "tool_result");
38006
+ if (report && removed > 0 && index !== void 0) {
38007
+ report.removedToolResults += removed;
38008
+ markSanitizeChanged(report, index);
38009
+ }
37966
38010
  if (content.length === 0) return null;
37967
38011
  if (content.every((b) => b.type === "text" && !b.text?.trim())) return null;
37968
38012
  return { ...msg, content };
37969
38013
  };
37970
- var stripToolUseBlocks = (msg) => {
38014
+ var stripToolUseBlocks = (msg, report, index) => {
38015
+ const removed = msg.content.filter((block) => block.type === "tool_use").length;
37971
38016
  const content = msg.content.filter((block) => block.type !== "tool_use");
38017
+ if (report && removed > 0 && index !== void 0) {
38018
+ report.removedToolUses += removed;
38019
+ markSanitizeChanged(report, index);
38020
+ }
37972
38021
  if (content.length === 0) return null;
37973
38022
  if (content.every((b) => b.type === "text" && !b.text?.trim())) return null;
37974
38023
  return { ...msg, content };
37975
38024
  };
37976
- var sanitizeMessages = (messages) => {
37977
- if (messages.length === 0) return messages;
38025
+ var recordDroppedMessage = (report, index) => {
38026
+ report.droppedMessages++;
38027
+ markSanitizeChanged(report, index);
38028
+ };
38029
+ var logSanitizeRepair = (report) => {
38030
+ console.warn(
38031
+ `[sanitize] repaired message history removedToolUses=${report.removedToolUses} removedToolResults=${report.removedToolResults} droppedMessages=${report.droppedMessages} emptyMessages=${report.removedEmptyMessages} memoryContextMessages=${report.removedMemoryContextMessages} shiftedLeadingMessages=${report.shiftedLeadingMessages} firstIndex=${report.firstChangedIndex ?? -1}`
38032
+ );
38033
+ };
38034
+ var sanitizeMessagesWithReport = (messages, options = {}) => {
38035
+ const report = createSanitizeReport();
38036
+ const mergeAdjacentNonProtocolMessages2 = options.mergeAdjacentNonProtocolMessages ?? true;
38037
+ if (messages.length === 0) {
38038
+ return { messages, report };
38039
+ }
37978
38040
  const result = [];
37979
38041
  for (let i = 0; i < messages.length; i++) {
37980
38042
  const msg = messages[i];
37981
- if (isInjectedMemoryContextMessage(msg)) continue;
37982
- if (!msg.content || msg.content.length === 0) continue;
37983
- if (msg.content.every((b) => b.type === "text" && !b.text?.trim())) continue;
38043
+ if (isInjectedMemoryContextMessage(msg)) {
38044
+ report.removedMemoryContextMessages++;
38045
+ recordDroppedMessage(report, i);
38046
+ continue;
38047
+ }
38048
+ if (!msg.content || msg.content.length === 0) {
38049
+ report.removedEmptyMessages++;
38050
+ recordDroppedMessage(report, i);
38051
+ continue;
38052
+ }
38053
+ if (msg.content.every((b) => b.type === "text" && !b.text?.trim())) {
38054
+ report.removedEmptyMessages++;
38055
+ recordDroppedMessage(report, i);
38056
+ continue;
38057
+ }
37984
38058
  let nextMsg = msg;
37985
38059
  if (msg.role === "assistant") {
37986
38060
  const toolUses = msg.content.filter((b) => b.type === "tool_use");
37987
38061
  if (toolUses.length > 0) {
37988
38062
  const next = messages[i + 1];
37989
38063
  if (!next || next.role !== "user") {
37990
- console.warn(`[sanitize] \u53D1\u73B0 assistant tool_use \u65E0\u5BF9\u5E94 tool_result\uFF0C\u5DF2\u79FB\u9664\u4E0D\u5408\u6CD5\u5DE5\u5177\u8C03\u7528\uFF08index=${i}\uFF09`);
37991
- nextMsg = stripToolUseBlocks(msg);
37992
- if (!nextMsg) continue;
38064
+ nextMsg = stripToolUseBlocks(msg, report, i);
38065
+ if (!nextMsg) {
38066
+ recordDroppedMessage(report, i);
38067
+ continue;
38068
+ }
37993
38069
  } else {
37994
38070
  const resultIds = new Set(
37995
38071
  next.content.filter((b) => b.type === "tool_result").map((b) => b.tool_use_id)
37996
38072
  );
37997
38073
  const allMatched = toolUses.every((tu) => resultIds.has(tu.id));
37998
38074
  if (!allMatched) {
37999
- console.warn(`[sanitize] tool_use/tool_result id \u4E0D\u5339\u914D\uFF0C\u5DF2\u79FB\u9664\u4E0D\u5408\u6CD5\u5DE5\u5177\u8C03\u7528\uFF08index=${i}\uFF09`);
38000
- nextMsg = stripToolUseBlocks(msg);
38001
- if (!nextMsg) continue;
38075
+ nextMsg = stripToolUseBlocks(msg, report, i);
38076
+ if (!nextMsg) {
38077
+ recordDroppedMessage(report, i);
38078
+ continue;
38079
+ }
38002
38080
  }
38003
38081
  }
38004
38082
  }
@@ -38012,23 +38090,39 @@ var sanitizeMessages = (messages) => {
38012
38090
  );
38013
38091
  const allResultsMatched = toolResults.every((tr) => prevToolUseIds.has(tr.tool_use_id));
38014
38092
  if (!prev2 || prev2.role !== "assistant" || prevToolUseIds.size === 0 || !allResultsMatched) {
38015
- console.warn(`[sanitize] \u53D1\u73B0\u5B64\u513F tool_result\uFF0C\u5DF2\u79FB\u9664\u4E0D\u5408\u6CD5\u5DE5\u5177\u7ED3\u679C`);
38016
- nextMsg = stripToolResultBlocks(nextMsg);
38017
- if (!nextMsg) continue;
38093
+ nextMsg = stripToolResultBlocks(nextMsg, report, i);
38094
+ if (!nextMsg) {
38095
+ recordDroppedMessage(report, i);
38096
+ continue;
38097
+ }
38018
38098
  }
38019
38099
  }
38020
38100
  }
38021
38101
  const prev = result[result.length - 1];
38022
- if (prev && prev.role === nextMsg.role && !hasProtocolBlock(prev) && !hasProtocolBlock(nextMsg)) {
38102
+ if (mergeAdjacentNonProtocolMessages2 && prev && prev.role === nextMsg.role && !hasProtocolBlock(prev) && !hasProtocolBlock(nextMsg)) {
38023
38103
  prev.content = [...prev.content, ...nextMsg.content];
38104
+ report.mergedAdjacentMessages++;
38105
+ markSanitizeChanged(report, i);
38024
38106
  } else {
38025
38107
  result.push({ ...nextMsg, content: [...nextMsg.content] });
38026
38108
  }
38027
38109
  }
38028
38110
  while (result.length > 0 && result[0].role !== "user") {
38029
38111
  result.shift();
38112
+ report.shiftedLeadingMessages++;
38113
+ markSanitizeChanged(report, 0);
38030
38114
  }
38031
- return result;
38115
+ if (report.shiftedLeadingMessages > 0) {
38116
+ const revalidated = sanitizeMessagesWithReport(result, options);
38117
+ if (revalidated.report.changed) {
38118
+ mergeSanitizeReport(report, revalidated.report);
38119
+ return { messages: revalidated.messages, report };
38120
+ }
38121
+ }
38122
+ return { messages: result, report };
38123
+ };
38124
+ var sanitizeMessages = (messages) => {
38125
+ return sanitizeMessagesWithReport(messages).messages;
38032
38126
  };
38033
38127
  var addMessage = async (storage, userId, message, date, cronTitle) => {
38034
38128
  const key = buildMessageKey(userId, date, cronTitle);
@@ -40566,6 +40660,128 @@ var isTextBlock = (block) => block.type === `text`;
40566
40660
  var isToolUseBlock = (block) => block.type === "tool_use";
40567
40661
  var extractText = (blocks) => blocks.filter(isTextBlock).map((b) => b.text).join("\n");
40568
40662
 
40663
+ // src/tools/tools/ImageUnderstandMetering.ts
40664
+ var import_node_crypto = require("node:crypto");
40665
+ var ImageUnderstandMeteringError = class extends Error {
40666
+ constructor(message, statusCode, meteringStatus) {
40667
+ super(message);
40668
+ this.statusCode = statusCode;
40669
+ this.meteringStatus = meteringStatus;
40670
+ this.name = "ImageUnderstandMeteringError";
40671
+ }
40672
+ statusCode;
40673
+ meteringStatus;
40674
+ };
40675
+ async function reportImageUnderstandUsage(input) {
40676
+ const baseUrl = input.baseUrl?.trim().replace(/\/+$/, "");
40677
+ const token = input.token?.trim();
40678
+ const tenantId = input.tenantId?.trim();
40679
+ if (!baseUrl || !token || !tenantId) {
40680
+ return { status: "disabled" };
40681
+ }
40682
+ const payload = buildImageUnderstandMeteringPayload({
40683
+ ...input,
40684
+ tenantId
40685
+ });
40686
+ const response = await fetch(`${baseUrl}/internal/metering/image-understand`, {
40687
+ method: "POST",
40688
+ headers: {
40689
+ "content-type": "application/json",
40690
+ authorization: `Bearer ${token}`
40691
+ },
40692
+ body: JSON.stringify(payload)
40693
+ });
40694
+ const body = await parseResponseJson(response);
40695
+ const result = body && typeof body === "object" && "result" in body ? body.result : void 0;
40696
+ if (result?.status === "credit_exhausted") {
40697
+ throw new ImageUnderstandMeteringError(
40698
+ "[ImageUnderstand] credit_exhausted",
40699
+ response.status,
40700
+ result.status
40701
+ );
40702
+ }
40703
+ if (!response.ok || result?.status === "failed") {
40704
+ throw new ImageUnderstandMeteringError(
40705
+ `[ImageUnderstand] \u56FE\u7247\u7406\u89E3\u6263\u8D39\u4E0A\u62A5\u5931\u8D25: ${result?.status ?? response.status}`,
40706
+ response.status,
40707
+ result?.status
40708
+ );
40709
+ }
40710
+ return {
40711
+ status: result?.status === "skipped_existing" ? "skipped_existing" : "charged",
40712
+ creditAmount: result?.creditAmount ?? null
40713
+ };
40714
+ }
40715
+ function buildImageUnderstandMeteringPayload(input) {
40716
+ const metadata = input.userRequest?.metadata ?? {};
40717
+ const messageId = stringValue(metadata.message_id) ?? nestedMessageId(metadata) ?? stringValue(input.userRequest?.requestId);
40718
+ const imageKey = stringValue(metadata.imageKey) ?? stringValue(metadata.image_key) ?? firstString(metadata.imageKeys);
40719
+ return {
40720
+ tenantId: input.tenantId,
40721
+ provider: input.provider,
40722
+ model: input.model,
40723
+ requestId: stringValue(input.userRequest?.requestId),
40724
+ messageId,
40725
+ imageKey,
40726
+ imageFingerprint: fingerprintImageSource(input.imageSource),
40727
+ toolCallId: stringValue(metadata.toolCallId),
40728
+ promptChars: input.prompt.length,
40729
+ resultChars: input.resultText.length,
40730
+ platform: stringValue(input.userRequest?.platform),
40731
+ occurredAt: (/* @__PURE__ */ new Date()).toISOString(),
40732
+ metadata: {
40733
+ imageSourceKind: imageSourceKind(input.imageSource),
40734
+ chatType: stringValue(input.userRequest?.chatType),
40735
+ authStyle: stringValue(input.authStyle)
40736
+ }
40737
+ };
40738
+ }
40739
+ function inferImageProvider(baseUrl) {
40740
+ if (!baseUrl) return "moonshot";
40741
+ try {
40742
+ const host = new URL(baseUrl).hostname.toLowerCase();
40743
+ if (host.includes("moonshot")) return "moonshot";
40744
+ if (host.includes("anthropic")) return "anthropic";
40745
+ if (host.includes("bigmodel") || host.includes("zhipu")) return "zhipu";
40746
+ return host.replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "") || "custom";
40747
+ } catch {
40748
+ return "custom";
40749
+ }
40750
+ }
40751
+ function fingerprintImageSource(imageSource) {
40752
+ return (0, import_node_crypto.createHash)("sha256").update(imageSource).digest("hex");
40753
+ }
40754
+ function imageSourceKind(imageSource) {
40755
+ if (imageSource.startsWith("http://") || imageSource.startsWith("https://")) return "url";
40756
+ if (imageSource.startsWith("/") || imageSource.startsWith(".") || /^[A-Za-z]:[\\/]/.test(imageSource)) return "local_file";
40757
+ return "base64";
40758
+ }
40759
+ function nestedMessageId(metadata) {
40760
+ const message = metadata.message;
40761
+ if (!message || typeof message !== "object" || Array.isArray(message)) return null;
40762
+ return stringValue(message.message_id);
40763
+ }
40764
+ function firstString(value) {
40765
+ if (!Array.isArray(value)) return null;
40766
+ for (const item of value) {
40767
+ const normalized = stringValue(item);
40768
+ if (normalized) return normalized;
40769
+ }
40770
+ return null;
40771
+ }
40772
+ function stringValue(value) {
40773
+ if (typeof value !== "string") return null;
40774
+ const normalized = value.trim();
40775
+ return normalized ? normalized : null;
40776
+ }
40777
+ async function parseResponseJson(response) {
40778
+ try {
40779
+ return await response.json();
40780
+ } catch {
40781
+ return null;
40782
+ }
40783
+ }
40784
+
40569
40785
  // src/tools/tools/ImageUnderstand.ts
40570
40786
  var guessMediaTypeFromData = (base64Data) => {
40571
40787
  if (base64Data.startsWith("/9j/")) return "image/jpeg";
@@ -40643,11 +40859,12 @@ var imageUnderstand = {
40643
40859
  if (!url) throw new Error(`[ImageUnderstand] TOOL_IMAGE_ANTHROPIC_BASE_URL\u672A\u914D\u7F6E`);
40644
40860
  if (!key) throw new Error(`[ImageUnderstand] TOOL_IMAGE_ANTHROPIC_AUTH_TOKEN\u672A\u914D\u7F6E`);
40645
40861
  if (!model) throw new Error(`[ImageUnderstand] TOOL_IMAGE_ANTHROPIC_MODEL\u672A\u914D\u7F6E`);
40862
+ const authStyle = process.env.TOOL_IMAGE_ANTHROPIC_AUTH_STYLE || "bearer";
40646
40863
  const anthropicOption = {
40647
40864
  baseURL: url,
40648
40865
  apiKey: key,
40649
40866
  model,
40650
- authStyle: process.env.TOOL_IMAGE_ANTHROPIC_AUTH_STYLE || "bearer"
40867
+ authStyle
40651
40868
  };
40652
40869
  const llmClient = createAnthropicAdapter(anthropicOption);
40653
40870
  const imageBlock = await resolveImageBlock(imageSource);
@@ -40657,7 +40874,20 @@ var imageUnderstand = {
40657
40874
  [imageMessage],
40658
40875
  system
40659
40876
  );
40660
- return extractText(response.content);
40877
+ const resultText = extractText(response.content);
40878
+ await reportImageUnderstandUsage({
40879
+ baseUrl: process.env.DUCLAW_CONTROL_PLANE_BASE_URL,
40880
+ token: process.env.DUCLAW_CONTROL_PLANE_METERING_TOKEN,
40881
+ tenantId: process.env.DUCLAW_TENANT_ID,
40882
+ provider: inferImageProvider(url),
40883
+ model,
40884
+ prompt,
40885
+ resultText,
40886
+ imageSource,
40887
+ authStyle,
40888
+ userRequest
40889
+ });
40890
+ return resultText;
40661
40891
  }
40662
40892
  };
40663
40893
 
@@ -41084,10 +41314,10 @@ var goalDelete = {
41084
41314
  };
41085
41315
 
41086
41316
  // src/tools/tools/department/DepartmentCreate.ts
41087
- var import_node_crypto3 = require("node:crypto");
41317
+ var import_node_crypto4 = require("node:crypto");
41088
41318
 
41089
41319
  // src/department/mailbox/mailbox.ts
41090
- var import_node_crypto2 = require("node:crypto");
41320
+ var import_node_crypto3 = require("node:crypto");
41091
41321
 
41092
41322
  // src/db/createDB.ts
41093
41323
  var import_better_sqlite3 = __toESM(require("better-sqlite3"));
@@ -41214,7 +41444,7 @@ var create_mailbox_events_table = () => {
41214
41444
  };
41215
41445
 
41216
41446
  // src/department/mailbox/events.ts
41217
- var import_node_crypto = require("node:crypto");
41447
+ var import_node_crypto2 = require("node:crypto");
41218
41448
  var parseDetail = (detailJson) => {
41219
41449
  if (!detailJson) return void 0;
41220
41450
  try {
@@ -41255,7 +41485,7 @@ var mapMailboxEventRow = (row) => {
41255
41485
  var recordMailboxEvent = (input) => {
41256
41486
  const db3 = createSqliteDB();
41257
41487
  const event = {
41258
- id: (0, import_node_crypto.randomUUID)().slice(0, 12),
41488
+ id: (0, import_node_crypto2.randomUUID)().slice(0, 12),
41259
41489
  messageId: input.messageId,
41260
41490
  mailboxId: input.mailboxId,
41261
41491
  actorMailboxId: input.actorMailboxId,
@@ -41579,7 +41809,7 @@ var sendMessage2 = (fromMailboxId, toMailboxId, content, options) => {
41579
41809
  thread_id,
41580
41810
  parent_message_id
41581
41811
  ) values (?,?,?,?,?,?,?,?,?,?) `);
41582
- const id = (0, import_node_crypto2.randomUUID)().slice(0, 8);
41812
+ const id = (0, import_node_crypto3.randomUUID)().slice(0, 8);
41583
41813
  const threadId = options?.threadId || id;
41584
41814
  let mailboxMsg = {
41585
41815
  id,
@@ -41694,7 +41924,7 @@ var departmentCreate = {
41694
41924
  return `[departmentCreate] \u4E0D\u5B58\u5728 id=${sourceGoalId} \u7684\u76EE\u6807`;
41695
41925
  }
41696
41926
  let departmentDefinition = {
41697
- id: (0, import_node_crypto3.randomUUID)().slice(0, 8),
41927
+ id: (0, import_node_crypto4.randomUUID)().slice(0, 8),
41698
41928
  name,
41699
41929
  charter,
41700
41930
  sourceGoalId,
@@ -41908,7 +42138,7 @@ var departmentList = {
41908
42138
  };
41909
42139
 
41910
42140
  // src/tools/tools/department/DepartmentMemberCreate.ts
41911
- var import_node_crypto4 = require("node:crypto");
42141
+ var import_node_crypto5 = require("node:crypto");
41912
42142
  var DESCRIPTION24 = `
41913
42143
  \u521B\u5EFA\u90E8\u95E8\u6210\u5458\u3002
41914
42144
 
@@ -41974,7 +42204,7 @@ var departmentMemberCreate = {
41974
42204
  }
41975
42205
  }
41976
42206
  let departmentMember = {
41977
- id: (0, import_node_crypto4.randomUUID)().slice(0, 8),
42207
+ id: (0, import_node_crypto5.randomUUID)().slice(0, 8),
41978
42208
  name,
41979
42209
  departmentId: department.id,
41980
42210
  mailBoxId: getMailBoxId(department.name, name),
@@ -42157,7 +42387,7 @@ ${replies}`;
42157
42387
  // src/department/learning.ts
42158
42388
  var import_node_fs3 = require("node:fs");
42159
42389
  var import_node_path11 = __toESM(require("node:path"));
42160
- var import_node_crypto5 = require("node:crypto");
42390
+ var import_node_crypto6 = require("node:crypto");
42161
42391
  var ensureDir = (dir) => (0, import_node_fs3.mkdirSync)(dir, { recursive: true });
42162
42392
  var readJsonArray = (filePath) => {
42163
42393
  if (!(0, import_node_fs3.existsSync)(filePath)) return [];
@@ -42193,7 +42423,7 @@ var listDepartmentMemories = (departmentName) => {
42193
42423
  var createDepartmentMemory = (departmentName, input) => {
42194
42424
  const now = Date.now();
42195
42425
  const memory = {
42196
- id: (0, import_node_crypto5.randomUUID)().slice(0, 8),
42426
+ id: (0, import_node_crypto6.randomUUID)().slice(0, 8),
42197
42427
  departmentName,
42198
42428
  title: input.title,
42199
42429
  content: input.content,
@@ -42235,7 +42465,7 @@ var proposeDepartmentSkill = (departmentName, input) => {
42235
42465
  }
42236
42466
  const now = Date.now();
42237
42467
  const skill = {
42238
- id: (0, import_node_crypto5.randomUUID)().slice(0, 8),
42468
+ id: (0, import_node_crypto6.randomUUID)().slice(0, 8),
42239
42469
  departmentName,
42240
42470
  skillName: input.skillName,
42241
42471
  description: input.description,
@@ -42272,7 +42502,7 @@ var createDepartmentProposal = (input) => {
42272
42502
  const records = readJsonArray(proposalsPath());
42273
42503
  const proposal = {
42274
42504
  ...input,
42275
- id: (0, import_node_crypto5.randomUUID)().slice(0, 8),
42505
+ id: (0, import_node_crypto6.randomUUID)().slice(0, 8),
42276
42506
  status: "pending",
42277
42507
  createdAt: Date.now()
42278
42508
  };
@@ -43297,7 +43527,7 @@ var readDreamHistoryLimit = () => {
43297
43527
  var import_node_fs5 = require("node:fs");
43298
43528
  var import_node_os2 = require("node:os");
43299
43529
  var import_node_path12 = require("node:path");
43300
- var import_node_crypto6 = require("node:crypto");
43530
+ var import_node_crypto7 = require("node:crypto");
43301
43531
  var SkillForgeEngine = class {
43302
43532
  proposalStorage;
43303
43533
  draftRoot;
@@ -43326,7 +43556,7 @@ var SkillForgeEngine = class {
43326
43556
  if (pending.some((p) => p.skillName === skillName)) {
43327
43557
  return null;
43328
43558
  }
43329
- const id = (0, import_node_crypto6.randomBytes)(4).toString("hex");
43559
+ const id = (0, import_node_crypto7.randomBytes)(4).toString("hex");
43330
43560
  const draftDir = (0, import_node_path12.join)(this.draftRoot, userId, id);
43331
43561
  (0, import_node_fs5.mkdirSync)(draftDir, { recursive: true });
43332
43562
  (0, import_node_fs5.writeFileSync)((0, import_node_path12.join)(draftDir, "SKILL.md"), skillMd, "utf-8");
@@ -43574,7 +43804,7 @@ var skillForgeDrop = (engine) => ({
43574
43804
  });
43575
43805
 
43576
43806
  // src/memory/MemoryEngine.ts
43577
- var import_node_crypto7 = require("node:crypto");
43807
+ var import_node_crypto8 = require("node:crypto");
43578
43808
  var MemoryEngine = class {
43579
43809
  storage;
43580
43810
  recallIndexStorage;
@@ -43602,7 +43832,7 @@ var MemoryEngine = class {
43602
43832
  }
43603
43833
  const now = Date.now();
43604
43834
  const memory = {
43605
- id: (0, import_node_crypto7.randomBytes)(4).toString("hex"),
43835
+ id: (0, import_node_crypto8.randomBytes)(4).toString("hex"),
43606
43836
  userId,
43607
43837
  title,
43608
43838
  content,
@@ -44794,7 +45024,14 @@ ${memoryInjection}` : "") + dreamInjection;
44794
45024
  }
44795
45025
  }
44796
45026
  }
44797
- const result = await toolExecutor.execute(useBlock.name, useBlock.input, request);
45027
+ const toolRequest = {
45028
+ ...request,
45029
+ metadata: {
45030
+ ...request.metadata ?? {},
45031
+ toolCallId: useBlock.id
45032
+ }
45033
+ };
45034
+ const result = await toolExecutor.execute(useBlock.name, useBlock.input, toolRequest);
44798
45035
  return toolResult(useBlock.id, result);
44799
45036
  } catch (error) {
44800
45037
  const err = error;
@@ -45075,10 +45312,24 @@ var executeJob = async (job) => {
45075
45312
  const resultFilePath = saveResultToFile(job, result);
45076
45313
  console.log(`[cron\u6267\u884C\u5B8C\u6210] \u5B9A\u65F6\u4EFB\u52A1\u6267\u884C\u7ED3\u679C: ${resultFilePath}`);
45077
45314
  updateJobLastRunTime(job.id);
45315
+ return { ok: true, resultFilePath };
45078
45316
  } catch (error) {
45079
- console.error(`[cron] \u4EFB\u52A1\u6267\u884C\u5931\u8D25: ${job.id} - ${error}`);
45317
+ const message = error instanceof Error ? error.message : String(error);
45318
+ console.error(`[cron] \u4EFB\u52A1\u6267\u884C\u5931\u8D25: ${job.id} - ${message}`);
45319
+ return { ok: false, error: message };
45080
45320
  }
45081
45321
  };
45322
+ var runJobById = async (jobId) => {
45323
+ const job = listJobs().find((item) => item.id === jobId);
45324
+ if (!job) {
45325
+ return { ok: false, error: "job_not_found" };
45326
+ }
45327
+ if (!job.enabled) {
45328
+ return { ok: false, error: "job_disabled", job };
45329
+ }
45330
+ const result = await executeJob(job);
45331
+ return { ...result, job };
45332
+ };
45082
45333
  var executeAgentJob = async (job) => {
45083
45334
  const { registry: registry2, executor } = getCronTools();
45084
45335
  const tools = getAllTools(registry2);
@@ -49761,6 +50012,23 @@ cronRoutes.get("/cron/jobs/:id/history", (c) => {
49761
50012
  return c.json({ error: err.message || "Failed to get job history" }, 500);
49762
50013
  }
49763
50014
  });
50015
+ cronRoutes.post("/cron/jobs/:id/run", async (c) => {
50016
+ const id = c.req.param("id");
50017
+ try {
50018
+ const result = await runJobById(id);
50019
+ if (!result.ok) {
50020
+ const status = result.error === "job_not_found" ? 404 : 400;
50021
+ return c.json({ error: result.error || "Failed to run job" }, status);
50022
+ }
50023
+ return c.json({
50024
+ ok: true,
50025
+ jobId: result.job?.id ?? id,
50026
+ resultFilePath: result.resultFilePath
50027
+ });
50028
+ } catch (err) {
50029
+ return c.json({ error: err.message || "Failed to run job" }, 500);
50030
+ }
50031
+ });
49764
50032
 
49765
50033
  // src/server/routes/mailbox.ts
49766
50034
  var mailboxRoutes = new Hono2();
@@ -50039,7 +50307,16 @@ var extractMemoryUserIdsFromKeys = (keys) => {
50039
50307
  };
50040
50308
  var collectCompactSummaries = async (userId) => {
50041
50309
  if (isFileBackedRuntime()) {
50042
- return [];
50310
+ const { compactSummaryStorage: compactSummaryStorage2 } = getSharedDeps();
50311
+ const records2 = [];
50312
+ const prefix2 = `agent:compact:summary:${userId}:`;
50313
+ for (const key of extractFileBackedStorageKeysForTest(getDuclawDataDir())) {
50314
+ if (!key.startsWith(prefix2)) continue;
50315
+ const logicalKey = key.startsWith("agent:") ? key.slice("agent:".length) : key;
50316
+ const value = await compactSummaryStorage2.get(logicalKey);
50317
+ if (value?.length) records2.push(...value);
50318
+ }
50319
+ return records2.sort((a, b) => b.createdAt - a.createdAt);
50043
50320
  }
50044
50321
  const client2 = await getRedisClient();
50045
50322
  const { compactSummaryStorage } = getSharedDeps();
@@ -50054,6 +50331,61 @@ var collectCompactSummaries = async (userId) => {
50054
50331
  }
50055
50332
  return records.sort((a, b) => b.createdAt - a.createdAt);
50056
50333
  };
50334
+ var isMailboxScopedUserId = (userId) => userId === "manager" || userId.includes("::") || userId.startsWith("cron-") || userId.startsWith("kanban:goal:");
50335
+ var collectManagerRelatedUserIds = (userId) => {
50336
+ if (isMailboxScopedUserId(userId)) return [userId];
50337
+ try {
50338
+ const db3 = createSqliteDB();
50339
+ const rows = db3.prepare(
50340
+ `SELECT DISTINCT to_mailbox_id AS toMailboxId, from_mailbox_id AS fromMailboxId
50341
+ FROM mailbox
50342
+ WHERE origin_user_id = ?
50343
+ ORDER BY to_mailbox_id ASC, from_mailbox_id ASC`
50344
+ ).all(userId);
50345
+ const ids = /* @__PURE__ */ new Set([userId]);
50346
+ for (const row of rows) {
50347
+ addPlausibleUserId(ids, row.toMailboxId || void 0);
50348
+ addPlausibleUserId(ids, row.fromMailboxId || void 0);
50349
+ }
50350
+ ids.delete("manager");
50351
+ return Array.from(ids);
50352
+ } catch (err) {
50353
+ console.warn(`[memoryRoutes] \u83B7\u53D6 Main Manager \u5173\u8054\u4E0A\u4E0B\u6587\u5931\u8D25 userId=${userId}: ${err.message}`);
50354
+ return [userId];
50355
+ }
50356
+ };
50357
+ var collectMemoryScopeUserIds = (userId) => collectManagerRelatedUserIds(userId);
50358
+ var aggregateMemorySummaries = async (userId, localConversationUserIds) => {
50359
+ const { memoryEngine, dreamStorage, dreamStateStorage, topicStorage } = getSharedDeps();
50360
+ const relatedUserIds = collectMemoryScopeUserIds(userId);
50361
+ const details = await Promise.all(relatedUserIds.map(async (scopeUserId) => {
50362
+ const [memories, dreamContent, dreamState, topics] = await Promise.all([
50363
+ memoryEngine.list(scopeUserId),
50364
+ dreamStorage.get(`dream:latest:${scopeUserId}`),
50365
+ dreamStateStorage.get(`dream:state:${scopeUserId}`),
50366
+ topicStorage.get(`topics:${scopeUserId}`)
50367
+ ]);
50368
+ return {
50369
+ userId: scopeUserId,
50370
+ memoryCount: memories.length,
50371
+ hasDream: !!dreamContent,
50372
+ hasConversation: (topics?.length ?? 0) > 0 || localConversationUserIds.has(scopeUserId),
50373
+ lastDreamAt: dreamState?.lastDreamAt ?? null,
50374
+ lastActivityAt: dreamState?.lastActivityAt ?? null
50375
+ };
50376
+ }));
50377
+ const memoryCount = details.reduce((sum, detail) => sum + detail.memoryCount, 0);
50378
+ return {
50379
+ userId,
50380
+ memoryCount,
50381
+ hasMemory: memoryCount > 0,
50382
+ hasDream: details.some((detail) => detail.hasDream),
50383
+ hasConversation: details.some((detail) => detail.hasConversation),
50384
+ lastDreamAt: Math.max(0, ...details.map((detail) => detail.lastDreamAt ?? 0)) || null,
50385
+ lastActivityAt: Math.max(0, ...details.map((detail) => detail.lastActivityAt ?? 0)) || null,
50386
+ relatedUserIds: relatedUserIds.length > 1 ? relatedUserIds : void 0
50387
+ };
50388
+ };
50057
50389
  var collectUserIds = async () => {
50058
50390
  const userIds = /* @__PURE__ */ new Set();
50059
50391
  const localConversationUserIds = collectLocalConversationUserIds();
@@ -50075,26 +50407,10 @@ var collectUserIds = async () => {
50075
50407
  var memoryRoutes = new Hono2();
50076
50408
  memoryRoutes.get("/memory/users", async (c) => {
50077
50409
  try {
50078
- const { memoryEngine, dreamStorage, dreamStateStorage, topicStorage } = getSharedDeps();
50079
50410
  const { userIds, localConversationUserIds } = await collectUserIds();
50080
- const summaries = await Promise.all(userIds.map(async (userId) => {
50081
- const [memories, dreamContent, dreamState, topics] = await Promise.all([
50082
- memoryEngine.list(userId),
50083
- dreamStorage.get(`dream:latest:${userId}`),
50084
- dreamStateStorage.get(`dream:state:${userId}`),
50085
- topicStorage.get(`topics:${userId}`)
50086
- ]);
50087
- const summary = {
50088
- userId,
50089
- memoryCount: memories.length,
50090
- hasMemory: memories.length > 0,
50091
- hasDream: !!dreamContent,
50092
- hasConversation: (topics?.length ?? 0) > 0 || localConversationUserIds.has(userId),
50093
- lastDreamAt: dreamState?.lastDreamAt ?? null,
50094
- lastActivityAt: dreamState?.lastActivityAt ?? null
50095
- };
50096
- return summary;
50097
- }));
50411
+ const summaries = await Promise.all(userIds.map(
50412
+ (userId) => aggregateMemorySummaries(userId, localConversationUserIds)
50413
+ ));
50098
50414
  summaries.sort(
50099
50415
  (a, b) => (b.lastActivityAt ?? 0) - (a.lastActivityAt ?? 0) || a.userId.localeCompare(b.userId)
50100
50416
  );
@@ -50108,7 +50424,9 @@ memoryRoutes.get("/memory", async (c) => {
50108
50424
  if (!userId) return c.json({ error: "userId is required" }, 400);
50109
50425
  try {
50110
50426
  const { memoryEngine } = getSharedDeps();
50111
- const memories = await memoryEngine.list(userId);
50427
+ const memories = (await Promise.all(
50428
+ collectMemoryScopeUserIds(userId).map((scopeUserId) => memoryEngine.list(scopeUserId))
50429
+ )).flat();
50112
50430
  return c.json(sortMemories(memories));
50113
50431
  } catch (err) {
50114
50432
  return c.json({ error: err.message || "Failed to list memories" }, 500);
@@ -50171,16 +50489,27 @@ memoryRoutes.get("/memory/dream", async (c) => {
50171
50489
  if (!userId) return c.json({ error: "userId is required" }, 400);
50172
50490
  try {
50173
50491
  const { dreamStorage, dreamHistoryStorage, dreamStateStorage } = getSharedDeps();
50174
- const [dreamContent, dreamHistory, state] = await Promise.all([
50175
- dreamStorage.get(`dream:latest:${userId}`),
50176
- dreamHistoryStorage.get(`dream:history:${userId}`),
50177
- dreamStateStorage.get(`dream:state:${userId}`)
50178
- ]);
50492
+ const scoped = await Promise.all(collectMemoryScopeUserIds(userId).map(async (scopeUserId) => {
50493
+ const [dreamContent2, dreamHistory2, state2] = await Promise.all([
50494
+ dreamStorage.get(`dream:latest:${scopeUserId}`),
50495
+ dreamHistoryStorage.get(`dream:history:${scopeUserId}`),
50496
+ dreamStateStorage.get(`dream:state:${scopeUserId}`)
50497
+ ]);
50498
+ return { userId: scopeUserId, dreamContent: dreamContent2 || "", dreamHistory: dreamHistory2 || [], state: state2 || null };
50499
+ }));
50500
+ const dreamContent = scoped.filter((item) => item.dreamContent.trim()).map((item) => scoped.length > 1 ? `## ${item.userId}
50501
+ ${item.dreamContent}` : item.dreamContent).join("\n\n");
50502
+ const dreamHistory = scoped.flatMap((item) => item.dreamHistory.map((entry) => ({
50503
+ ...entry,
50504
+ content: scoped.length > 1 ? `[${item.userId}]
50505
+ ${entry.content}` : entry.content
50506
+ }))).sort((a, b) => b.createdAt - a.createdAt);
50507
+ const state = scoped.map((item) => item.state).filter((item) => !!item).sort((a, b) => b.lastActivityAt - a.lastActivityAt)[0] ?? null;
50179
50508
  return c.json({
50180
50509
  userId,
50181
- dreamContent: dreamContent || "",
50182
- dreamHistory: dreamHistory || [],
50183
- state: state || null
50510
+ dreamContent,
50511
+ dreamHistory,
50512
+ state
50184
50513
  });
50185
50514
  } catch (err) {
50186
50515
  return c.json({ error: err.message || "Failed to get dream summary" }, 500);
@@ -50190,7 +50519,9 @@ memoryRoutes.get("/memory/compact", async (c) => {
50190
50519
  const userId = requireUserId(c.req.query("userId"));
50191
50520
  if (!userId) return c.json({ error: "userId is required" }, 400);
50192
50521
  try {
50193
- const summaries = await collectCompactSummaries(userId);
50522
+ const summaries = (await Promise.all(
50523
+ collectMemoryScopeUserIds(userId).map((scopeUserId) => collectCompactSummaries(scopeUserId))
50524
+ )).flat().sort((a, b) => b.createdAt - a.createdAt);
50194
50525
  return c.json({
50195
50526
  userId,
50196
50527
  summaries
@@ -50207,19 +50538,25 @@ memoryRoutes.get("/memory/recall", async (c) => {
50207
50538
  const limit = Math.min(Number(c.req.query("limit")) || 20, 50);
50208
50539
  try {
50209
50540
  const { recallIndexStorage } = getSharedDeps();
50541
+ const scopeUserIds = collectMemoryScopeUserIds(userId);
50210
50542
  if (query) {
50211
- const result = await searchRecallIndex(recallIndexStorage, userId, query, date, limit);
50543
+ const scopedResults = await Promise.all(scopeUserIds.map(
50544
+ (scopeUserId) => searchRecallIndex(recallIndexStorage, scopeUserId, query, date, limit)
50545
+ ));
50546
+ const matches = scopedResults.flatMap((result) => result.matches).sort((a, b) => b.score - a.score).slice(0, limit);
50212
50547
  return c.json({
50213
50548
  userId,
50214
50549
  query,
50215
- total: result.total,
50216
- entries: result.matches.map((match2) => ({
50550
+ total: scopedResults.reduce((sum, result) => sum + result.total, 0),
50551
+ entries: matches.map((match2) => ({
50217
50552
  ...match2.entry,
50218
50553
  score: match2.score
50219
50554
  }))
50220
50555
  });
50221
50556
  }
50222
- const entries = await recallIndexStorage.get(`recall:index:${userId}`) || [];
50557
+ const entries = (await Promise.all(
50558
+ scopeUserIds.map((scopeUserId) => recallIndexStorage.get(`recall:index:${scopeUserId}`))
50559
+ )).flatMap((items) => items ?? []);
50223
50560
  const filtered = date ? entries.filter((entry) => entry.date === date) : entries;
50224
50561
  filtered.sort((a, b) => b.createdAt - a.createdAt);
50225
50562
  return c.json({
@@ -50237,10 +50574,18 @@ memoryRoutes.get("/memory/context", async (c) => {
50237
50574
  if (!userId) return c.json({ error: "userId is required" }, 400);
50238
50575
  try {
50239
50576
  const { memoryEngine, dreamStorage } = getSharedDeps();
50240
- const [memoryContextBlock, dreamContent] = await Promise.all([
50241
- memoryEngine.buildContextBlock(userId),
50242
- dreamStorage.get(`dream:latest:${userId}`)
50243
- ]);
50577
+ const scoped = await Promise.all(collectMemoryScopeUserIds(userId).map(async (scopeUserId) => {
50578
+ const [memoryContextBlock2, dreamContent2] = await Promise.all([
50579
+ memoryEngine.buildContextBlock(scopeUserId),
50580
+ dreamStorage.get(`dream:latest:${scopeUserId}`)
50581
+ ]);
50582
+ return { userId: scopeUserId, memoryContextBlock: memoryContextBlock2, dreamContent: dreamContent2 };
50583
+ }));
50584
+ const memoryContextBlock = scoped.filter((item) => item.memoryContextBlock.trim()).map((item) => scoped.length > 1 ? `<main-manager-context-source userId="${item.userId}">
50585
+ ${item.memoryContextBlock}
50586
+ </main-manager-context-source>` : item.memoryContextBlock).join("\n\n");
50587
+ const dreamContent = scoped.filter((item) => item.dreamContent?.trim()).map((item) => scoped.length > 1 ? `## ${item.userId}
50588
+ ${item.dreamContent}` : item.dreamContent).join("\n\n");
50244
50589
  const dreamContextBlock = dreamContent ? [
50245
50590
  "<memory-context>",
50246
50591
  "\u4EE5\u4E0B\u662F\u4F60\u901A\u8FC7\u505A\u68A6\u4FDD\u7559\u7684\u8DE8\u5929\u8BB0\u5FC6\u3002\u8BB0\u4F4F\u8FD9\u4E9B\u4FE1\u606F\uFF0C\u4F46\u4E0D\u8981\u4E3B\u52A8\u63D0\u53CA\u2014\u2014\u9664\u975E\u7528\u6237\u7684\u8BDD\u9898\u76F8\u5173\u3002",
@@ -50313,7 +50658,7 @@ var systemRoutes = new Hono2();
50313
50658
  var startTime = Date.now();
50314
50659
  systemRoutes.get("/system/info", (c) => {
50315
50660
  return c.json({
50316
- version: true ? "1.8.1" : "unknown",
50661
+ version: true ? "1.8.3" : "unknown",
50317
50662
  uptime: Math.floor((Date.now() - startTime) / 1e3),
50318
50663
  env: process.env.NODE_ENV || "development",
50319
50664
  nodeVersion: process.version
@@ -50414,8 +50759,12 @@ async function main() {
50414
50759
  } else {
50415
50760
  console.log(`[main] Core Channel Gateway \u5DF2\u8DF3\u8FC7\uFF0C\u5F53\u524D\u5916\u90E8\u6D88\u606F\u5165\u53E3\u7531 SaaS \u5C42\u63A5\u7BA1`);
50416
50761
  }
50417
- startScheduler();
50418
- console.log(`[main] \u5B9A\u65F6\u4EFB\u52A1\u8C03\u5EA6\u5668\u5DF2\u542F\u52A8`);
50762
+ if (process.env.DUCLAW_CRON_SCHEDULER === "saas") {
50763
+ console.log(`[main] \u5B9A\u65F6\u4EFB\u52A1\u8C03\u5EA6\u5668\u5DF2\u8DF3\u8FC7\uFF0C\u5F53\u524D\u7531 SaaS \u5C42\u63A5\u7BA1`);
50764
+ } else {
50765
+ startScheduler();
50766
+ console.log(`[main] \u5B9A\u65F6\u4EFB\u52A1\u8C03\u5EA6\u5668\u5DF2\u542F\u52A8`);
50767
+ }
50419
50768
  startMailboxPoller();
50420
50769
  console.log(`[main] \u591A\u667A\u80FD\u4F53\u90AE\u7BB1\u8F6E\u8BE2\u5DF2\u542F\u52A8`);
50421
50770
  const kanbanPort = Number(process.env.KANBAN_PORT || 3e3);