@downcity/plugins 1.0.60 → 1.0.61

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.
Files changed (91) hide show
  1. package/bin/BuiltinPlugins.d.ts +15 -0
  2. package/bin/BuiltinPlugins.d.ts.map +1 -1
  3. package/bin/BuiltinPlugins.js +7 -1
  4. package/bin/BuiltinPlugins.js.map +1 -1
  5. package/bin/index.d.ts +6 -0
  6. package/bin/index.d.ts.map +1 -1
  7. package/bin/index.js +3 -0
  8. package/bin/index.js.map +1 -1
  9. package/bin/memory/Action.d.ts +15 -10
  10. package/bin/memory/Action.d.ts.map +1 -1
  11. package/bin/memory/Action.js +233 -16
  12. package/bin/memory/Action.js.map +1 -1
  13. package/bin/memory/MemoryPlugin.d.ts +10 -4
  14. package/bin/memory/MemoryPlugin.d.ts.map +1 -1
  15. package/bin/memory/MemoryPlugin.js +79 -37
  16. package/bin/memory/MemoryPlugin.js.map +1 -1
  17. package/bin/memory/runtime/Search.d.ts +1 -1
  18. package/bin/memory/runtime/Search.d.ts.map +1 -1
  19. package/bin/memory/runtime/Search.js +11 -7
  20. package/bin/memory/runtime/Search.js.map +1 -1
  21. package/bin/memory/runtime/Store.d.ts +8 -23
  22. package/bin/memory/runtime/Store.d.ts.map +1 -1
  23. package/bin/memory/runtime/Store.js +28 -43
  24. package/bin/memory/runtime/Store.js.map +1 -1
  25. package/bin/memory/runtime/SystemProvider.d.ts +4 -8
  26. package/bin/memory/runtime/SystemProvider.d.ts.map +1 -1
  27. package/bin/memory/runtime/SystemProvider.js +55 -62
  28. package/bin/memory/runtime/SystemProvider.js.map +1 -1
  29. package/bin/memory/runtime/Writer.d.ts +48 -10
  30. package/bin/memory/runtime/Writer.d.ts.map +1 -1
  31. package/bin/memory/runtime/Writer.js +197 -60
  32. package/bin/memory/runtime/Writer.js.map +1 -1
  33. package/bin/memory/types/Memory.d.ts +222 -33
  34. package/bin/memory/types/Memory.d.ts.map +1 -1
  35. package/bin/memory/types/Memory.js +4 -3
  36. package/bin/memory/types/Memory.js.map +1 -1
  37. package/bin/shell/ShellPlugin.d.ts +2 -1
  38. package/bin/shell/ShellPlugin.d.ts.map +1 -1
  39. package/bin/shell/ShellPlugin.js +3 -3
  40. package/bin/shell/ShellPlugin.js.map +1 -1
  41. package/bin/shell/ShellRuntimeTypes.d.ts +7 -2
  42. package/bin/shell/ShellRuntimeTypes.d.ts.map +1 -1
  43. package/bin/shell/runtime/ShellActionRuntime.d.ts.map +1 -1
  44. package/bin/shell/runtime/ShellActionRuntime.js +6 -6
  45. package/bin/shell/runtime/ShellActionRuntime.js.map +1 -1
  46. package/bin/shell/runtime/ShellActionRuntimeSupport.d.ts +14 -5
  47. package/bin/shell/runtime/ShellActionRuntimeSupport.d.ts.map +1 -1
  48. package/bin/shell/runtime/ShellActionRuntimeSupport.js +58 -22
  49. package/bin/shell/runtime/ShellActionRuntimeSupport.js.map +1 -1
  50. package/bin/shell/runtime/ShellProcessEvents.js +3 -3
  51. package/bin/shell/runtime/ShellProcessEvents.js.map +1 -1
  52. package/bin/shell/types/ShellPluginOptions.d.ts +95 -0
  53. package/bin/shell/types/ShellPluginOptions.d.ts.map +1 -0
  54. package/bin/shell/types/ShellPluginOptions.js +10 -0
  55. package/bin/shell/types/ShellPluginOptions.js.map +1 -0
  56. package/bin/task/Scheduler.d.ts +8 -0
  57. package/bin/task/Scheduler.d.ts.map +1 -1
  58. package/bin/task/Scheduler.js +7 -9
  59. package/bin/task/Scheduler.js.map +1 -1
  60. package/bin/task/TaskPlugin.d.ts +18 -1
  61. package/bin/task/TaskPlugin.d.ts.map +1 -1
  62. package/bin/task/TaskPlugin.js +23 -1
  63. package/bin/task/TaskPlugin.js.map +1 -1
  64. package/bin/task/types/TaskPluginOptions.d.ts +22 -0
  65. package/bin/task/types/TaskPluginOptions.d.ts.map +1 -0
  66. package/bin/task/types/TaskPluginOptions.js +9 -0
  67. package/bin/task/types/TaskPluginOptions.js.map +1 -0
  68. package/package.json +2 -2
  69. package/src/BuiltinPlugins.ts +27 -1
  70. package/src/index.ts +35 -0
  71. package/src/memory/Action.ts +292 -25
  72. package/src/memory/MemoryPlugin.ts +82 -40
  73. package/src/memory/runtime/Search.ts +16 -9
  74. package/src/memory/runtime/Store.ts +52 -49
  75. package/src/memory/runtime/SystemProvider.ts +55 -69
  76. package/src/memory/runtime/Writer.ts +262 -81
  77. package/src/memory/types/Memory.ts +296 -35
  78. package/src/shell/ShellPlugin.ts +4 -3
  79. package/src/shell/ShellRuntimeTypes.ts +7 -2
  80. package/src/shell/runtime/ShellActionRuntime.ts +18 -9
  81. package/src/shell/runtime/ShellActionRuntimeSupport.ts +106 -21
  82. package/src/shell/runtime/ShellProcessEvents.ts +3 -3
  83. package/src/shell/types/ShellPluginOptions.ts +112 -0
  84. package/src/task/Scheduler.ts +15 -9
  85. package/src/task/TaskPlugin.ts +27 -1
  86. package/src/task/types/TaskPluginOptions.ts +22 -0
  87. package/bin/memory/runtime/Flush.d.ts +0 -15
  88. package/bin/memory/runtime/Flush.d.ts.map +0 -1
  89. package/bin/memory/runtime/Flush.js +0 -63
  90. package/bin/memory/runtime/Flush.js.map +0 -1
  91. package/src/memory/runtime/Flush.ts +0 -83
