@zhushanwen/pi-scheduler 0.10.4 → 0.11.0
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 +1 -1
- package/src/__tests__/sdk-contract.test.ts +53 -0
- package/src/__tests__/summary.test.ts +135 -0
- package/src/index.ts +19 -3
- package/src/render-result.ts +46 -0
- package/src/tool.ts +72 -0
package/package.json
CHANGED
|
@@ -212,3 +212,56 @@ describe('pi-scheduler SDK contract', () => {
|
|
|
212
212
|
expect(cleanup).toHaveBeenCalledTimes(2)
|
|
213
213
|
})
|
|
214
214
|
})
|
|
215
|
+
|
|
216
|
+
// ── execute 组装 details.summary(withScheduleSummary 包装线回归防线)──
|
|
217
|
+
// 纯函数(buildScheduleSummary / buildScheduleControlSummary)已被 summary.test.ts 直测;
|
|
218
|
+
// 本组护的是 index.ts execute 内的两处组装线(schedule → withScheduleSummary(result,
|
|
219
|
+
// buildScheduleSummary(...));schedule_control → buildScheduleControlSummary 同款)——
|
|
220
|
+
// 组装行丢失时 details.summary 缺失,GUI 收起态与 TUI 折叠态双双退化为原始 JSON,此处红。
|
|
221
|
+
|
|
222
|
+
/** execute 返回的 details 形状(组装线断言面:summary 恒为 string)。 */
|
|
223
|
+
interface SummaryDetails {
|
|
224
|
+
summary: unknown
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
describe('execute 组装 details.summary(withScheduleSummary 包装线)', () => {
|
|
228
|
+
/** 装配 → 触发 session_start,返回捕获的两 tool 与事件表。调用方 finally 里 fire session_shutdown 停 tick timer。 */
|
|
229
|
+
async function setupWithSession(): Promise<{
|
|
230
|
+
tools: CapturedTool[]
|
|
231
|
+
events: Map<string, (...args: unknown[]) => void>
|
|
232
|
+
fakeCtx: ExtensionContext
|
|
233
|
+
}> {
|
|
234
|
+
const { pi, tools, events } = createMockPi()
|
|
235
|
+
schedulerExtension(pi)
|
|
236
|
+
const fakeCtx = createFakeCtx()
|
|
237
|
+
await events.get('session_start')!({ type: 'session_start', reason: 'startup' }, fakeCtx)
|
|
238
|
+
return { tools, events, fakeCtx }
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
it('schedule 创建 → details.summary = Scheduled "<name>" · every 5m(index.ts schedule 包装线)', async () => {
|
|
242
|
+
const { tools, events, fakeCtx } = await setupWithSession()
|
|
243
|
+
try {
|
|
244
|
+
const tool = tools.find(t => t.name === 'schedule')!
|
|
245
|
+
const result = await tool.execute('call-sum-1', { prompt: 'check build', schedule: '5m' }, undefined, undefined, fakeCtx)
|
|
246
|
+
// 期望文案锚点:summary.test.ts 直测口径(interval 规格化 every 5m + en-US)
|
|
247
|
+
expect((result.details as SummaryDetails).summary).toBe('Scheduled "check build" · every 5m')
|
|
248
|
+
} finally {
|
|
249
|
+
await events.get('session_shutdown')?.()
|
|
250
|
+
}
|
|
251
|
+
})
|
|
252
|
+
|
|
253
|
+
it('schedule_control list → details.summary = Listed N tasks(index.ts schedule_control 包装线)', async () => {
|
|
254
|
+
const { tools, events, fakeCtx } = await setupWithSession()
|
|
255
|
+
try {
|
|
256
|
+
// 先建 1 个任务,list 计数才有非零区分度
|
|
257
|
+
const scheduleTool = tools.find(t => t.name === 'schedule')!
|
|
258
|
+
await scheduleTool.execute('call-sum-2', { prompt: 'standup', schedule: '5m' }, undefined, undefined, fakeCtx)
|
|
259
|
+
|
|
260
|
+
const controlTool = tools.find(t => t.name === 'schedule_control')!
|
|
261
|
+
const result = await controlTool.execute('call-sum-3', { action: 'list' }, undefined, undefined, fakeCtx)
|
|
262
|
+
expect((result.details as SummaryDetails).summary).toBe('Listed 1 tasks')
|
|
263
|
+
} finally {
|
|
264
|
+
await events.get('session_shutdown')?.()
|
|
265
|
+
}
|
|
266
|
+
})
|
|
267
|
+
})
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import type { Theme } from '@earendil-works/pi-coding-agent'
|
|
2
|
+
import { describe, expect, it } from 'vitest'
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
buildScheduleControlSummary,
|
|
6
|
+
buildScheduleSummary,
|
|
7
|
+
withScheduleSummary,
|
|
8
|
+
type ScheduleControlParamsT,
|
|
9
|
+
type ScheduleParamsT,
|
|
10
|
+
} from '../tool.js'
|
|
11
|
+
import { renderScheduleToolResult } from '../render-result.js'
|
|
12
|
+
|
|
13
|
+
function toolResult(details: unknown, text = 'body'): { content: { type: 'text'; text: string }[]; details: unknown } {
|
|
14
|
+
return { content: [{ type: 'text', text }], details }
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// ── buildScheduleSummary ─────────────────────────────
|
|
18
|
+
|
|
19
|
+
describe('buildScheduleSummary', () => {
|
|
20
|
+
const createParams: ScheduleParamsT = { prompt: 'standup', schedule: '5m', name: 'Standup reminder' }
|
|
21
|
+
|
|
22
|
+
it('创建成功 → Scheduled "<name>" · <schedule>(interval 规格化文案,en-US)', () => {
|
|
23
|
+
const r = toolResult({
|
|
24
|
+
task: { name: 'Standup reminder', kind: 'recurring', schedule: { mode: 'interval', intervalMs: 300000 } },
|
|
25
|
+
nextRuns: [],
|
|
26
|
+
})
|
|
27
|
+
expect(buildScheduleSummary(createParams, r)).toBe('Scheduled "Standup reminder" · every 5m')
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
it('cron 规格 → 原样表达式', () => {
|
|
31
|
+
const r = toolResult({
|
|
32
|
+
task: { name: 'Nightly', kind: 'recurring', schedule: { mode: 'cron', cronExpression: '*/10 * * * *' } },
|
|
33
|
+
nextRuns: [],
|
|
34
|
+
})
|
|
35
|
+
expect(buildScheduleSummary(createParams, r)).toBe('Scheduled "Nightly" · */10 * * * *')
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
it('取消态 → Cancelled', () => {
|
|
39
|
+
expect(buildScheduleSummary(createParams, toolResult({ cancelled: true }))).toBe('Cancelled')
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
it('details 形态漂移(无 task)→ 降级 Scheduled 不抛错', () => {
|
|
43
|
+
expect(buildScheduleSummary(createParams, toolResult({}))).toBe('Scheduled')
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it('task.schedule 非法形态 → 回落 params.schedule 原文', () => {
|
|
47
|
+
const r = toolResult({ task: { name: 'Odd', schedule: { mode: 'bogus' } } })
|
|
48
|
+
expect(buildScheduleSummary(createParams, r)).toBe('Scheduled "Odd" · 5m')
|
|
49
|
+
})
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
// ── buildScheduleControlSummary ──────────────────────
|
|
53
|
+
|
|
54
|
+
describe('buildScheduleControlSummary', () => {
|
|
55
|
+
it('list → Listed N tasks', () => {
|
|
56
|
+
const params: ScheduleControlParamsT = { action: 'list' }
|
|
57
|
+
expect(buildScheduleControlSummary(params, toolResult({ tasks: [{}, {}] }))).toBe('Listed 2 tasks')
|
|
58
|
+
expect(buildScheduleControlSummary(params, toolResult({ tasks: [] }))).toBe('Listed 0 tasks')
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
it('toggle enabled=false → Paused;enabled=true → Resumed', () => {
|
|
62
|
+
expect(
|
|
63
|
+
buildScheduleControlSummary({ action: 'toggle', id: 'ab12cd34', enabled: false }, toolResult({})),
|
|
64
|
+
).toBe('Paused task ab12cd34')
|
|
65
|
+
expect(
|
|
66
|
+
buildScheduleControlSummary({ action: 'toggle', id: 'ab12cd34', enabled: true }, toolResult({})),
|
|
67
|
+
).toBe('Resumed task ab12cd34')
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
it('delete / run → 动作 + id', () => {
|
|
71
|
+
expect(buildScheduleControlSummary({ action: 'delete', id: 'ab12cd34' }, toolResult({}))).toBe(
|
|
72
|
+
'Deleted task ab12cd34',
|
|
73
|
+
)
|
|
74
|
+
expect(buildScheduleControlSummary({ action: 'run', id: 'ab12cd34' }, toolResult({}))).toBe(
|
|
75
|
+
'Ran task ab12cd34 now',
|
|
76
|
+
)
|
|
77
|
+
})
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
// ── withScheduleSummary ──────────────────────────────
|
|
81
|
+
|
|
82
|
+
describe('withScheduleSummary', () => {
|
|
83
|
+
it('details 追加 summary,现有字段保留', () => {
|
|
84
|
+
const r = toolResult({ tasks: [{}] })
|
|
85
|
+
const out = withScheduleSummary(r, 'Listed 1 tasks')
|
|
86
|
+
expect(out.details).toEqual({ tasks: [{}], summary: 'Listed 1 tasks' })
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
it('非对象 details → 防御替换为 { summary }', () => {
|
|
90
|
+
const r = toolResult('legacy-string')
|
|
91
|
+
const out = withScheduleSummary(r, 'Done')
|
|
92
|
+
expect(out.details).toEqual({ summary: 'Done' })
|
|
93
|
+
})
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
// ── renderScheduleToolResult(TUI renderResult)──────
|
|
97
|
+
|
|
98
|
+
const mockTheme = {
|
|
99
|
+
fg: (_color: string, text: string) => text,
|
|
100
|
+
bg: (_color: string, text: string) => text,
|
|
101
|
+
bold: (text: string) => text,
|
|
102
|
+
italic: (text: string) => text,
|
|
103
|
+
underline: (text: string) => text,
|
|
104
|
+
inverse: (text: string) => text,
|
|
105
|
+
strikethrough: (text: string) => text,
|
|
106
|
+
getFgAnsi: (_color: string) => '',
|
|
107
|
+
getBgAnsi: (_color: string) => '',
|
|
108
|
+
getColorMode: () => 'truecolor' as const,
|
|
109
|
+
getThinkingBorderColor: () => (text: string) => text,
|
|
110
|
+
getBashModeBorderColor: () => (text: string) => text,
|
|
111
|
+
} as unknown as Theme
|
|
112
|
+
|
|
113
|
+
function renderedText(result: unknown, expanded: boolean): string {
|
|
114
|
+
return renderScheduleToolResult(result, { expanded }, mockTheme)
|
|
115
|
+
.render(400)
|
|
116
|
+
.map((line) => line.trimEnd())
|
|
117
|
+
.join('\n')
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
describe('renderScheduleToolResult', () => {
|
|
121
|
+
it('折叠态 → details.summary 一行', () => {
|
|
122
|
+
const r = withScheduleSummary(toolResult({ tasks: [{}] }, 'multi\nline'), 'Listed 1 tasks')
|
|
123
|
+
expect(renderedText(r, false)).toBe('Listed 1 tasks')
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
it('展开态 → content 纯文本', () => {
|
|
127
|
+
const r = toolResult({}, 'line1\nline2')
|
|
128
|
+
expect(renderedText(r, true)).toBe('line1\nline2')
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
it('无 summary(旧 entry)→ 折叠态回退 content 首行', () => {
|
|
132
|
+
const r = toolResult({}, 'Header\nbody')
|
|
133
|
+
expect(renderedText(r, false)).toBe('Header')
|
|
134
|
+
})
|
|
135
|
+
})
|
package/src/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent'
|
|
1
|
+
import type { ExtensionAPI, ExtensionContext, Theme } from '@earendil-works/pi-coding-agent'
|
|
2
2
|
import type { GuiContext } from '@zhushanwen/extension-protocol'
|
|
3
3
|
import { toErrorMessage } from '@zhushanwen/pi-ext-guards'
|
|
4
4
|
import { getLogger } from '@zhushanwen/pi-extension-logger'
|
|
@@ -10,9 +10,12 @@ import { formatSchedule } from './format.js'
|
|
|
10
10
|
import { readUiLocale, t } from './i18n.js'
|
|
11
11
|
import { importLegacyStore } from './importer.js'
|
|
12
12
|
import { abortPendingScheduleForms } from './interaction.js'
|
|
13
|
+
import { renderScheduleToolResult } from './render-result.js'
|
|
13
14
|
import { SchedulerRuntime, type SchedulerModelOps } from './runtime.js'
|
|
14
15
|
import { SchedulerService } from './service.js'
|
|
15
16
|
import {
|
|
17
|
+
buildScheduleControlSummary,
|
|
18
|
+
buildScheduleSummary,
|
|
16
19
|
controlGuidelines,
|
|
17
20
|
handleSchedule,
|
|
18
21
|
handleScheduleControl,
|
|
@@ -21,6 +24,7 @@ import {
|
|
|
21
24
|
scheduleGuidelines,
|
|
22
25
|
ScheduleParams,
|
|
23
26
|
type ScheduleParamsT,
|
|
27
|
+
withScheduleSummary,
|
|
24
28
|
} from './tool.js'
|
|
25
29
|
import { computeTasksFingerprint, setSchedulerWidget } from './widget.js'
|
|
26
30
|
import type { ScheduledTask } from './types.js'
|
|
@@ -274,11 +278,17 @@ export default function schedulerExtension(pi: ExtensionAPI): void {
|
|
|
274
278
|
_ctx: ExtensionContext,
|
|
275
279
|
) {
|
|
276
280
|
try {
|
|
277
|
-
|
|
281
|
+
const result = await handleSchedule(getService(), params, signal)
|
|
282
|
+
// 收起态一行摘要:details 追加 summary(现有字段保留),GUI 收起态与 TUI 折叠态共用
|
|
283
|
+
return withScheduleSummary(result, buildScheduleSummary(params, result))
|
|
278
284
|
} catch (err) {
|
|
279
285
|
throw new Error(`Error: ${toErrorMessage(err)}`)
|
|
280
286
|
}
|
|
281
287
|
},
|
|
288
|
+
|
|
289
|
+
renderResult(result: unknown, options: { expanded: boolean }, theme: Theme) {
|
|
290
|
+
return renderScheduleToolResult(result, options, theme)
|
|
291
|
+
},
|
|
282
292
|
})
|
|
283
293
|
|
|
284
294
|
// 注册 schedule_control tool(错误路径同上:throw 让 pi 置 isError)
|
|
@@ -298,11 +308,17 @@ export default function schedulerExtension(pi: ExtensionAPI): void {
|
|
|
298
308
|
_ctx: ExtensionContext,
|
|
299
309
|
) {
|
|
300
310
|
try {
|
|
301
|
-
|
|
311
|
+
const result = await handleScheduleControl(getService(), params)
|
|
312
|
+
// 收起态一行摘要:同 schedule
|
|
313
|
+
return withScheduleSummary(result, buildScheduleControlSummary(params, result))
|
|
302
314
|
} catch (err) {
|
|
303
315
|
throw new Error(`Error: ${toErrorMessage(err)}`)
|
|
304
316
|
}
|
|
305
317
|
},
|
|
318
|
+
|
|
319
|
+
renderResult(result: unknown, options: { expanded: boolean }, theme: Theme) {
|
|
320
|
+
return renderScheduleToolResult(result, options, theme)
|
|
321
|
+
},
|
|
306
322
|
})
|
|
307
323
|
|
|
308
324
|
// 注册 /schedule 命令。传 getter 而非 service 实例:factory 执行时
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* schedule / schedule_control 的 TUI renderResult(折叠态一行摘要 / 展开态 content 纯文本)。
|
|
3
|
+
*
|
|
4
|
+
* summary 来源 = details.summary(index.ts execute 经 withScheduleSummary 追加);
|
|
5
|
+
* 历史持久化 entry 无 summary 时折叠态回退 content 文本首行。
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { Theme } from '@earendil-works/pi-coding-agent'
|
|
9
|
+
import { Text } from '@earendil-works/pi-tui'
|
|
10
|
+
import { firstContentText } from '@zhushanwen/extension-protocol'
|
|
11
|
+
|
|
12
|
+
/** content[0] 的 text 纯文本(content 缺失或非 text 类型时兜底空串)。 */
|
|
13
|
+
function contentText(result: unknown): string {
|
|
14
|
+
const content = fieldValue(result, 'content')
|
|
15
|
+
if (!Array.isArray(content)) return ''
|
|
16
|
+
return firstContentText({ content })
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** unknown 值按字段名安全取值(index-signature 收窄,替代全可选结构断言)。 */
|
|
20
|
+
function fieldValue(v: unknown, key: string): unknown {
|
|
21
|
+
if (typeof v !== 'object' || v === null) return undefined
|
|
22
|
+
return (v as Record<string, unknown>)[key]
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** 从 details 安全读取 summary(容错非对象形态与旧版本 entry)。 */
|
|
26
|
+
function readDetailsSummary(result: unknown): string | undefined {
|
|
27
|
+
const summary = fieldValue(fieldValue(result, 'details'), 'summary')
|
|
28
|
+
return typeof summary === 'string' && summary !== '' ? summary : undefined
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** 折叠态回退:content 文本首行(保持一行摘要形态)。 */
|
|
32
|
+
function firstLine(text: string): string {
|
|
33
|
+
const idx = text.indexOf('\n')
|
|
34
|
+
return idx === -1 ? text : text.slice(0, idx)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** 折叠态(expanded=false)= details.summary 一行(dim);展开态 = content 纯文本。 */
|
|
38
|
+
export function renderScheduleToolResult(
|
|
39
|
+
result: unknown,
|
|
40
|
+
options: { expanded: boolean },
|
|
41
|
+
theme: Theme,
|
|
42
|
+
): Text {
|
|
43
|
+
if (options.expanded) return new Text(contentText(result), 0, 0)
|
|
44
|
+
const summary = readDetailsSummary(result) ?? firstLine(contentText(result))
|
|
45
|
+
return new Text(theme.fg('dim', summary), 0, 0)
|
|
46
|
+
}
|
package/src/tool.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { Static, Type } from 'typebox'
|
|
2
2
|
|
|
3
|
+
import { formatSchedule } from './format.js'
|
|
3
4
|
import { parseSchedule } from './parsing.js'
|
|
4
5
|
import type { SchedulerService, ServiceResult } from './service.js'
|
|
6
|
+
import type { ScheduleSpec, TaskKind } from './types.js'
|
|
5
7
|
|
|
6
8
|
// ── schedule tool ──
|
|
7
9
|
|
|
@@ -150,3 +152,73 @@ function toToolResult(result: ServiceResult): {
|
|
|
150
152
|
details: result.data ?? {},
|
|
151
153
|
}
|
|
152
154
|
}
|
|
155
|
+
|
|
156
|
+
// ── 收起态一行摘要(GUI details.summary + TUI 折叠态共用)──
|
|
157
|
+
// summary 是展示面:details 形态漂移(历史 entry)时容错降级,不抛错。
|
|
158
|
+
|
|
159
|
+
interface ToolResultLike { // oe-exempt:20261017:framework:details 数据形状窄 view(单实现消费,非抽象接口)
|
|
160
|
+
content: { type: 'text'; text: string }[]
|
|
161
|
+
details: unknown
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function asRecord(v: unknown): Record<string, unknown> {
|
|
165
|
+
return typeof v === 'object' && v !== null ? (v as Record<string, unknown>) : {}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function strOf(v: unknown): string | undefined {
|
|
169
|
+
return typeof v === 'string' ? v : undefined
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/** ScheduleSpec 运行时窄化(details.task.schedule 经 JSON 往返后仍需 shape 校验)。 */
|
|
173
|
+
function asScheduleSpec(v: unknown): ScheduleSpec | undefined {
|
|
174
|
+
const spec = asRecord(v)
|
|
175
|
+
if (spec.mode !== 'interval' && spec.mode !== 'cron') return undefined
|
|
176
|
+
return spec as ScheduleSpec
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/** 给 tool result 的 details 追加 summary(现有字段保留;非对象 details 防御替换)。 */
|
|
180
|
+
export function withScheduleSummary<T extends ToolResultLike>(
|
|
181
|
+
result: T,
|
|
182
|
+
summary: string,
|
|
183
|
+
): T {
|
|
184
|
+
const details = result.details
|
|
185
|
+
if (details === null || typeof details !== 'object' || Array.isArray(details)) {
|
|
186
|
+
return { ...result, details: { summary } }
|
|
187
|
+
}
|
|
188
|
+
return { ...result, details: { ...details, summary } }
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/** schedule(创建)摘要:`Scheduled "<name>" · <schedule>`;取消态与数据缺失时按形态降级。 */
|
|
192
|
+
export function buildScheduleSummary(params: ScheduleParamsT, result: ToolResultLike): string {
|
|
193
|
+
const details = asRecord(result.details)
|
|
194
|
+
if (details.cancelled === true) return 'Cancelled'
|
|
195
|
+
const task = asRecord(details.task)
|
|
196
|
+
const name = strOf(task.name)
|
|
197
|
+
if (name === undefined) return 'Scheduled'
|
|
198
|
+
const spec = asScheduleSpec(task.schedule)
|
|
199
|
+
const kind = task.kind === 'once' || task.kind === 'recurring' ? (task.kind as TaskKind) : undefined
|
|
200
|
+
const scheduleText = spec !== undefined ? formatSchedule(spec, kind, 'en-US') : params.schedule
|
|
201
|
+
return `Scheduled "${name}" · ${scheduleText}`
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/** schedule_control 摘要:按 action 分支(toggle 按 enabled 目标态分 Paused/Resumed)。 */
|
|
205
|
+
export function buildScheduleControlSummary(
|
|
206
|
+
params: ScheduleControlParamsT,
|
|
207
|
+
result: ToolResultLike,
|
|
208
|
+
): string {
|
|
209
|
+
const details = asRecord(result.details)
|
|
210
|
+
switch (params.action) {
|
|
211
|
+
case 'list':
|
|
212
|
+
return `Listed ${Array.isArray(details.tasks) ? details.tasks.length : 0} tasks`
|
|
213
|
+
case 'toggle':
|
|
214
|
+
if (params.enabled === false) return `Paused task ${params.id ?? ''}`
|
|
215
|
+
if (params.enabled === true) return `Resumed task ${params.id ?? ''}`
|
|
216
|
+
return `Toggled task ${params.id ?? ''}`
|
|
217
|
+
case 'delete':
|
|
218
|
+
return `Deleted task ${params.id ?? ''}`
|
|
219
|
+
case 'run':
|
|
220
|
+
return `Ran task ${params.id ?? ''} now`
|
|
221
|
+
default:
|
|
222
|
+
return 'Done'
|
|
223
|
+
}
|
|
224
|
+
}
|