duclaw-cli 1.8.2 → 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.2" : "unknown"}`);
30245
+ console.log(`duclaw-cli v${true ? "1.8.3" : "unknown"}`);
30246
30246
  }
30247
30247
  function getDuclawTemplate() {
30248
30248
  return {
@@ -45312,10 +45312,24 @@ var executeJob = async (job) => {
45312
45312
  const resultFilePath = saveResultToFile(job, result);
45313
45313
  console.log(`[cron\u6267\u884C\u5B8C\u6210] \u5B9A\u65F6\u4EFB\u52A1\u6267\u884C\u7ED3\u679C: ${resultFilePath}`);
45314
45314
  updateJobLastRunTime(job.id);
45315
+ return { ok: true, resultFilePath };
45315
45316
  } catch (error) {
45316
- 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 };
45317
45320
  }
45318
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
+ };
45319
45333
  var executeAgentJob = async (job) => {
45320
45334
  const { registry: registry2, executor } = getCronTools();
45321
45335
  const tools = getAllTools(registry2);
@@ -49998,6 +50012,23 @@ cronRoutes.get("/cron/jobs/:id/history", (c) => {
49998
50012
  return c.json({ error: err.message || "Failed to get job history" }, 500);
49999
50013
  }
50000
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
+ });
50001
50032
 
50002
50033
  // src/server/routes/mailbox.ts
50003
50034
  var mailboxRoutes = new Hono2();
@@ -50276,7 +50307,16 @@ var extractMemoryUserIdsFromKeys = (keys) => {
50276
50307
  };
50277
50308
  var collectCompactSummaries = async (userId) => {
50278
50309
  if (isFileBackedRuntime()) {
50279
- 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);
50280
50320
  }
50281
50321
  const client2 = await getRedisClient();
50282
50322
  const { compactSummaryStorage } = getSharedDeps();
@@ -50291,6 +50331,61 @@ var collectCompactSummaries = async (userId) => {
50291
50331
  }
50292
50332
  return records.sort((a, b) => b.createdAt - a.createdAt);
