@zhushanwen/pi-scheduler 0.11.1 → 0.11.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/README.md +2 -8
- package/package.json +1 -1
- package/src/__tests__/sdk-contract.test.ts +35 -1
- package/src/__tests__/summary.test.ts +7 -0
- package/src/format.ts +1 -1
- package/src/index.ts +0 -5
- package/src/render-result.ts +3 -28
- package/src/tool.ts +9 -2
package/README.md
CHANGED
|
@@ -28,15 +28,9 @@ npm install @zhushanwen/pi-scheduler
|
|
|
28
28
|
|
|
29
29
|
安装后扩展在 pi 会话启动时自动装配(见下节),无需额外配置。
|
|
30
30
|
|
|
31
|
-
##
|
|
31
|
+
## 工具激活
|
|
32
32
|
|
|
33
|
-
`schedule` / `schedule_control`
|
|
34
|
-
|
|
35
|
-
```json
|
|
36
|
-
{ "defaultTools": ["+tool_search"] }
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
`+` 前缀是增量修饰(保留 read/bash/edit/write 默认集)。未开启 `tool_search` 时这两个工具对模型不可见。
|
|
33
|
+
`schedule` / `schedule_control` 两工具均为 direct 常驻——注册不声明 `exposure` 字段(pi SDK 缺省 `direct`),工具声明随会话常驻、对模型默认可见,无需经 `tool_search` 检索激活,独立 pi 开箱即用。
|
|
40
34
|
|
|
41
35
|
## 激活方式
|
|
42
36
|
|
package/package.json
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
// 读取——若在 factory 顶层捕获 service! 非空断言,注册时 service 为 null,
|
|
18
18
|
// execute 调用会 NPE。此套件验证 session_start 前 execute 优雅 throw 而非 crash。
|
|
19
19
|
|
|
20
|
-
import type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent'
|
|
20
|
+
import type { ExtensionAPI, ExtensionContext, Theme } from '@earendil-works/pi-coding-agent'
|
|
21
21
|
import { describe, expect, it, vi } from 'vitest'
|
|
22
22
|
|
|
23
23
|
// MF-3:session_start handler 会真实执行 importLegacyStore(ctx.cwd, ...) —— fakeCtx cwd='/test'
|
|
@@ -105,6 +105,40 @@ describe('pi-scheduler SDK contract', () => {
|
|
|
105
105
|
}
|
|
106
106
|
})
|
|
107
107
|
|
|
108
|
+
// exposure 重划终态锚(C-ext-05,2026-10-23 ADR-0136):两工具保持 direct 常驻 =
|
|
109
|
+
// 注册时不写 exposure 字段(SDK 缺省 direct,pi-semantics PS-91)——误写 "deferred" 会把
|
|
110
|
+
// 触发入口从模型默认可见集收走(tool_search 才可激活),此处红。
|
|
111
|
+
it('exposure 注册终态:schedule / schedule_control 均 direct 常驻(不写 exposure 字段,SDK 缺省)', () => {
|
|
112
|
+
const { pi, tools } = createMockPi()
|
|
113
|
+
schedulerExtension(pi)
|
|
114
|
+
expect(tools.map(t => t.name).sort()).toEqual(['schedule', 'schedule_control'])
|
|
115
|
+
for (const tool of tools) {
|
|
116
|
+
expect(tool.exposure).toBeUndefined()
|
|
117
|
+
}
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
it('每个 tool 有 renderResult 函数成员(C-ext-05)+ 折叠态输出 details.summary 一行', () => {
|
|
121
|
+
const { pi, tools } = createMockPi()
|
|
122
|
+
schedulerExtension(pi)
|
|
123
|
+
for (const tool of tools) {
|
|
124
|
+
expect(typeof tool.renderResult).toBe('function')
|
|
125
|
+
const renderResult = tool.renderResult as (
|
|
126
|
+
result: unknown,
|
|
127
|
+
options: { expanded: boolean },
|
|
128
|
+
theme: Theme,
|
|
129
|
+
) => { render: (width: number) => string[] }
|
|
130
|
+
const renderTheme = { fg: (_token: string, text: string) => text } as unknown as Theme
|
|
131
|
+
const result = {
|
|
132
|
+
content: [{ type: 'text', text: 'schedule output…' }],
|
|
133
|
+
details: { summary: 'Scheduled reminder in 5m' },
|
|
134
|
+
}
|
|
135
|
+
// Text.render 按宽度 pad,trim 后断言(折叠态 = details.summary 一行)
|
|
136
|
+
const lines = renderResult(result, { expanded: false }, renderTheme).render(400)
|
|
137
|
+
expect(lines.length).toBe(1)
|
|
138
|
+
expect(lines[0].trim()).toBe('Scheduled reminder in 5m')
|
|
139
|
+
}
|
|
140
|
+
})
|
|
141
|
+
|
|
108
142
|
it('execute 是 async function', () => {
|
|
109
143
|
const { pi, tools } = createMockPi()
|
|
110
144
|
schedulerExtension(pi)
|
|
@@ -47,6 +47,13 @@ describe('buildScheduleSummary', () => {
|
|
|
47
47
|
const r = toolResult({ task: { name: 'Odd', schedule: { mode: 'bogus' } } })
|
|
48
48
|
expect(buildScheduleSummary(createParams, r)).toBe('Scheduled "Odd" · 5m')
|
|
49
49
|
})
|
|
50
|
+
|
|
51
|
+
it('name / schedule 含换行 → 摘要归一单行(S-1)', () => {
|
|
52
|
+
const r = toolResult({ task: { name: 'Multi\nline', schedule: { mode: 'bogus' } } })
|
|
53
|
+
expect(buildScheduleSummary({ ...createParams, schedule: '5m\nextra' }, r)).toBe(
|
|
54
|
+
'Scheduled "Multi line" · 5m extra',
|
|
55
|
+
)
|
|
56
|
+
})
|
|
50
57
|
})
|
|
51
58
|
|
|
52
59
|
// ── buildScheduleControlSummary ──────────────────────
|
package/src/format.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// 时间格式化器(r4 M1 locale 版):formatSchedule / formatRelativeTime 是 locale 必填的
|
|
2
|
-
// 本地实现(UiLocale 双语输出,i18n.ts / widget / service / commands
|
|
2
|
+
// 本地实现(UiLocale 双语输出,i18n.ts / widget / service / commands 消费);固定版 locale
|
|
3
3
|
// 化与 U2 protocol 下沉(无 locale 面,服务 taiji plugin 管理侧)暂双轨并存,收敛待后续
|
|
4
4
|
// 把 locale 版提升进 @zhushanwen/extension-protocol。formatDuration / MS_PER_* 单源
|
|
5
5
|
// @zhushanwen/extension-protocol(re-export 保持既有 import 路径)。
|
package/src/index.ts
CHANGED
|
@@ -260,9 +260,6 @@ export default function schedulerExtension(pi: ExtensionAPI): void {
|
|
|
260
260
|
pi.registerTool({
|
|
261
261
|
name: 'schedule',
|
|
262
262
|
label: 'Schedule',
|
|
263
|
-
// deferred(tool-declaration-slimming D4):不常驻声明,tool_search 命中即激活;
|
|
264
|
-
// 迁移写入的 +tool_search 条目(runtime pi-codemode-settings)承载检索入口
|
|
265
|
-
exposure: 'deferred',
|
|
266
263
|
description:
|
|
267
264
|
'Create a scheduled task that fires a message at intervals or cron schedule. ' +
|
|
268
265
|
'The task is created immediately (no confirmation form). It is a session-scoped reminder, ' +
|
|
@@ -295,8 +292,6 @@ export default function schedulerExtension(pi: ExtensionAPI): void {
|
|
|
295
292
|
pi.registerTool({
|
|
296
293
|
name: 'schedule_control',
|
|
297
294
|
label: 'Schedule Control',
|
|
298
|
-
// deferred(tool-declaration-slimming D4):同 schedule
|
|
299
|
-
exposure: 'deferred',
|
|
300
295
|
description: 'Manage scheduled tasks: list, toggle, delete, or run immediately.',
|
|
301
296
|
parameters: ScheduleControlParams,
|
|
302
297
|
promptGuidelines: controlGuidelines,
|
package/src/render-result.ts
CHANGED
|
@@ -7,32 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import type { Theme } from '@earendil-works/pi-coding-agent'
|
|
9
9
|
import { Text } from '@earendil-works/pi-tui'
|
|
10
|
-
import {
|
|
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
|
-
}
|
|
10
|
+
import { firstLine, readDetailsSummary, resultContentText } from '@zhushanwen/extension-protocol'
|
|
36
11
|
|
|
37
12
|
/** 折叠态(expanded=false)= details.summary 一行(dim);展开态 = content 纯文本。 */
|
|
38
13
|
export function renderScheduleToolResult(
|
|
@@ -40,7 +15,7 @@ export function renderScheduleToolResult(
|
|
|
40
15
|
options: { expanded: boolean },
|
|
41
16
|
theme: Theme,
|
|
42
17
|
): Text {
|
|
43
|
-
if (options.expanded) return new Text(
|
|
44
|
-
const summary = readDetailsSummary(result) ?? firstLine(
|
|
18
|
+
if (options.expanded) return new Text(resultContentText(result), 0, 0)
|
|
19
|
+
const summary = readDetailsSummary(result) ?? firstLine(resultContentText(result))
|
|
45
20
|
return new Text(theme.fg('dim', summary), 0, 0)
|
|
46
21
|
}
|
package/src/tool.ts
CHANGED
|
@@ -188,6 +188,13 @@ export function withScheduleSummary<T extends ToolResultLike>(
|
|
|
188
188
|
return { ...result, details: { ...details, summary } }
|
|
189
189
|
}
|
|
190
190
|
|
|
191
|
+
/** 多行文本 → 单行摘要文本(换行与连续空白折叠为单空格;plan tool.ts 同模式)。
|
|
192
|
+
* details.summary 单行契约见 docs/extensions/extension-conventions.md
|
|
193
|
+
* 「Tool result details.summary 约定」(自由文本 name/schedule 可含 \n,进摘要前先归一)。 */
|
|
194
|
+
function toSingleLine(text: string): string {
|
|
195
|
+
return text.replace(/\s+/g, ' ').trim()
|
|
196
|
+
}
|
|
197
|
+
|
|
191
198
|
/** schedule(创建)摘要:`Scheduled "<name>" · <schedule>`;取消态与数据缺失时按形态降级。 */
|
|
192
199
|
export function buildScheduleSummary(params: ScheduleParamsT, result: ToolResultLike): string {
|
|
193
200
|
const details = asRecord(result.details)
|
|
@@ -197,8 +204,8 @@ export function buildScheduleSummary(params: ScheduleParamsT, result: ToolResult
|
|
|
197
204
|
if (name === undefined) return 'Scheduled'
|
|
198
205
|
const spec = asScheduleSpec(task.schedule)
|
|
199
206
|
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}`
|
|
207
|
+
const scheduleText = spec !== undefined ? formatSchedule(spec, kind, 'en-US') : toSingleLine(params.schedule)
|
|
208
|
+
return `Scheduled "${toSingleLine(name)}" · ${scheduleText}`
|
|
202
209
|
}
|
|
203
210
|
|
|
204
211
|
/** schedule_control 摘要:按 action 分支(toggle 按 enabled 目标态分 Paused/Resumed)。 */
|