@@ -13,6 +13,10 @@ import type {
13
13
  ShellPluginState,
14
14
  ShellSessionRuntimeState,
15
15
  } from "@/shell/ShellRuntimeTypes.js";
16
+ import type {
17
+ ResolvedShellPluginOptions,
18
+ ShellPluginOptions,
19
+ } from "@/shell/types/ShellPluginOptions.js";
16
20
  import type {
17
21
  ShellQueryRequest,
18
22
  ShellSessionSnapshot,
@@ -32,35 +36,104 @@ export {
32
36
  createOutputChunk,
33
37
  } from "./ShellActionResponse.js";
34
38
 
35
- const MAX_ACTIVE_SHELLS = 64;
36
- const SESSION_CLEANUP_DELAY_MS = 10 * 60 * 1000;
37
- const MAX_IN_MEMORY_OUTPUT_CHARS = 1_000_000;
38
- const MIN_WAIT_MS = 50;
39
- const MAX_WAIT_MS = 30_000;
40
- const OUTPUT_PREVIEW_CHARS = 280;
39
+ const DEFAULT_SHELL_PLUGIN_OPTIONS: ResolvedShellPluginOptions = {
40
+ maxActiveShells: 64,
41
+ cleanupDelayMs: 10 * 60 * 1000,
42
+ maxInMemoryOutputChars: 1_000_000,
43
+ outputPreviewChars: 280,
44
+ minWaitMs: 50,
45
+ maxWaitMs: 30_000,
46
+ defaultInlineWaitMs: 1_200,
47
+ defaultWaitTimeoutMs: 10_000,
48
+ defaultExecTimeoutMs: 60_000,
49
+ };
41
50
 
42
51
  /**
43
52
  * shell.start 默认内联等待时间。
44
53
  */
45
- export const DEFAULT_INLINE_WAIT_MS = 1_200;
54
+ export const DEFAULT_INLINE_WAIT_MS = DEFAULT_SHELL_PLUGIN_OPTIONS.defaultInlineWaitMs;
46
55
 
47
56
  /**
48
57
  * shell.wait 默认等待超时。
49
58
  */
50
- export const DEFAULT_WAIT_TIMEOUT_MS = 10_000;
59
+ export const DEFAULT_WAIT_TIMEOUT_MS = DEFAULT_SHELL_PLUGIN_OPTIONS.defaultWaitTimeoutMs;
51
60
 
52
61
  /**
53
62
  * shell.exec 默认总超时。
54
63
  */
55
- export const DEFAULT_EXEC_TIMEOUT_MS = 60_000;
64
+ export const DEFAULT_EXEC_TIMEOUT_MS = DEFAULT_SHELL_PLUGIN_OPTIONS.defaultExecTimeoutMs;
65
+
66
+ function readPositiveInteger(
67
+ value: number | undefined,
68
+ fallback: number,
69
+ ): number {
70
+ if (typeof value !== "number" || !Number.isFinite(value)) {
71
+ return fallback;
72
+ }
73
+ return Math.max(1, Math.floor(value));
74
+ }
75
+
76
+ /**
77
+ * 归一化 ShellPlugin 可选运行参数。
78
+ */
79
+ export function resolveShellPluginOptions(
80
+ options: ShellPluginOptions = {},
81
+ ): ResolvedShellPluginOptions {
82
+ const minWaitMs = readPositiveInteger(
83
+ options.minWaitMs,
84
+ DEFAULT_SHELL_PLUGIN_OPTIONS.minWaitMs,
85
+ );
86
+ const maxWaitMs = Math.max(
87
+ minWaitMs,
88
+ readPositiveInteger(
89
+ options.maxWaitMs,
90
+ DEFAULT_SHELL_PLUGIN_OPTIONS.maxWaitMs,
91
+ ),
92
+ );
93
+ return {
94
+ maxActiveShells: readPositiveInteger(
95
+ options.maxActiveShells,
96
+ DEFAULT_SHELL_PLUGIN_OPTIONS.maxActiveShells,
97
+ ),
98
+ cleanupDelayMs: readPositiveInteger(
99
+ options.cleanupDelayMs,
100
+ DEFAULT_SHELL_PLUGIN_OPTIONS.cleanupDelayMs,
101
+ ),
102
+ maxInMemoryOutputChars: readPositiveInteger(
103
+ options.maxInMemoryOutputChars,
104
+ DEFAULT_SHELL_PLUGIN_OPTIONS.maxInMemoryOutputChars,
105
+ ),
106
+ outputPreviewChars: readPositiveInteger(
107
+ options.outputPreviewChars,
108
+ DEFAULT_SHELL_PLUGIN_OPTIONS.outputPreviewChars,
109
+ ),
110
+ minWaitMs,
111
+ maxWaitMs,
112
+ defaultInlineWaitMs: readPositiveInteger(
113
+ options.defaultInlineWaitMs,
114
+ DEFAULT_SHELL_PLUGIN_OPTIONS.defaultInlineWaitMs,
115
+ ),
116
+ defaultWaitTimeoutMs: readPositiveInteger(
117
+ options.defaultWaitTimeoutMs,
118
+ DEFAULT_SHELL_PLUGIN_OPTIONS.defaultWaitTimeoutMs,
119
+ ),
120
+ defaultExecTimeoutMs: readPositiveInteger(
121
+ options.defaultExecTimeoutMs,
122
+ DEFAULT_SHELL_PLUGIN_OPTIONS.defaultExecTimeoutMs,
123
+ ),
124
+ };
125
+ }
56
126
 
57
127
  /**
58
128
  * 创建 shell plugin runtime 初始状态。
59
129
  */
60
- export function createShellPluginState(): ShellPluginState {
130
+ export function createShellPluginState(
131
+ options: ShellPluginOptions = {},
132
+ ): ShellPluginState {
61
133
  return {
134
+ options: resolveShellPluginOptions(options),
62
135
  sessions: new Map<string, ShellSessionRuntimeState>(),
63
- boundRuntime: null,
136
+ context: null,
64
137
  };
65
138
  }
66
139
 
@@ -75,11 +148,22 @@ export function nowMs(): number {
75
148
  * 归一化 wait/timeout 参数。
76
149
  */
77
150
  export function clampWaitMs(value: number | undefined, fallback: number): number {
151
+ return clampWaitMsWithOptions(DEFAULT_SHELL_PLUGIN_OPTIONS, value, fallback);
152
+ }
153
+
154
+ /**
155
+ * 结合 ShellPlugin options 归一化 wait/timeout 参数。
156
+ */
157
+ export function clampWaitMsWithOptions(
158
+ options: ResolvedShellPluginOptions,
159
+ value: number | undefined,
160
+ fallback: number,
161
+ ): number {
78
162
  const raw =
79
163
  typeof value === "number" && Number.isFinite(value)
80
164
  ? Math.floor(value)
81
165
  : fallback;
82
- return Math.min(MAX_WAIT_MS, Math.max(MIN_WAIT_MS, raw));
166
+ return Math.min(options.maxWaitMs, Math.max(options.minWaitMs, raw));
83
167
  }
84
168
 
85
169
  function normalizeOutputChunk(raw: string): string {
@@ -223,6 +307,7 @@ export async function updateSessionSnapshot(
223
307
  * 追加 shell 输出并同步更新快照。
224
308
  */
225
309
  export async function appendSessionOutput(
310
+ state: ShellPluginState,
226
311
  session: ShellSessionRuntimeState,
227
312
  raw: string,
228
313
  ): Promise<void> {
@@ -230,8 +315,8 @@ export async function appendSessionOutput(
230
315
  if (!text) return;
231
316
 
232
317
  session.outputText += text;
233
- if (session.outputText.length > MAX_IN_MEMORY_OUTPUT_CHARS) {
234
- const overflow = session.outputText.length - MAX_IN_MEMORY_OUTPUT_CHARS;
318
+ if (session.outputText.length > state.options.maxInMemoryOutputChars) {
319
+ const overflow = session.outputText.length - state.options.maxInMemoryOutputChars;
235
320
  session.outputText = session.outputText.slice(overflow);
236
321
  session.snapshot.droppedChars += overflow;
237
322
  }
@@ -239,7 +324,7 @@ export async function appendSessionOutput(
239
324
  session.snapshot.outputChars += text.length;
240
325
  session.snapshot.lastOutputAt = nowMs();
241
326
  session.snapshot.lastOutputPreview = session.outputText
242
- .slice(-OUTPUT_PREVIEW_CHARS)
327
+ .slice(-state.options.outputPreviewChars)
243
328
  .trim();
244
329
  session.snapshot.externalRefs = extractExternalRefsFromText(
245
330
  text,
@@ -260,7 +345,7 @@ export function scheduleCleanup(state: ShellPluginState, shellId: string): void
260
345
  const current = state.sessions.get(shellId);
261
346
  if (!current) return;
262
347
  state.sessions.delete(shellId);
263
- }, SESSION_CLEANUP_DELAY_MS);
348
+ }, state.options.cleanupDelayMs);
264
349
  if (typeof session.cleanupTimer.unref === "function") {
265
350
  session.cleanupTimer.unref();
266
351
  }
@@ -270,15 +355,15 @@ export function scheduleCleanup(state: ShellPluginState, shellId: string): void
270
355
  * 控制 in-memory shell session 容量。
271
356
  */
272
357
  export function ensureCapacity(state: ShellPluginState): void {
273
- if (state.sessions.size < MAX_ACTIVE_SHELLS) return;
358
+ if (state.sessions.size < state.options.maxActiveShells) return;
274
359
  const removable = Array.from(state.sessions.values())
275
360
  .filter((item) => item.snapshot.status !== "running" && item.snapshot.status !== "starting")
276
361
  .sort((a, b) => a.snapshot.updatedAt - b.snapshot.updatedAt);
277
362
  for (const item of removable) {
278
- if (state.sessions.size < MAX_ACTIVE_SHELLS) break;
363
+ if (state.sessions.size < state.options.maxActiveShells) break;
279
364
  state.sessions.delete(item.snapshot.shellId);
280
365
  }
281
- if (state.sessions.size >= MAX_ACTIVE_SHELLS) {
366
+ if (state.sessions.size >= state.options.maxActiveShells) {
282
367
  throw new Error(
283
368
  `Too many active shell sessions (${state.sessions.size}). Please close or wait older sessions first.`,
284
369
  );
@@ -377,9 +462,9 @@ export async function finalizeExit(
377
462
  if (
378
463
  session.snapshot.autoNotifyOnExit &&
379
464
  session.snapshot.notificationSent === false &&
380
- state.boundRuntime
465
+ state.context
381
466
  ) {
382
- await emitChatCompletionEvent(state.boundRuntime, session.snapshot);
467
+ await emitChatCompletionEvent(state.context, session.snapshot);
383
468
  session.snapshot.notificationSent = true;
384
469
  await persistSnapshot(session);
385
470
  }
@@ -44,13 +44,13 @@ export function attachShellProcessEventHandlers(params: {
44
44
  const { state, session } = params;
45
45
  const { child } = session;
46
46
  child.stdout.on("data", (chunk: string | Buffer) => {
47
- void appendSessionOutput(session, String(chunk ?? "")).catch(() => undefined);
47
+ void appendSessionOutput(state, session, String(chunk ?? "")).catch(() => undefined);
48
48
  });
49
49
  child.stderr.on("data", (chunk: string | Buffer) => {
50
- void appendSessionOutput(session, String(chunk ?? "")).catch(() => undefined);
50
+ void appendSessionOutput(state, session, String(chunk ?? "")).catch(() => undefined);
51
51
  });
52
52
  child.on("error", (error: Error) => {
53
- void appendSessionOutput(session, `\n[process error] ${String(error)}\n`).catch(
53
+ void appendSessionOutput(state, session, `\n[process error] ${String(error)}\n`).catch(
54
54
  () => undefined,
55
55
  );
56
56
  void finalizeExitAfterOutputDrain(state, session, -1).catch(() => undefined);
@@ -0,0 +1,112 @@
1
+ /**
2
+ * ShellPlugin constructor 参数类型。
3
+ *
4
+ * 关键点(中文)
5
+ * - 所有字段都是可选 runtime 调参。
6
+ * - 不传参数时保持 ShellPlugin 既有硬编码默认行为。
7
+ * - 这些参数只影响 shell plugin runtime,不改变 sandbox 权限模型。
8
+ */
9
+
10
+ /**
11
+ * ShellPlugin 可选运行参数。
12
+ */
13
+ export interface ShellPluginOptions {
14
+ /**
15
+ * 最大 in-memory shell session 数量。
16
+ *
17
+ * 超过该数量时,runtime 会优先清理已经结束的旧 session;如果仍然超限则拒绝启动新 shell。
18
+ */
19
+ maxActiveShells?: number;
20
+
21
+ /**
22
+ * 终态 shell session 在内存中保留多久后自动清理,单位毫秒。
23
+ */
24
+ cleanupDelayMs?: number;
25
+
26
+ /**
27
+ * 单个 shell session 在内存中保留的最大输出字符数。
28
+ *
29
+ * 超出的历史输出仍会写入持久化输出文件,但内存快照只保留尾部内容。
30
+ */
31
+ maxInMemoryOutputChars?: number;
32
+
33
+ /**
34
+ * shell 输出预览保留的最大字符数。
35
+ */
36
+ outputPreviewChars?: number;
37
+
38
+ /**
39
+ * wait/timeout 参数允许的最小毫秒数。
40
+ */
41
+ minWaitMs?: number;
42
+
43
+ /**
44
+ * wait/timeout 参数允许的最大毫秒数。
45
+ */
46
+ maxWaitMs?: number;
47
+
48
+ /**
49
+ * `shell.start` 默认内联等待时间,单位毫秒。
50
+ */
51
+ defaultInlineWaitMs?: number;
52
+
53
+ /**
54
+ * `shell.wait` 默认等待超时,单位毫秒。
55
+ */
56
+ defaultWaitTimeoutMs?: number;
57
+
58
+ /**
59
+ * `shell.exec` 默认总超时,单位毫秒。
60
+ */
61
+ defaultExecTimeoutMs?: number;
62
+ }
63
+
64
+ /**
65
+ * ShellPlugin 归一化后的运行参数。
66
+ */
67
+ export interface ResolvedShellPluginOptions {
68
+ /**
69
+ * 最大 in-memory shell session 数量。
70
+ */
71
+ maxActiveShells: number;
72
+
73
+ /**
74
+ * 终态 shell session 在内存中保留多久后自动清理,单位毫秒。
75
+ */
76
+ cleanupDelayMs: number;
77
+
78
+ /**
79
+ * 单个 shell session 在内存中保留的最大输出字符数。
80
+ */
81
+ maxInMemoryOutputChars: number;
82
+
83
+ /**
84
+ * shell 输出预览保留的最大字符数。
85
+ */
86
+ outputPreviewChars: number;
87
+
88
+ /**
89
+ * wait/timeout 参数允许的最小毫秒数。
90
+ */
91
+ minWaitMs: number;
92
+
93
+ /**
94
+ * wait/timeout 参数允许的最大毫秒数。
95
+ */
96
+ maxWaitMs: number;
97
+
98
+ /**
99
+ * `shell.start` 默认内联等待时间,单位毫秒。
100
+ */
101
+ defaultInlineWaitMs: number;
102
+
103
+ /**
104
+ * `shell.wait` 默认等待超时,单位毫秒。
105
+ */
106
+ defaultWaitTimeoutMs: number;
107
+
108
+ /**
109
+ * `shell.exec` 默认总超时,单位毫秒。
110
+ */
111
+ defaultExecTimeoutMs: number;
112
+ }
@@ -15,7 +15,6 @@ import {
15
15
  import { listTasks, readTask, writeTask } from "./runtime/Store.js";
16
16
  import { runTaskNow } from "./runtime/Runner.js";
17
17
  import { TaskCronEngine } from "./types/Cron.js";
18
- import { resolveRuntimeTimezone } from "@downcity/agent/internal/utils/Time.js";
19
18
 
20
19
  const TASK_LOG_PREFIX = "[TASK]";
21
20
 
@@ -26,13 +25,20 @@ function formatTaskLogMessage(message: string): string {
26
25
  export async function registerTaskCronJobs(params: {
27
26
  context: AgentContext;
28
27
  engine: TaskCronEngine;
28
+ /**
29
+ * cron 表达式使用的 IANA 时区。
30
+ */
31
+ timezone: string;
32
+ /**
33
+ * 当前 TaskPlugin 实例持有的运行中 task 锁。
34
+ */
35
+ runningTaskIds: Set<string>;
29
36
  }): Promise<{ tasksFound: number; jobsScheduled: number }> {
30
37
  const context = params.context;
31
38
  const logger = context.logger;
32
39
  const tasks = await listTasks(context.rootPath);
33
- const runtimeTimezone = resolveRuntimeTimezone();
40
+ const runtimeTimezone = params.timezone;
34
41
 
35
- const runningByTaskId = new Set<string>();
36
42
  let jobsScheduled = 0;
37
43
 
38
44
  for (const item of tasks) {
@@ -59,7 +65,7 @@ export async function registerTaskCronJobs(params: {
59
65
  if (!taskId) return;
60
66
 
61
67
  // 关键点(中文):同一 taskId 串行;重叠触发时跳过,避免并发执行污染 run 目录。
62
- if (runningByTaskId.has(taskId)) {
68
+ if (params.runningTaskIds.has(taskId)) {
63
69
  void logger.log("warn", formatTaskLogMessage("Task skipped (already running)"), {
64
70
  taskId,
65
71
  via: "cron",
@@ -67,7 +73,7 @@ export async function registerTaskCronJobs(params: {
67
73
  return;
68
74
  }
69
75
 
70
- runningByTaskId.add(taskId);
76
+ params.runningTaskIds.add(taskId);
71
77
  try {
72
78
  // 关键点(中文):触发瞬间复查最新 task.md,避免 status/when 变更后仍沿用旧注册状态。
73
79
  const latest = await readTask({
@@ -110,7 +116,7 @@ export async function registerTaskCronJobs(params: {
110
116
  error: String(error),
111
117
  });
112
118
  } finally {
113
- runningByTaskId.delete(taskId);
119
+ params.runningTaskIds.delete(taskId);
114
120
  }
115
121
  },
116
122
  });
@@ -147,7 +153,7 @@ export async function registerTaskCronJobs(params: {
147
153
  if (Date.now() < plannedTimeMs) return;
148
154
 
149
155
  // 关键点(中文):同一 taskId 串行;重叠触发时跳过,避免并发执行污染 run 目录。
150
- if (runningByTaskId.has(taskId)) {
156
+ if (params.runningTaskIds.has(taskId)) {
151
157
  void logger.log("warn", formatTaskLogMessage("Task skipped (already running)"), {
152
158
  taskId,
153
159
  via: "time",
@@ -156,7 +162,7 @@ export async function registerTaskCronJobs(params: {
156
162
  }
157
163
 
158
164
  let shouldDeactivateOneShot = false;
159
- runningByTaskId.add(taskId);
165
+ params.runningTaskIds.add(taskId);
160
166
  try {
161
167
  const latest = await readTask({
162
168
  taskId,
@@ -224,7 +230,7 @@ export async function registerTaskCronJobs(params: {
224
230
  });
225
231
  }
226
232
  }
227
- runningByTaskId.delete(taskId);
233
+ params.runningTaskIds.delete(taskId);
228
234
  }
229
235
  },
230
236
  });
@@ -14,6 +14,7 @@ import type {
14
14
  TaskCronRegisterResult,
15
15
  TaskSchedulerReloadResult,
16
16
  } from "@/task/types/TaskPluginTypes.js";
17
+ import type { TaskPluginOptions } from "@/task/types/TaskPluginOptions.js";
17
18
  import { TaskCronTriggerEngine } from "@/task/runtime/CronTrigger.js";
18
19
  import { registerTaskCronJobs } from "@/task/Scheduler.js";
19
20
  import {
@@ -23,6 +24,7 @@ import {
23
24
  reloadTaskSchedulerAfterMutation,
24
25
  } from "@/task/runtime/TaskActionExecution.js";
25
26
  import { TASK_PLUGIN_PROMPT } from "@/task/runtime/TaskPluginSystem.js";
27
+ import { resolveRuntimeTimezone } from "@downcity/agent/internal/utils/Time.js";
26
28
 
27
29
  const TASK_LOG_PREFIX = "[TASK]";
28
30
 
@@ -49,6 +51,11 @@ export class TaskPlugin extends BasePlugin {
49
51
  */
50
52
  readonly actions: PluginActions;
51
53
 
54
+ /**
55
+ * 当前实例持有的显式配置。
56
+ */
57
+ public readonly options: TaskPluginOptions;
58
+
52
59
  /**
53
60
  * 当前实例持有的 cron engine。
54
61
  *
@@ -58,8 +65,18 @@ export class TaskPlugin extends BasePlugin {
58
65
  */
59
66
  public cronEngine: TaskCronTriggerEngine | null = null;
60
67
 
61
- constructor() {
68
+ /**
69
+ * 当前实例持有的运行中 task 锁。
70
+ *
71
+ * 关键点(中文)
72
+ * - scheduler reload 会替换 cron engine。
73
+ * - 锁必须挂在 plugin 实例上,避免 reload 后同一 task 被新旧调度器并发触发。
74
+ */
75
+ private readonly runningTaskIds = new Set<string>();
76
+
77
+ constructor(options?: TaskPluginOptions) {
62
78
  super();
79
+ this.options = options || {};
63
80
 
64
81
  this.actions = createTaskPluginActions({
65
82
  reloadSchedulerAfterMutation: async (params) =>
@@ -115,6 +132,8 @@ export class TaskPlugin extends BasePlugin {
115
132
  const registerResult = await registerTaskCronJobs({
116
133
  context,
117
134
  engine,
135
+ timezone: this.resolveTimezone(),
136
+ runningTaskIds: this.runningTaskIds,
118
137
  });
119
138
  await engine.start();
120
139
  this.cronEngine = engine;
@@ -163,4 +182,11 @@ export class TaskPlugin extends BasePlugin {
163
182
  reloadScheduler: async (context) => this.restartCronRuntime(context),
164
183
  });
165
184
  }
185
+
186
+ /**
187
+ * 解析当前 task cron 使用的时区。
188
+ */
189
+ private resolveTimezone(): string {
190
+ return String(this.options.timezone || "").trim() || resolveRuntimeTimezone();
191
+ }
166
192
  }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * TaskPluginOptions:TaskPlugin 构造参数。
3
+ *
4
+ * 关键点(中文)
5
+ * - TaskPlugin 本身就是定时任务 runtime,不提供 enabled 开关。
6
+ * - 只暴露用户能直接理解的时区配置,内部调度实现细节不进入 constructor。
7
+ */
8
+
9
+ /**
10
+ * TaskPlugin 构造参数。
11
+ */
12
+ export interface TaskPluginOptions {
13
+ /**
14
+ * cron task 使用的 IANA 时区。
15
+ *
16
+ * 说明(中文)
17
+ * - 例如 `Asia/Shanghai`、`America/Los_Angeles`。
18
+ * - 省略时使用当前运行机器的本机时区。
19
+ * - `time:<ISO8601-with-timezone>` 一次性任务以 ISO 字符串自身的 offset 为准,这里的时区主要影响 cron 表达式。
20
+ */
21
+ timezone?: string;
22
+ }
@@ -1,15 +0,0 @@
1
- /**
2
- * Memory Flush(手动刷写)。
3
- *
4
- * 关键点(中文)
5
- * - 首版使用确定性规则,不依赖额外模型调用。
6
- * - 仅提取最近消息的可读文本并落盘到 daily 记忆。
7
- */
8
- import type { AgentContext } from "@downcity/agent/internal/types/runtime/agent/AgentContext.js";
9
- import type { MemoryFlushPayload, MemoryFlushResponse } from "../../memory/types/Memory.js";
10
- import type { MemoryRuntimeState } from "./Store.js";
11
- /**
12
- * 把当前会话最近消息刷写到 daily 记忆。
13
- */
14
- export declare function flushMemory(context: AgentContext, state: MemoryRuntimeState, payload: MemoryFlushPayload): Promise<MemoryFlushResponse>;
15
- //# sourceMappingURL=Flush.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"Flush.d.ts","sourceRoot":"","sources":["../../../src/memory/runtime/Flush.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,8DAA8D,CAAC;AACjG,OAAO,KAAK,EACV,kBAAkB,EAClB,mBAAmB,EACpB,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AA0BrD;;GAEG;AACH,wBAAsB,WAAW,CAC/B,OAAO,EAAE,YAAY,EACrB,KAAK,EAAE,kBAAkB,EACzB,OAAO,EAAE,kBAAkB,GAC1B,OAAO,CAAC,mBAAmB,CAAC,CAkC9B"}
@@ -1,63 +0,0 @@
1
- /**
2
- * Memory Flush(手动刷写)。
3
- *
4
- * 关键点(中文)
5
- * - 首版使用确定性规则,不依赖额外模型调用。
6
- * - 仅提取最近消息的可读文本并落盘到 daily 记忆。
7
- */
8
- import { isTextUIPart } from "ai";
9
- import { storeMemory } from "./Writer.js";
10
- function toUiParts(message) {
11
- return Array.isArray(message?.parts) ? message.parts : [];
12
- }
13
- function extractReadableLine(message) {
14
- const role = String(message.role || "").toLowerCase() === "user" ? "User" : "Assistant";
15
- const text = toUiParts(message)
16
- .filter(isTextUIPart)
17
- .map((part) => String(part.text || "").trim())
18
- .filter(Boolean)
19
- .join("\n")
20
- .trim();
21
- if (!text) {
22
- return "";
23
- }
24
- return `${role}: ${text}`;
25
- }
26
- /**
27
- * 把当前会话最近消息刷写到 daily 记忆。
28
- */
29
- export async function flushMemory(context, state, payload) {
30
- const sessionId = String(payload.sessionId || "").trim();
31
- if (!sessionId) {
32
- throw new Error("sessionId is required");
33
- }
34
- const maxMessages = Number.isFinite(payload.maxMessages)
35
- ? Math.max(1, Math.floor(payload.maxMessages))
36
- : 30;
37
- const historyStore = context.session.get(sessionId).getHistoryStore();
38
- const total = await historyStore.size();
39
- const start = Math.max(0, total - maxMessages);
40
- const messages = await historyStore.slice(start, total);
41
- const lines = messages
42
- .map((msg) => extractReadableLine(msg))
43
- .filter((line) => line.length > 0);
44
- const summary = lines.length > 0
45
- ? lines.join("\n\n")
46
- : "本次 flush 未找到可写入的用户/助手文本内容。";
47
- const content = [
48
- `Flush Session: ${sessionId}`,
49
- `Window: ${start}-${Math.max(start, total - 1)}`,
50
- "",
51
- summary,
52
- ].join("\n");
53
- const saved = await storeMemory(context, state, {
54
- content,
55
- target: "daily",
56
- });
57
- return {
58
- path: saved.path,
59
- messageCount: lines.length,
60
- writtenChars: saved.writtenChars,
61
- };
62
- }
63
- //# sourceMappingURL=Flush.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"Flush.js","sourceRoot":"","sources":["../../../src/memory/runtime/Flush.ts"],"names":[],"mappings":"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"}