dsh-later 1.0.0 → 1.1.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.
@@ -15,7 +15,8 @@
15
15
  */
16
16
  import { z } from 'zod';
17
17
  import type { SessionEvent } from '@deepseek-ai/dsh-session';
18
- import type { ScheduleRecord } from '@deepseek-ai/dsh-schedule';
18
+ import type { AfterScheduleRecord, AtScheduleRecord, EveryScheduleRecord } from '@deepseek-ai/dsh-schedule';
19
+ import type { PausedEntry } from './ownership-store.js';
19
20
  import type { UserScheduleProjectionValue } from './domain.js';
20
21
  /** 投影 wire 值 schema(wire.viewSchema:离开宿主前校验客户端载荷)。 */
21
22
  export declare const userSchedulesSchema: z.ZodObject<{
@@ -28,6 +29,11 @@ export declare const userSchedulesSchema: z.ZodObject<{
28
29
  scheduled_at: z.ZodString;
29
30
  created_at: z.ZodOptional<z.ZodString>;
30
31
  delivery_mode: z.ZodLiteral<"session-local">;
32
+ status: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<"active">, z.ZodLiteral<"paused">]>>;
33
+ remaining_seconds: z.ZodOptional<z.ZodNumber>;
34
+ paused_at: z.ZodOptional<z.ZodString>;
35
+ schedule_id: z.ZodOptional<z.ZodString>;
36
+ window_seconds: z.ZodOptional<z.ZodNumber>;
31
37
  }, z.core.$strict>>;
32
38
  }, z.core.$strict>;
33
39
  export declare const userSchedulesStateSchema: z.ZodObject<{
@@ -43,11 +49,37 @@ export declare const userSchedulesStateSchema: z.ZodObject<{
43
49
  createdAt: z.ZodOptional<z.ZodNumber>;
44
50
  }, z.core.$loose>>;
45
51
  seedSeq: z.ZodNumber;
52
+ paused: z.ZodDefault<z.ZodArray<z.ZodObject<{
53
+ uid: z.ZodString;
54
+ prompt: z.ZodString;
55
+ delivery: z.ZodUnion<readonly [z.ZodLiteral<"context">, z.ZodLiteral<"user">]>;
56
+ kind: z.ZodLiteral<"after">;
57
+ remainingSeconds: z.ZodNumber;
58
+ originalScheduledAt: z.ZodString;
59
+ originalAfterSeconds: z.ZodOptional<z.ZodNumber>;
60
+ lastScheduleId: z.ZodString;
61
+ pausedAt: z.ZodNumber;
62
+ }, z.core.$strip>>>;
46
63
  }, z.core.$strip>;
47
- /** 活动存储记录:dsh-schedule 记录 + 创建时刻(来自 create 事件的 time)。 */
48
- export type StoredScheduleRecord = ScheduleRecord & {
64
+ /**
65
+ * 活动存储记录:dsh-schedule 记录 + 创建时刻(来自 create 事件的 time)。
66
+ *
67
+ * 显式按**本插件产出的三种 kind** 收窄,而非直接用 `ScheduleRecord`:
68
+ * 0.1.7 的联合新增了 daily/weekly/cron,它们的字段面
69
+ * (`afterSeconds` / `everySeconds`)与本插件的 wire 契约不同,透传会让
70
+ * `wireItemOf` 读到不存在的字段。
71
+ *
72
+ * 逐个具名引用而非 `Extract`:`Extract<ScheduleRecord, {kind:'after'|…}>`
73
+ * 在本仓库的 tsc 配置下不触发收窄(判别式联合的 `Extract` 需 kind 为
74
+ * 单字面量,逐个列举后交集才正确)。
75
+ */
76
+ export type StoredScheduleRecord = (AfterScheduleRecord & {
49
77
  readonly createdAt?: number;
50
- };
78
+ }) | (AtScheduleRecord & {
79
+ readonly createdAt?: number;
80
+ }) | (EveryScheduleRecord & {
81
+ readonly createdAt?: number;
82
+ });
51
83
  /** 投影内部状态(plain JSON:owned/active 用数组承载)。 */