50293
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
+ };
50294
50389
  var collectUserIds = async () => {
50295
50390
  const userIds = /* @__PURE__ */ new Set();
50296
50391
  const localConversationUserIds = collectLocalConversationUserIds();
@@ -50312,26 +50407,10 @@ var collectUserIds = async () => {
50312
50407
  var memoryRoutes = new Hono2();
50313
50408
  memoryRoutes.get("/memory/users", async (c) => {
50314
50409
  try {
50315
- const { memoryEngine, dreamStorage, dreamStateStorage, topicStorage } = getSharedDeps();
50316
50410
  const { userIds, localConversationUserIds } = await collectUserIds();
50317
- const summaries = await Promise.all(userIds.map(async (userId) => {
50318
- const [memories, dreamContent, dreamState, topics] = await Promise.all([
50319
- memoryEngine.list(userId),
50320
- dreamStorage.get(`dream:latest:${userId}`),
50321
- dreamStateStorage.get(`dream:state:${userId}`),
50322
- topicStorage.get(`topics:${userId}`)
50323
- ]);
50324
- const summary = {
50325
- userId,
50326
- memoryCount: memories.length,
50327
- hasMemory: memories.length > 0,
50328
- hasDream: !!dreamContent,
50329
- hasConversation: (topics?.length ?? 0) > 0 || localConversationUserIds.has(userId),
50330
- lastDreamAt: dreamState?.lastDreamAt ?? null,
50331
- lastActivityAt: dreamState?.lastActivityAt ?? null
50332
- };
50333
- return summary;
50334
- }));
50411
+ const summaries = await Promise.all(userIds.map(
50412
+ (userId) => aggregateMemorySummaries(userId, localConversationUserIds)
50413
+ ));
50335
50414
  summaries.sort(
50336
50415
  (a, b) => (b.lastActivityAt ?? 0) - (a.lastActivityAt ?? 0) || a.userId.localeCompare(b.userId)
50337
50416
  );
@@ -50345,7 +50424,9 @@ memoryRoutes.get("/memory", async (c) => {
50345
50424
  if (!userId) return c.json({ error: "userId is required" }, 400);
50346
50425
  try {
50347
50426
  const { memoryEngine } = getSharedDeps();
50348
- const memories = await memoryEngine.list(userId);
50427
+ const memories = (await Promise.all(
50428
+ collectMemoryScopeUserIds(userId).map((scopeUserId) => memoryEngine.list(scopeUserId))
50429
+ )).flat();
50349
50430
  return c.json(sortMemories(memories));
50350
50431
  } catch (err) {
50351
50432
  return c.json({ error: err.message || "Failed to list memories" }, 500);
@@ -50408,16 +50489,27 @@ memoryRoutes.get("/memory/dream", async (c) => {
50408
50489
  if (!userId) return c.json({ error: "userId is required" }, 400);
50409
50490
  try {
50410
50491
  const { dreamStorage, dreamHistoryStorage, dreamStateStorage } = getSharedDeps();
50411
- const [dreamContent, dreamHistory, state] = await Promise.all([
50412
- dreamStorage.get(`dream:latest:${userId}`),
50413
- dreamHistoryStorage.get(`dream:history:${userId}`),
50414
- dreamStateStorage.get(`dream:state:${userId}`)
50415
- ]);
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;
50416
50508
  return c.json({
50417
50509
  userId,
50418
- dreamContent: dreamContent || "",
50419
- dreamHistory: dreamHistory || [],
50420
- state: state || null
50510
+ dreamContent,
50511
+ dreamHistory,
50512
+ state
50421
50513
  });
50422
50514
  } catch (err) {
50423
50515
  return c.json({ error: err.message || "Failed to get dream summary" }, 500);
@@ -50427,7 +50519,9 @@ memoryRoutes.get("/memory/compact", async (c) => {
50427
50519
  const userId = requireUserId(c.req.query("userId"));
50428
50520
  if (!userId) return c.json({ error: "userId is required" }, 400);
50429
50521
  try {
50430
- 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);
50431
50525
  return c.json({
50432
50526
  userId,
50433
50527
  summaries
@@ -50444,19 +50538,25 @@ memoryRoutes.get("/memory/recall", async (c) => {
50444
50538
  const limit = Math.min(Number(c.req.query("limit")) || 20, 50);
50445
50539
  try {
50446
50540
  const { recallIndexStorage } = getSharedDeps();
50541
+ const scopeUserIds = collectMemoryScopeUserIds(userId);
50447
50542
  if (query) {
50448
- 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);
50449
50547
  return c.json({
50450
50548
  userId,
50451
50549
  query,
50452
- total: result.total,
50453
- entries: result.matches.map((match2) => ({
50550
+ total: scopedResults.reduce((sum, result) => sum + result.total, 0),
50551
+ entries: matches.map((match2) => ({
50454
50552
  ...match2.entry,
50455
50553
  score: match2.score
50456
50554
  }))
50457
50555
  });
50458
50556
  }
50459
- 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 ?? []);
50460
50560
  const filtered = date ? entries.filter((entry) => entry.date === date) : entries;
50461
50561
  filtered.sort((a, b) => b.createdAt - a.createdAt);
50462
50562
  return c.json({
@@ -50474,10 +50574,18 @@ memoryRoutes.get("/memory/context", async (c) => {
50474
50574
  if (!userId) return c.json({ error: "userId is required" }, 400);
50475
50575
  try {
50476
50576
  const { memoryEngine, dreamStorage } = getSharedDeps();
50477
- const [memoryContextBlock, dreamContent] = await Promise.all([
50478
- memoryEngine.buildContextBlock(userId),
50479
- dreamStorage.get(`dream:latest:${userId}`)
50480
- ]);
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");
50481
50589
  const dreamContextBlock = dreamContent ? [
50482
50590
  "<memory-context>",
50483
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",
@@ -50550,7 +50658,7 @@ var systemRoutes = new Hono2();
50550
50658
  var startTime = Date.now();
50551
50659
  systemRoutes.get("/system/info", (c) => {
50552
50660
  return c.json({
50553
- version: true ? "1.8.2" : "unknown",
50661
+ version: true ? "1.8.3" : "unknown",
50554
50662
  uptime: Math.floor((Date.now() - startTime) / 1e3),
50555
50663
  env: process.env.NODE_ENV || "development",
50556
50664
  nodeVersion: process.version
@@ -50651,8 +50759,12 @@ async function main() {
50651
50759
  } else {
50652
50760
  console.log(`[main] Core Channel Gateway \u5DF2\u8DF3\u8FC7\uFF0C\u5F53\u524D\u5916\u90E8\u6D88\u606F\u5165\u53E3\u7531 SaaS \u5C42\u63A5\u7BA1`);
50653
50761
  }
50654
- startScheduler();
50655
- 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
+ }
50656
50768
  startMailboxPoller();
50657
50769
  console.log(`[main] \u591A\u667A\u80FD\u4F53\u90AE\u7BB1\u8F6E\u8BE2\u5DF2\u542F\u52A8`);
50658
50770
  const kanbanPort = Number(process.env.KANBAN_PORT || 3e3);