@zhushanwen/pi-scheduler 0.3.1 → 0.3.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhushanwen/pi-scheduler",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
4
4
  "type": "module",
5
5
  "main": "index.ts",
6
6
  "pi": {
@@ -21,7 +21,7 @@ import { describe, expect, it, vi } from 'vitest'
21
21
  // 会触碰真实用户 FS(~/.pi/agent/scheduler/root/test/scheduler.json 的 renameSync/existsSync/
22
22
  // unlinkSync;该目录是活跃数据目录,一旦路径存在会 rename+unlink 真实用户数据且结果不确定)。
23
23
  // mock 掉 importer 模块:session_start 装配路径仍被调用(vi.fn 记录调用),FS 副作用为零;
24
- // 装配时序由 tools/verify-scheduler-e2e.cjs 的 S10/S12/S17 真实环境覆盖。
24
+ // 装配时序由 scripts/verify-scheduler-e2e.cjs 的 S10/S12/S17 真实环境覆盖。
25
25
  // mock 返回 vi.fn() 作为延迟删除 cleanup(MF-1:turn_end / session_shutdown 装配链路可测)。
26
26
  vi.mock('../importer.js', () => ({ importLegacyStore: vi.fn(() => vi.fn()) }))
27
27
 
@@ -12,6 +12,9 @@ describe('SchedulerService', () => {
12
12
 
13
13
  beforeEach(() => {
14
14
  backend = new MockSchedulerBackend()
15
+ // 固定时间避免 clock-boundary flake:formatRelativeTime 内部读 Date.now(),
16
+ // 两次读之间的延迟可能导致 "in 1h" 变成 "in 59m"。
17
+ backend.nowValue = Date.now()
15
18
  service = new SchedulerService(new SchedulerRuntime(backend, mockCtx), () => backend.now())
16
19
  })
17
20
 
package/src/service.ts CHANGED
@@ -71,19 +71,21 @@ export class SchedulerService {
71
71
  }
72
72
 
73
73
  const count = task.kind === 'once' ? 1 : PREVIEW_RUN_COUNT
74
- const nextRuns = await computeNextRuns(task.schedule, this.now(), count)
74
+ // 消息内相对时间与 nextRuns 必须同基准:分开读时钟会在整点边界漂移(in 1h in 59m)
75
+ const now = this.now()
76
+ const nextRuns = await computeNextRuns(task.schedule, now, count)
75
77
  // once 单行内联回显(只执行 1 次,编号列表会误导);recurring 保持 5 行编号列表
76
78
  const runPreview =
77
79
  task.kind === 'once'
78
- ? `Next run: ${formatRelativeTime(nextRuns[0]!)}`
80
+ ? `Next run: ${formatRelativeTime(nextRuns[0]!, now)}`
79
81
  : [
80
82
  'Next 5 runs:',
81
- ...nextRuns.map((t, i) => ` ${i + 1}. ${formatRelativeTime(t)}`),
83
+ ...nextRuns.map((t, i) => ` ${i + 1}. ${formatRelativeTime(t, now)}`),
82
84
  ].join('\n')
83
85
  // 一行紧凑:name(id) + schedule(含 kind 信息) + expires + force。
84
86
  // 删冗余 Kind 行(formatSchedule 已含 once/every);Expires/Force 合并(默认 no-expires/no-force 显式)。
85
87
  const expiresLabel = task.expiresAt
86
- ? `expires ${formatRelativeTime(task.expiresAt)}`
88
+ ? `expires ${formatRelativeTime(task.expiresAt, now)}`
87
89
  : 'no-expires'
88
90
  const forceLabel = task.force ? 'force' : 'no-force'
89
91
  const message = [
@@ -99,8 +101,10 @@ export class SchedulerService {
99
101
  if (tasks.length === 0) {
100
102
  return { success: true, message: 'No scheduled tasks.', data: { tasks: [] } }
101
103
  }
104
+ // 同 create:同一基准渲染全部相对时间,避免逐项读时钟的边界漂移
105
+ const now = this.now()
102
106
  const message = tasks.map(t =>
103
- `${t.enabled ? '●' : '○'} ${t.id} ${t.name} · ${formatSchedule(t.schedule, t.kind)} · ${formatRelativeTime(t.nextRunAt)}`
107
+ `${t.enabled ? '●' : '○'} ${t.id} ${t.name} · ${formatSchedule(t.schedule, t.kind)} · ${formatRelativeTime(t.nextRunAt, now)}`
104
108
  ).join('\n')
105
109
  return { success: true, message, data: { tasks } }
106
110
  }