52
84
  export interface UserScheduleProjectionState {
53
85
  /** 会话 id(init 由 header 注入)。 */
@@ -58,6 +90,8 @@ export interface UserScheduleProjectionState {
58
90
  readonly active: readonly StoredScheduleRecord[];
59
91
  /** 已见 `session/end-seed` 的 seq;更早的继承前缀一律忽略。 */
60
92
  readonly seedSeq: number;
93
+ /** sidecar 暂停留档镜像。 */
94
+ readonly paused: readonly PausedEntry[];
61
95
  }
62
96
  /** 初始状态:按会话 id 从所有权 sidecar 装载(进程重启后恢复 GUI 归属)。 */
63
97
  export declare function initUserScheduleProjection(header?: {
@@ -67,7 +101,19 @@ export declare function initUserScheduleProjection(header?: {
67
101
  * 增量 fold。返回同一引用当事件无关;返回新状态当 owned/active 发生变化。
68
102
  */
69
103
  export declare function applyUserScheduleProjection(state: UserScheduleProjectionState, event: SessionEvent): UserScheduleProjectionState;
70
- /** 投影 view:只暴露用户创建的、当前活动的记录。 */
104
+ /**
105
+ * 投影 view:活动 + 暂停(同列表;paused 靠 status 区分,dock 原地冻结)。
106
+ *
107
+ * ⚠️ 这里是**唯一**能让「删除暂停项」生效的地方。原因链:
108
+ * - 删除暂停项只清 sidecar、**不写会话事件**(补写 `schedule/change` delete 会
109
+ * 因为该 id 在 pause 时已被删除而抛 `targets inactive id`,把日志读坏);
110
+ * - 投影 cell 只在事件到达时由 `apply` 推进,且跨进程持久化(projcache),
111
+ * 客户端又按 seq「更高者胜」消费控制帧——没有新事件就没有新帧;
112
+ * - 于是 `state.paused` 会永久停留在最后一次事件时的快照上。
113
+ * 因此 view 不能直接信任 `state.paused`,必须**以 sidecar 为准重读**:
114
+ * sidecar 是暂停留档的权威存储(`ownship-store`),`state.paused` 只是镜像。
115
+ * 这样无论 cell 多陈旧,读出来的 paused 列表都与真实留档一致。
116
+ */
71
117
  export declare function viewUserScheduleProjection(state: UserScheduleProjectionState): UserScheduleProjectionValue;
72
118
  /**
73
119
  * 投影单元声明(传给 sessionProjections.register)。
@@ -93,6 +139,17 @@ export declare const userSchedulesProjectionUnit: {
93
139
  createdAt: z.ZodOptional<z.ZodNumber>;
94
140
  }, z.core.$loose>>;
95
141
  seedSeq: z.ZodNumber;
142
+ paused: z.ZodDefault<z.ZodArray<z.ZodObject<{
143
+ uid: z.ZodString;
144
+ prompt: z.ZodString;
145
+ delivery: z.ZodUnion<readonly [z.ZodLiteral<"context">, z.ZodLiteral<"user">]>;
146
+ kind: z.ZodLiteral<"after">;
147
+ remainingSeconds: z.ZodNumber;
148
+ originalScheduledAt: z.ZodString;
149
+ originalAfterSeconds: z.ZodOptional<z.ZodNumber>;
150
+ lastScheduleId: z.ZodString;
151
+ pausedAt: z.ZodNumber;
152
+ }, z.core.$strip>>>;
96
153
  }, z.core.$strip>;
97
154
  readonly init: typeof initUserScheduleProjection;
98
155
  readonly apply: typeof applyUserScheduleProjection;
@@ -107,9 +164,14 @@ export declare const userSchedulesProjectionUnit: {
107
164
  scheduled_at: z.ZodString;
108
165
  created_at: z.ZodOptional<z.ZodString>;
109
166
  delivery_mode: z.ZodLiteral<"session-local">;
167
+ status: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<"active">, z.ZodLiteral<"paused">]>>;
168
+ remaining_seconds: z.ZodOptional<z.ZodNumber>;
169
+ paused_at: z.ZodOptional<z.ZodString>;
170
+ schedule_id: z.ZodOptional<z.ZodString>;
171
+ window_seconds: z.ZodOptional<z.ZodNumber>;
110
172
  }, z.core.$strict>>;
111
173
  }, z.core.$strict>;
112
174
  readonly view: typeof viewUserScheduleProjection;
113
175
  };
114
- readonly stateVersion: 3;
176
+ readonly stateVersion: 4;
115
177
  };
@@ -19,6 +19,13 @@ import type { Context } from '@deepseek-ai/cordis';
19
19
  import type { Agent } from '@deepseek-ai/dsh-agent';
20
20
  import type { ScheduleRecord, OneShotScheduleRecord, EveryScheduleRecord } from '@deepseek-ai/dsh-schedule';
21
21
  import type { UserScheduleDelivery } from './domain.js';
22
+ import type { DeliveryMode } from './delivery.js';
23
+ /** runtime 关心的设置子集(插话投递策略)。 */
24
+ export interface SteerDeliverySettings {
25
+ readonly steerDelivery: DeliveryMode;
26
+ readonly confirmDelivery: boolean;
27
+ readonly onUnconfirmed: 'keep' | 'discard';
28
+ }
22
29
  /** 一次「用户提醒决策」:一批同形态到期一次性、或一批到期固定间隔、或下一次等待目标。
23
30
  *
24
31
  * P1-5:one-shot 从单条改为**同投递形态的批次**——网页关闭期间积压的多条
@@ -41,10 +48,31 @@ type UserDecision = {
41
48
  target?: number;
42
49
  };
43
50
  export declare function dueUserDecision(active: readonly ScheduleRecord[], now: number, delivery?: ReadonlyMap<string, UserScheduleDelivery>): UserDecision;
51
+ /**
52
+ * 多条到期一次性提醒的合并注入 framing(P1-5)。
53
+ * 形状与 dsh-schedule 的固定间隔批次 framing 完全一致:
54
+ * 动态字段全部经 JSON 转义,保持「非信任提醒内容」的防护语义。
55
+ *
56
+ * ⚠️ 这是**上游文案的本地副本**。0.2.0 起 dsh-schedule 把 one-shot 与 recurring
57
+ * 两条 framing 统一收敛为一句 `SCHEDULED_MESSAGE_FRAMING`,取代了此前
58
+ * 「Present all due reminders … not new user instructions.」的长句。
59
+ * 本插件只对**多条合并**这一种上游不提供的形态自渲染(单条走
60
+ * `renderReminderFraming`,自动跟随上游),故此处需手工对齐,否则同一会话里
61
+ * 单条与批次的防护措辞会不一致。
62
+ */
63
+ export declare function renderOneShotBatchFraming(records: readonly OneShotScheduleRecord[]): string;
44
64
  /** 每个 agent 的独立调度器。 */
45
65
  export declare class UserScheduleRuntime {
46
66
  private readonly ctx;
47
67
  private readonly agent;
68
+ /**
69
+ * 读取当前生效设置(每次调用按需取值,与工具/命令同源)。
70
+ *
71
+ * 注入而非直接读 ctx.settings:settings 的注册在 index 的 `ctx.inject(['settings'])`
72
+ * 回调里完成,runtime 拿不到那个 scope;把它做成闭包注入,保证「设置 UI 改了立刻生效」
73
+ * 这一既有语义(`applies: 'live'`)在插话路径上同样成立。
74
+ */
75
+ private readonly getSettings;
48
76
  private readonly stop;
49
77
  private resolveStop;
50
78
  private timer;
@@ -55,7 +83,7 @@ export declare class UserScheduleRuntime {
55
83
  private failures;
56
84
  private idleWait;
57
85
  private disposal;
58
- constructor(ctx: Context, agent: Agent);
86
+ constructor(ctx: Context, agent: Agent, getSettings: () => SteerDeliverySettings);
59
87
  /** 是否仍为活动 root agent。 */
60
88
  private isLive;
61
89
  /** 发起一次重算(合并多个触发为一次)。 */
@@ -95,8 +123,37 @@ export declare class UserScheduleRuntime {
95
123
  code: string;
96
124
  message: string;
97
125
  }>;
126
+ /**
127
+ * 按配置把消息投进队列。
128
+ *
129
+ * `followup`(默认)入 `next-turn`:与 `next-step` 是两条独立队列,
130
+ * `claim('next-step', …)` 不触碰它,**结构上不存在**与 `turn/start` 竞争的窗口。
131
+ * `steer` 入 `next-step`,更快但会在撞上回合启动时被吞(见 delivery.ts 注释)。
132
+ */
133
+ private deliver;
134
+ /**
135
+ * 核对投递是否真正进入会话日志。
136
+ *
137
+ * 关闭确认时直接返回 true(fire-and-forget,保持旧语义)。
138
+ */
139
+ private verifyDelivery;
98
140
  /** 执行一次用户提醒决策与派发。 */
99
141
  private driveOnce;
142
+ /**
143
+ * 暂停项的插话投递。
144
+ *
145
+ * 暂停项不在日志里(其日志记录在 pause 时被删),所以从 sidecar 留档取材:
146
+ * `PausedEntry` 已含 `prompt` 与 `delivery`,足以构造消息。呈现方式与
147
+ * {@link buildMessage} 的 one-shot 分支保持一致(`user` 代发 vs `context` 注入),
148
+ * 避免两条路径的语义分叉。
149
+ *
150
+ * 投递成功后清掉留档:提醒已送达,不应再留在待发送列表。这与 dispatch 会把记录
151
+ * 移出 `active` 语义一致。(清留档不写会话事件,故投影不会自动刷新——客户端已用
152
+ * 本地摘除 + 投影对账处理同类情况,与"删除暂停项"同源。)
153
+ *
154
+ * @returns `undefined` 表示该 id 不是暂停项,交由调用方走常规日志路径。
155
+ */
156
+ private steerPausedById;
100
157
  /**
101
158
  * 构造注入的消息,按投递形态分流:
102
159
  * - `user`(`/later` 显式请求的延迟发送):原样内容 + `source.kind='user'`,
@@ -0,0 +1,111 @@
1
+ /**
2
+ * dsh-schedule 0.1.5 → 0.1.7 的版本桥接层。
3
+ *
4
+ * 0.1.7 对 `dsh-schedule` 做了三处破坏性变更,本模块把差异收敛为三个函数,
5
+ * 使 `user-tools.ts` / `runtime.ts` 不必写版本分支:
6
+ *
7
+ * 1. `allocateScheduleId(folded)` → 删除。0.1.5 用确定性递增
8
+ * (`schedule-1`、`schedule-2`…,靠 `seenIds` 避让),0.1.7 内联改为
9
+ * `ScheduleId(\`schedule-${randomUUID()}\`)`(见 0.1.7 `ScheduleService.create`)。
10
+ * 本插件的 `ownership-store` 把 id 当**不透明字符串**处理(无排序、无数字
11
+ * 解析,见 docs/verification.md),故行为差异对功能无影响。
12
+ *
13
+ * 2. `createAfterScheduleRecord` 新增第 5 个必填参数 `title`。0.1.7 起
14
+ * `decodeStoredTitle` 会在读取时校验:缺失 / 未 trim / 超长(>120)的
15
+ * 存量记录抛 `ScheduleLogError`——**不是跳过,是整份日志拒读**。
16
+ *
17
+ * 3. `renderEveryReminderBatchFraming` → 改名
18
+ * `renderRecurringReminderBatchFraming`(语义与参数不变)。
19
+ *
20
+ * @module dsh-later/schedule-compat
21
+ */
22
+ import * as sched from '@deepseek-ai/dsh-schedule';
23
+ import type { ScheduleRecord } from '@deepseek-ai/dsh-schedule';
24
+ declare const ScheduleId: typeof sched.ScheduleId;
25
+ /** 0.1.7 `MAX_TITLE_LENGTH`;0.1.5 无此概念,该常量在此仅作截断阈值。 */
26
+ export declare const SCHEDULE_TITLE_MAX = 120;
27
+ /**
28
+ * 0.1.7 的 `allocateScheduleId` 等价物。
29
+ *
30
+ * 随机 UUID 而非递增序号:与官方 `ScheduleService.create` 行为一致,且并发
31
+ * 创建时天然无碰撞。0.1.5 下也可用(`ScheduleId` 是纯 brand 校验,任意
32
+ * 非空字符串通过),因此本函数在两个代次上语义相同,无需双轨。
33
+ *
34
+ * @param _folded 仅为**保持调用点不变**而接受 0.1.5 的 `folded` 参数;0.1.7
35
+ * 不再需要它来避让碰撞(UUID 本身唯一),故不使用。
36
+ */
37
+ export declare function allocateScheduleIdCompat(_folded?: unknown): ReturnType<typeof ScheduleId>;
38
+ /**
39
+ * 从提醒正文派生 `title`。
40
+ *
41
+ * 0.1.7 要求 title 非空、已 trim、≤120 字符。实测本插件历史 496 条提醒中
42
+ * 最长 575 字符(27 条超限,占 5.4%),故**必须截断**而非直接透传 prompt。
43
+ * 截断在词边界优先,找不到合适边界时按字符硬切并补省略号,保证结果始终
44
+ * 是合法 title(`scheduleTitle` 不会抛)。
45
+ */
46
+ export declare function deriveScheduleTitle(prompt: string): string;
47
+ /** `createAfterScheduleRecord` 的跨版本包装。 */
48
+ export declare function createAfterRecord(id: ReturnType<typeof ScheduleId>, prompt: string, afterSeconds: number, now: number): ScheduleRecord;
49
+ /** `createAtScheduleRecord` 的跨版本包装。 */
50
+ export declare function createAtRecord(id: ReturnType<typeof ScheduleId>, prompt: string, scheduledAt: string, now: number): ScheduleRecord;
51
+ /** `createEveryScheduleRecord` 的跨版本包装。 */
52
+ export declare function createEveryRecord(id: ReturnType<typeof ScheduleId>, prompt: string, everySeconds: number, now: number): ScheduleRecord;
53
+ /**
54
+ * 周期提醒批次的 framing 渲染。
55
+ *
56
+ * 0.1.5 导出 `renderEveryReminderBatchFraming`,0.1.7 改名为
57
+ * `renderRecurringReminderBatchFraming`(并扩展到 daily/weekly/cron,本插件
58
+ * 只产生 `every` 记录)。两代参数与返回结构一致,按存在性择一。
59
+ */
60
+ export declare function renderRecurringBatchFraming(reminders: readonly unknown[]): string;
61
+ /**
62
+ * 提醒注入消息的 `source`。
63
+ *
64
+ * 0.1.5 的 `MessageSourceMap` 含 `plugin: { kind:'plugin'; plugin:string }`;
65
+ * 0.1.7 **删除了 `plugin` 与 `host` 两个 kind**,改由内核为每个生产者登记
66
+ * 具名 kind。官方 `dsh-schedule` 在 0.1.7 里注入提醒用的正是
67
+ * `source: { kind: 'schedule' }`(见其 `lib/index.js` 的 `agent.followup` 调用点)
68
+ * —— 与本插件投递的语义一致,故 0.1.7 走 `schedule`。
69
+ *
70
+ * `form:'notice'` + `summary` 两代都在(`ContextFormed` 未变),保留。
71
+ *
72
+ * 不能用具名类型断言跨代:source 的字面量 union 在两代不同,断言只会把
73
+ * 编译错误推迟到运行期。
74
+ */
75
+ export declare function scheduleMessageSource(boundedSummary: string): Record<string, unknown>;
76
+ /**
77
+ * `foldScheduleEvents` 结果的读视图收窄。
78
+ *
79
+ * 0.1.5 的 `FoldedSchedules.active` 是 `ScheduleRecord[]`;0.1.7 改为
80
+ * `LegacyScheduleRecord[]`(title 可选)。本插件只消费 `id` / `kind` /
81
+ * `prompt` / `afterSeconds` / `everySeconds` / `scheduledAt`,这些字段在
82
+ * `Legacy*` 视图中全部存在,故收窄无损。
83
+ */
84
+ export declare function asLegacySchedules(folded: unknown): {
85
+ readonly active: readonly ScheduleRecord[];
86
+ readonly seenIds: ReadonlySet<string>;
87
+ };
88
+ /**
89
+ * `schedule/change` 事件里 `create` 操作的载荷类型。
90
+ *
91
+ * 0.1.7 起内核把「事件载荷」与「内部记录」拆成两个类型:
92
+ * - `ScheduleCreateChange.schedule` 是 **`LegacyScheduleRecord`**(title 可选)
93
+ * - `ScheduleRecord` 是 Host 内部的新格式(title 必填,且含 daily/weekly/cron)
94
+ *
95
+ * 本插件只产生 `after` / `at` / `every` 三种(见 buildScheduleRecord),三者都是
96
+ * `Legacy*` 的对应分支去掉「title 必填」后的结果,故结构兼容。
97
+ *
98
+ * 但 TS 层面 `ScheduleRecord`(6 变体联合)对 `LegacyScheduleRecord`(3 变体)
99
+ * 不可赋值——**这不是运行时问题,是联合宽度问题**。故在此显式收窄:本插件
100
+ * 的产出已经过 `createAfter/At/EveryRecord` 限定,不含 daily/weekly/cron。
101
+ */
102
+ export declare function toChangePayload(record: ScheduleRecord): never;
103
+ /**
104
+ * `foldScheduleEvents().active` 元素的读取侧类型。
105
+ *
106
+ * 0.1.5 直接返回 `ScheduleRecord`;0.1.7 返回 `LegacyScheduleRecord`
107
+ * (title 可选,因为历史事件没有)。本插件读路径只在 title 存在时展示,
108
+ * 缺失时回退到 prompt——故按 Legacy 收窄是**语义正确**的,而非绕过检查。
109
+ */
110
+ export declare function asLegacyRecords(records: readonly unknown[]): readonly ScheduleRecord[];
111
+ export {};
@@ -0,0 +1,123 @@
1
+ /**
2
+ * 上游内核跨代兼容层:把「鸭子类型双轨探测 + peer 代次矩阵」收敛到单文件。
3
+ *
4
+ * 背景(docs/verification.md):DSH 内核 0.1.1 → 0.1.2 之间有多处不兼容,
5
+ * 此前散落在 user-tools / index / package.json 各处。上游每发新 rc 需要:
6
+ * 1. package.json peer + dev 各包补一枚举项(node-semver 不把预发布算进范围)
7
+ * 2. Session API 形状若有变化,只改本模块的 read* 函数
8
+ *
9
+ * 新 rc 落地 checklist:
10
+ * - 在 KERNEL_GENERATIONS 追加一代(id + peerRange + sessionApi)
11
+ * - 跑 `node scripts/sync-peer-matrix.mjs` 重写 package.json peer 枚举
12
+ * - 跑 `npm test`(含 tests/upstream-compat.test.mjs 矩阵护栏)
13
+ *
14
+ * @module dsh-later/upstream-compat
15
+ */
16
+ import type { Session, SessionEvent } from '@deepseek-ai/dsh-session';
17
+ /**
18
+ * 已验证的内核代次(peer 枚举的唯一事实来源)。
19
+ *
20
+ * - `id`:semver 精确版本(发布 tag)
21
+ * - `peerRange`:写入 package.json 的范围(node-semver 预发布需显式枚举)
22
+ * - `sessionApi`:Session 读日志的形状(见 readOwnEvents / readInheritedEventCount)
23
+ */
24
+ export declare const KERNEL_GENERATIONS: readonly [{
25
+ readonly id: "0.1.1-rc.2";
26
+ readonly peerRange: "^0.1.1-rc.2";
27
+ readonly sessionApi: "legacy-events";
28
+ }, {
29
+ readonly id: "0.1.2-rc.1";
30
+ readonly peerRange: "^0.1.2-rc.1";
31
+ readonly sessionApi: "own-events";
32
+ }, {
33
+ /**
34
+ * `0.1.5` 代次:Session 形状未变(仍 `ownEvents()` + `inheritedEventCount`),
35
+ * 且插件依赖的 `dsh-schedule` 导出面完整 —— 实测 `rc.1/rc.2/rc.3` 逐个
36
+ * `import()` 全部 15 个 host 模块均通过(见 docs/verification.md)。
37
+ */
38
+ readonly id: "0.1.5-rc.1";
39
+ readonly peerRange: "^0.1.5-rc.1";
40
+ readonly sessionApi: "own-events";
41
+ }, {
42
+ /**
43
+ * `0.1.7` 代次:Session 形状仍未变(`ownEvents()` + `inheritedEventCount`);
44
+ * `dsh-schedule` 的三处破坏性变更(`allocateScheduleId` 删除、
45
+ * `renderEveryReminderBatchFraming` 改名 `renderRecurringReminderBatchFraming`、
46
+ * `createAfterScheduleRecord` 新增必填 `title`)**全部收敛在
47
+ * `schedule-compat.ts`**——调用点 `user-tools.ts` / `runtime.ts` 不含版本分支。
48
+ *
49
+ * 三条差异在 bridge 里按「存在性择一」(framing)或「统一用新语义」
50
+ * (UUID id、title 派生)处理,两代行为一致,故可安全同时声明这两个范围。
51
+ *
52
+ * 不单列 0.1.6-alpha.*:alpha 线无独立兼容面,且 0.1.7 已覆盖其功能面。
53
+ */
54
+ readonly id: "0.1.7-rc.2";
55
+ readonly peerRange: "^0.1.7-rc.2";
56
+ readonly sessionApi: "own-events";
57
+ }, {
58
+ /**
59
+ * `0.2.0` 代次:内核跨了次版本号(0.1 → 0.2),但本插件依赖的 API 面
60
+ * **全部保持**。逐项核对(见 docs/verification.md「0.2.0 兼容核对」):
61
+ *
62
+ * - Session:形状未变(`ownEvents()` + `inheritedEventCount`)
63
+ * - dsh-schedule:0.1.7 的三处破坏性变更即为 0.2.0 现状;`foldScheduleEvents`
64
+ * / `renderReminderFraming` / `resolveEveryOccurrence` / `decodeScheduleChange`
65
+ * / `scheduleView` / `ScheduleId` 导出面完整,且
66
+ * `renderRecurringReminderBatchFraming` 仍在 —— `schedule-compat.ts` 无需改动
67
+ * - dsh-llm:`createUserMessage` / `boundContextSummary` 仍在;`MessageSourceMap`
68
+ * 已无 `plugin` kind,仅剩 user/model/tool/system-prompt,而 dsh-schedule
69
+ * 声明的 `schedule` kind 经 module augmentation 并入 —— 0.1.7 分支继续适用
70
+ * - dsh-agent:`followup()`(next-turn)与 `steer()`(next-step)语义未变
71
+ * - dsh-tools:`defineTool` 仍在
72
+ *
73
+ * 实证:以 0.2.0-rc.2 类型面对 `src/**`(host 端)跑 `tsc --noEmit` **零错误**,
74
+ * 且本地 183 项测试全绿。
75
+ *
76
+ * 附带影响:0.2.0 内核开始**强制**校验本范围(peer 不满足即整插件不加载,
77
+ * 此前只是警告)。故这一代次的缺失曾让插件在 0.2.0 上完全失效。
78
+ */
79
+ readonly id: "0.2.0-rc.2";
80
+ readonly peerRange: "^0.2.0-rc.2";
81
+ readonly sessionApi: "own-events";
82
+ }];
83
+ /**
84
+ * 随内核代次一起 bump 的 peer 包(宿主 hoisting 变化会波及这些运行时 import)。
85
+ * cordis / schemastery 等独立 semver 包**不**列入——它们不随内核 rc 枚举。
86
+ */
87
+ export declare const KERNEL_SCOPED_PACKAGES: readonly ["@deepseek-ai/dsh-agent", "@deepseek-ai/dsh-commands", "@deepseek-ai/dsh-llm", "@deepseek-ai/dsh-schedule", "@deepseek-ai/dsh-session", "@deepseek-ai/dsh-session-projection", "@deepseek-ai/dsh-settings", "@deepseek-ai/dsh-tools"];
88
+ /** 由 KERNEL_GENERATIONS 拼出的 peer 范围串(`a || b`)。 */
89
+ export declare function peerRangeFor(packageName: string): string;
90
+ /** 全部需逐代枚举的 `{ package, range }`(sync-peer-matrix / 矩阵测试共用)。 */
91
+ export declare function peerMatrixEntries(): ReadonlyArray<{
92
+ packageName: string;
93
+ range: string;
94
+ }>;
95
+ /** Session 读日志的最小结构(鸭子类型;两代公有面)。 */
96
+ export interface SessionLogSurface {
97
+ readonly header?: {
98
+ readonly seedLength?: unknown;
99
+ } | undefined;
100
+ readonly events?: readonly SessionEvent[] | undefined;
101
+ readonly ownEvents?: (() => readonly SessionEvent[]) | undefined;
102
+ readonly inheritedEventCount?: unknown;
103
+ }
104
+ /**
105
+ * 读取「本会话自有」事件日志(不含 fork 继承前缀时由调用方配合 offset)。
106
+ *
107
+ * - 0.1.2+:`session.ownEvents()`(推荐;仅 live 段)
108
+ * - 0.1.1:`session.events`(含 seed;配合 seedLength 切片)
109
+ */
110
+ export declare function readOwnEvents(session: SessionLogSurface | Session): readonly SessionEvent[];
111
+ /**
112
+ * 读取「fork 继承前缀长度」——`foldScheduleEvents` 第二参数。
113
+ *
114
+ * - 0.1.2+:`session.inheritedEventCount`(SessionLogOffset 品牌数字,运行时仍是 number)
115
+ * - 0.1.1:`session.header.seedLength`(等价物;此前实现漏了这条回退)
116
+ * - 均缺失:0(新建会话 / 测试假 Session)
117
+ *
118
+ * 统一以裸 `number` 返回:cache key 可用 `===`;传入 foldScheduleEvents 时
119
+ * 0.1.2 的 brandshape 校验接受 safe integer。
120
+ */
121
+ export declare function readInheritedEventCount(session: SessionLogSurface | Session): number;
122
+ /** 探测到的 sessionApi 代次(诊断 / 测试用;不参与业务分支)。 */
123
+ export declare function detectSessionApi(session: SessionLogSurface | Session): 'own-events' | 'legacy-events';
@@ -19,9 +19,9 @@
19
19
  import type { Context } from '@deepseek-ai/cordis';
20
20
  import type { Agent } from '@deepseek-ai/dsh-agent';
21
21
  import type { Session, SessionEvent } from '@deepseek-ai/dsh-session';
22
- import { foldScheduleEvents } from '@deepseek-ai/dsh-schedule';
23
22
  import type { ScheduleRecord, ScheduleView } from '@deepseek-ai/dsh-schedule';
24
23
  import type { UserScheduleDelivery } from './domain.js';
24
+ import type { PausedEntry } from './ownership-store.js';
25
25
  /** 单 session 用户任务上限(PRD:防滥用)。 */
26
26
  export declare const DEFAULT_MAX_SCHEDULES = 100;
27
27
  /**
@@ -53,7 +53,7 @@ export declare function validatePrompt(raw: unknown, limits: PromptLimits): {
53
53
  value: string;
54
54
  } | UserScheduleError;
55
55
  /** 用户工具稳定的闭包错误集合(在 dsh-schedule 闭包之上追加本插件专用码)。 */
56
- export type UserScheduleErrorCode = 'invalid_prompt' | 'invalid_selector' | 'invalid_time_zone' | 'not_future' | 'time_out_of_range' | 'frequency_too_high' | 'invalid_rule' | 'schedule_not_found' | 'already_overdue' | 'quota_exceeded' | 'persistence_uncertain' | 'internal_error';
56
+ export type UserScheduleErrorCode = 'invalid_prompt' | 'invalid_selector' | 'invalid_time_zone' | 'not_future' | 'time_out_of_range' | 'frequency_too_high' | 'invalid_rule' | 'schedule_not_found' | 'already_overdue' | 'quota_exceeded' | 'persistence_uncertain' | 'unsupported_kind' | 'not_paused' | 'internal_error';
57
57
  /** 封闭错误值。 */
58
58
  export interface UserScheduleError {
59
59
  readonly ok: false;
@@ -82,6 +82,21 @@ export type UserScheduleDeleteResult = UserScheduleResult<{
82
82
  readonly deleted: false;
83
83
  readonly code: 'schedule_not_found';
84
84
  }>;
85
+ /** `user_schedule_pause` 成功响应(仅 after)。 */
86
+ export type UserSchedulePauseResult = UserScheduleResult<{
87
+ readonly ok: true;
88
+ readonly uid: string;
89
+ readonly schedule_id: string;
90
+ readonly remaining_seconds: number;
91
+ readonly scheduled_at: string;
92
+ }>;
93
+ /** `user_schedule_resume` 成功响应:新 scheduleId + 原 uid。 */
94
+ export type UserScheduleResumeResult = UserScheduleResult<{
95
+ readonly ok: true;
96
+ readonly uid: string;
97
+ readonly schedule_id: string;
98
+ readonly remaining_seconds: number;
99
+ }>;
85
100
  /** `user_schedule_create` 的规范化输入。 */
86
101
  export interface UserScheduleCreateInput {
87
102
  readonly prompt: string;
@@ -121,15 +136,30 @@ export declare function foldOwnedDelivery(events: readonly SessionEvent[]): Read
121
136
  * 消除热路径(每次 drive / 每次工具调用)对全量日志的重复 O(n) 扫描。
122
137
  */
123
138
  export interface UserFoldState {
124
- readonly folded: ReturnType<typeof foldScheduleEvents>;
139
+ /**
140
+ * `foldScheduleEvents` 的结果,按**本插件的读视图**收窄。
141
+ *
142
+ * 0.1.5 返回 `ScheduleRecord`;0.1.7 返回 `LegacyScheduleRecord`
143
+ * (`title` 可选,因为 title 成为必填前写入的历史事件没有该字段),
144
+ * 且 `ScheduleRecord` 联合新增了 daily/weekly/cron 三个本插件不产生的变体。
145
+ *
146
+ * 收窄到 `Legacy*` 视图对读路径是**语义正确**的:本插件只创建
147
+ * after/at/every,title 缺失时投影回退到 prompt(见 projection.ts)。
148
+ * 统一在此收口,避免每个消费点各写一次断言。
149
+ */
150
+ readonly folded: FoldedUserSchedules;
125
151
  readonly owned: ReadonlySet<string>;
126
152
  readonly delivery: ReadonlyMap<string, UserScheduleDelivery>;
127
153
  }
154
+ /** fold 结果中本插件关心的字段面(`active` 按 Legacy 视图收窄)。 */
155
+ export interface FoldedUserSchedules {
156
+ readonly active: readonly ScheduleRecord[];
157
+ readonly seenIds: ReadonlySet<string>;
158
+ }
128
159
  /** fold 当前会话的完整用户调度状态(带缓存;调用方不得变更返回值)。
129
160
  *
130
- * 跨代兼容:0.1.1 暴露 `session.events` + `header.seedLength`;0.1.2 改名
131
- * 为 `session.ownEvents()` + `session.inheritedEventCount`。通过鸭子类型
132
- * (`(session as { ownEvents?; events? })`)双轨探测,不引入 `any`。
161
+ * 跨代 Session API 探测已收敛到 `upstream-compat.ts`(readOwnEvents /
162
+ * readInheritedEventCount)——新 rc 若再改名只动那一处。
133
163
  */
134
164
  export declare function foldUserState(session: Session): UserFoldState;
135
165
  /** 稳定内部错误(不透出异常细节)。 */
@@ -170,7 +200,18 @@ export interface UserScheduleCreateOptions {
170
200
  export declare function userScheduleCreate(input: unknown, agent: Agent, ctx: Context, maxSchedules?: number, options?: UserScheduleCreateOptions, limits?: PromptLimits): Promise<UserScheduleCreateResult>;
171
201
  /** `user_schedule_list` 核心实现。 */
172
202
  export declare function userScheduleList(agent: Agent, ctx: Context): Promise<UserScheduleListResult>;
173
- /** `user_schedule_delete` 核心实现。 */
203
+ /** `user_schedule_delete` 核心实现。id 可为 scheduleId 或稳定 uid。 */
174
204
  export declare function userScheduleDelete(id: unknown, agent: Agent, ctx: Context): Promise<UserScheduleDeleteResult>;
175
205
  /** `user_schedule_edit`(改内容)核心实现:删旧建新,保留原时刻与 delivery。 */
176
206
  export declare function userScheduleEditPrompt(id: unknown, newPrompt: unknown, agent: Agent, ctx: Context, limits?: PromptLimits): Promise<UserScheduleCreateResult | UserScheduleError>;
207
+ /**
208
+ * 暂停一条 **after** 提醒(方案 B):sidecar 留档 + 日志 delete。
209
+ * 仅 kind==='after' 且未到点;恢复见 {@link userScheduleResume}。
210
+ */
211
+ export declare function userSchedulePause(id: unknown, agent: Agent, ctx: Context): Promise<UserSchedulePauseResult>;
212
+ /** 恢复一条暂停中的 after 提醒:`after_seconds = remaining` 重建;uid 不变。 */
213
+ export declare function userScheduleResume(uid: unknown, agent: Agent, ctx: Context, maxSchedules?: number): Promise<UserScheduleResumeResult>;
214
+ /** 测试/投影辅助:某会话全部暂停留档。 */
215
+ export declare function listPausedForSession(sessionId: string): Readonly<Record<string, PausedEntry>>;
216
+ /** 测试辅助:按 pause 前 schedule id 查留档。 */
217
+ export declare function findPausedEntry(sessionId: string, scheduleId: string): PausedEntry | undefined;