@agent-phonon/protocol 0.8.7 → 0.9.1

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.
@@ -0,0 +1,400 @@
1
+ import { z } from "zod";
2
+ import { AgentId, ProjectId, SessionId, TenantId } from "./common.js";
3
+ import { WorkflowId, WorkflowPlan } from "./workflow.js";
4
+
5
+ /**
6
+ * L4 Scheduling Protocol (schedule.* / run.*)
7
+ *
8
+ * Design draft 2026-06-30 (see docs/L4_SCHEDULING.md).
9
+ *
10
+ * 核心原则:
11
+ * - **调度真相源在 device**:daemon 本地持 schedule 表 + 本地时钟触发;server 只是镜像视图 + 管理面。
12
+ * server 断连时定时任务照跑、跑完缓存、重连按同意协议补推。
13
+ * - **不重复发明 run**:一个 run = 一次 L1 session(或 L3 workflow)。run 的 event stream 直接是
14
+ * 该 session 的 stream.event;run 的持久化直接复用 v0.8.7 的 session JSONL transcript 快照。
15
+ * - **webhook 是 trigger 的一种**,与 cron(定时)、manual(手动)并列,最终收敛到同一条
16
+ * 「发起一次 run」的内部路径,只是 triggerSource 不同。
17
+ * - **默认安全**:webhook 是「外部能打进来触发本地 agent」的最高风险入口,每 schedule 独立 token、
18
+ * server 验签、且 webhook 触发的 run 同样过 device consent/policy 门。
19
+ */
20
+
21
+ export const ScheduleId = z.string().min(1).brand<"ScheduleId">();
22
+ export type ScheduleId = z.infer<typeof ScheduleId>;
23
+
24
+ export const RunId = z.string().min(1).brand<"RunId">();
25
+ export type RunId = z.infer<typeof RunId>;
26
+
27
+ // ---------------------------------------------------------------------------
28
+ // Trigger(三态:cron / webhook / manual)
29
+ // ---------------------------------------------------------------------------
30
+
31
+ /** 定时触发:本地时钟按 cron 表达式触发。tz 缺省取设备时区。 */
32
+ export const CronTrigger = z.object({
33
+ kind: z.literal("cron"),
34
+ /** 标准 5 段 cron 表达式:分 时 日 月 周。限长 200,恰好 5 段(防 DoS/畸形输入)。 */
35
+ expr: z
36
+ .string()
37
+ .min(1)
38
+ .max(200)
39
+ .refine((s) => s.trim().split(/\s+/).length === 5, {
40
+ message: "cron expr must have exactly 5 fields (minute hour day month weekday)",
41
+ }),
42
+ /** IANA 时区名,如 "Asia/Shanghai"。缺省用设备本地时区。 */
43
+ tz: z.string().max(64).optional(),
44
+ });
45
+ export type CronTrigger = z.infer<typeof CronTrigger>;
46
+
47
+ /** Webhook 触发:外部 POST /hooks/<token> 命中后由 server 转成一次内部 trigger 下发。 */
48
+ export const WebhookTrigger = z.object({
49
+ kind: z.literal("webhook"),
50
+ /**
51
+ * 该 schedule 专属密钥。创建时由 device 生成并返回一次;
52
+ * schedule.get/list 默认脱敏(与 secrets redaction 一致),仅 reveal 时返回。
53
+ */
54
+ webhookToken: z.string().optional(),
55
+ });
56
+ export type WebhookTrigger = z.infer<typeof WebhookTrigger>;
57
+
58
+ /** 手动触发:仅能由 schedule.trigger 显式发起。 */
59
+ export const ManualTrigger = z.object({
60
+ kind: z.literal("manual"),
61
+ });
62
+ export type ManualTrigger = z.infer<typeof ManualTrigger>;
63
+
64
+ export const ScheduleTrigger = z.discriminatedUnion("kind", [
65
+ CronTrigger,
66
+ WebhookTrigger,
67
+ ManualTrigger,
68
+ ]);
69
+ export type ScheduleTrigger = z.infer<typeof ScheduleTrigger>;
70
+
71
+ // ---------------------------------------------------------------------------
72
+ // Target(到点后要发起的 run 的「配方」)
73
+ // ---------------------------------------------------------------------------
74
+
75
+ export const ScheduleTarget = z.object({
76
+ /** 复用 L1 session 还是 L3 workflow。 */
77
+ runKind: z.enum(["session", "workflow"]).default("session"),
78
+ /** 必须绑定 project(与 session.create 一致)。 */
79
+ project: ProjectId,
80
+ /** runKind=session:用哪个 agent。 */
81
+ agent: AgentId.optional(),
82
+ model: z.string().optional(),
83
+ /** runKind=session:任务文本(webhook body 可注入,见实现)。 */
84
+ prompt: z.string().optional(),
85
+ /** runKind=workflow:L3 plan。 */
86
+ plan: WorkflowPlan.optional(),
87
+ agentConfig: z.record(z.unknown()).optional(),
88
+ skills: z.array(z.string()).optional(),
89
+ });
90
+ export type ScheduleTarget = z.infer<typeof ScheduleTarget>;
91
+
92
+ // ---------------------------------------------------------------------------
93
+ // Consent(同意协议:run 结束后主动推送的粒度)
94
+ // ---------------------------------------------------------------------------
95
+
96
+ /**
97
+ * full = 推完整 event stream(transcript 引用 + 关键事件),server 可完整回放。
98
+ * summary = 只推 resultText + status + usage。
99
+ * status-only = 只推 status + 起止时间,零内容外泄(高敏任务:跑归跑,内容不出设备)。
100
+ */
101
+ export const SchedulePushConsent = z.enum(["full", "summary", "status-only"]);
102
+ export type SchedulePushConsent = z.infer<typeof SchedulePushConsent>;
103
+
104
+ export const ScheduleConsent = z.object({
105
+ push: SchedulePushConsent.default("summary"),
106
+ });
107
+ export type ScheduleConsent = z.infer<typeof ScheduleConsent>;
108
+
109
+ // ---------------------------------------------------------------------------
110
+ // Policy(执行策略,全部可选,默认安全)
111
+ // ---------------------------------------------------------------------------
112
+
113
+ export const SchedulePolicy = z.object({
114
+ /** 单次 run 超时(毫秒)。 */
115
+ timeoutMs: z.number().int().positive().optional(),
116
+ /** 上次 run 还在跑时,新触发如何处理。默认 skip。 */
117
+ overlap: z.enum(["skip", "queue", "allow"]).default("skip"),
118
+ /** 失败重试次数。默认 0。 */
119
+ maxRetries: z.number().int().min(0).default(0),
120
+ /** 错过的 cron 触发点是否补跑(默认 false,避免离线后重连补跑风暴)。 */
121
+ catchUp: z.boolean().default(false),
122
+ });
123
+ export type SchedulePolicy = z.infer<typeof SchedulePolicy>;
124
+
125
+ // ---------------------------------------------------------------------------
126
+ // Schedule(定时任务定义)
127
+ // ---------------------------------------------------------------------------
128
+
129
+ export const Schedule = z.object({
130
+ id: ScheduleId,
131
+ tenantId: TenantId,
132
+ name: z.string().min(1),
133
+ enabled: z.boolean(),
134
+ trigger: ScheduleTrigger,
135
+ target: ScheduleTarget,
136
+ consent: ScheduleConsent,
137
+ policy: SchedulePolicy.optional(),
138
+ createdAt: z.string(),
139
+ updatedAt: z.string(),
140
+ lastRunAt: z.string().optional(),
141
+ /** cron 下次触发的预计算时间(仅 cron)。 */
142
+ nextRunAt: z.string().optional(),
143
+ });
144
+ export type Schedule = z.infer<typeof Schedule>;
145
+
146
+ // ---------------------------------------------------------------------------
147
+ // Run(每次执行记录)
148
+ // ---------------------------------------------------------------------------
149
+
150
+ export const RunStatus = z.enum([
151
+ "pending",
152
+ "running",
153
+ "success",
154
+ "failed",
155
+ "timeout",
156
+ "cancelled",
157
+ "skipped",
158
+ ]);
159
+ export type RunStatus = z.infer<typeof RunStatus>;
160
+
161
+ export const RunTriggerSource = z.enum(["cron", "webhook", "manual"]);
162
+ export type RunTriggerSource = z.infer<typeof RunTriggerSource>;
163
+
164
+ export const Run = z.object({
165
+ id: RunId,
166
+ scheduleId: ScheduleId,
167
+ tenantId: TenantId,
168
+ triggerSource: RunTriggerSource,
169
+ status: RunStatus,
170
+ /** runKind=session:对应的 L1 session。 */
171
+ sessionId: SessionId.optional(),
172
+ /** runKind=workflow:对应的 L3 workflow。 */
173
+ workflowId: WorkflowId.optional(),
174
+ startedAt: z.string().optional(),
175
+ finishedAt: z.string().optional(),
176
+ exitReason: z.string().optional(),
177
+ error: z.string().optional(),
178
+ /** 复用 v0.8.7 session JSONL 快照路径。 */
179
+ transcriptPath: z.string().optional(),
180
+ /** 终态产物文本(summary 推送用)。 */
181
+ resultText: z.string().optional(),
182
+ usage: z.record(z.unknown()).optional(),
183
+ });
184
+ export type Run = z.infer<typeof Run>;
185
+
186
+ // ===========================================================================
187
+ // 方法 params / result
188
+ // ===========================================================================
189
+
190
+ // ---- schedule.create ----
191
+ export const ScheduleCreateParams = z.object({
192
+ name: z.string().min(1),
193
+ trigger: ScheduleTrigger,
194
+ target: ScheduleTarget,
195
+ consent: ScheduleConsent.optional(),
196
+ policy: SchedulePolicy.optional(),
197
+ enabled: z.boolean().optional(),
198
+ clientRequestId: z.string().optional(),
199
+ });
200
+ export type ScheduleCreateParams = z.infer<typeof ScheduleCreateParams>;
201
+
202
+ export const ScheduleCreateResult = z.object({
203
+ schedule: Schedule,
204
+ /** kind=webhook 时返回一次明文 token(之后默认脱敏)。 */
205
+ webhookToken: z.string().optional(),
206
+ });
207
+ export type ScheduleCreateResult = z.infer<typeof ScheduleCreateResult>;
208
+
209
+ // ---- schedule.update ----
210
+ export const ScheduleUpdateParams = z.object({
211
+ scheduleId: ScheduleId,
212
+ name: z.string().min(1).optional(),
213
+ enabled: z.boolean().optional(),
214
+ trigger: ScheduleTrigger.optional(),
215
+ target: ScheduleTarget.optional(),
216
+ consent: ScheduleConsent.optional(),
217
+ policy: SchedulePolicy.optional(),
218
+ clientRequestId: z.string().optional(),
219
+ });
220
+ export type ScheduleUpdateParams = z.infer<typeof ScheduleUpdateParams>;
221
+
222
+ export const ScheduleUpdateResult = z.object({ schedule: Schedule });
223
+ export type ScheduleUpdateResult = z.infer<typeof ScheduleUpdateResult>;
224
+
225
+ // ---- schedule.delete ----
226
+ export const ScheduleDeleteParams = z.object({
227
+ scheduleId: ScheduleId,
228
+ clientRequestId: z.string().optional(),
229
+ });
230
+ export type ScheduleDeleteParams = z.infer<typeof ScheduleDeleteParams>;
231
+
232
+ export const ScheduleDeleteResult = z.object({
233
+ scheduleId: ScheduleId,
234
+ deleted: z.boolean(),
235
+ });
236
+ export type ScheduleDeleteResult = z.infer<typeof ScheduleDeleteResult>;
237
+
238
+ // ---- schedule.list ----
239
+ export const ScheduleListParams = z.object({
240
+ enabled: z.boolean().optional(),
241
+ triggerKind: z.enum(["cron", "webhook", "manual"]).optional(),
242
+ /** 是否返回明文 webhookToken(默认脱敏)。 */
243
+ reveal: z.boolean().optional(),
244
+ limit: z.number().int().positive().optional(),
245
+ });
246
+ export type ScheduleListParams = z.infer<typeof ScheduleListParams>;
247
+
248
+ export const ScheduleListResult = z.object({
249
+ schedules: z.array(Schedule),
250
+ });
251
+ export type ScheduleListResult = z.infer<typeof ScheduleListResult>;
252
+
253
+ // ---- schedule.get ----
254
+ export const ScheduleGetParams = z.object({
255
+ scheduleId: ScheduleId,
256
+ reveal: z.boolean().optional(),
257
+ });
258
+ export type ScheduleGetParams = z.infer<typeof ScheduleGetParams>;
259
+
260
+ export const ScheduleGetResult = z.object({ schedule: Schedule });
261
+ export type ScheduleGetResult = z.infer<typeof ScheduleGetResult>;
262
+
263
+ // ---- schedule.enable / schedule.disable ----
264
+ export const ScheduleEnableParams = z.object({
265
+ scheduleId: ScheduleId,
266
+ clientRequestId: z.string().optional(),
267
+ });
268
+ export type ScheduleEnableParams = z.infer<typeof ScheduleEnableParams>;
269
+
270
+ export const ScheduleEnableResult = z.object({ schedule: Schedule });
271
+ export type ScheduleEnableResult = z.infer<typeof ScheduleEnableResult>;
272
+
273
+ // ---- schedule.trigger(手动触发一次 run)----
274
+ export const ScheduleTriggerParams = z.object({
275
+ scheduleId: ScheduleId,
276
+ /** 触发来源标记(manual / 测试 cron / 重放 webhook)。默认 manual。 */
277
+ source: RunTriggerSource.optional(),
278
+ /** 可选:注入到 target.prompt 的输入变量(webhook body 等)。 */
279
+ input: z.record(z.unknown()).optional(),
280
+ clientRequestId: z.string().optional(),
281
+ });
282
+ export type ScheduleTriggerParams = z.infer<typeof ScheduleTriggerParams>;
283
+
284
+ export const ScheduleTriggerResult = z.object({
285
+ scheduleId: ScheduleId,
286
+ runId: RunId,
287
+ status: RunStatus,
288
+ });
289
+ export type ScheduleTriggerResult = z.infer<typeof ScheduleTriggerResult>;
290
+
291
+ // ---- schedule.runs.list ----
292
+ export const ScheduleRunsListParams = z.object({
293
+ scheduleId: ScheduleId,
294
+ status: RunStatus.optional(),
295
+ limit: z.number().int().positive().optional(),
296
+ });
297
+ export type ScheduleRunsListParams = z.infer<typeof ScheduleRunsListParams>;
298
+
299
+ export const ScheduleRunsListResult = z.object({
300
+ runs: z.array(Run),
301
+ });
302
+ export type ScheduleRunsListResult = z.infer<typeof ScheduleRunsListResult>;
303
+
304
+ // ---- run.get ----
305
+ export const RunGetParams = z.object({
306
+ runId: RunId,
307
+ });
308
+ export type RunGetParams = z.infer<typeof RunGetParams>;
309
+
310
+ export const RunGetResult = z.object({ run: Run });
311
+ export type RunGetResult = z.infer<typeof RunGetResult>;
312
+
313
+ // ---- run.events.subscribe / unsubscribe ----
314
+ export const RunEventsSubscribeParams = z.object({
315
+ runId: RunId,
316
+ });
317
+ export type RunEventsSubscribeParams = z.infer<typeof RunEventsSubscribeParams>;
318
+
319
+ export const RunEventsSubscribeResult = z.object({
320
+ runId: RunId,
321
+ subscribed: z.boolean(),
322
+ /** 若 run 已绑定 session,回显其 sessionId,便于 server 关联 stream.event。 */
323
+ sessionId: SessionId.optional(),
324
+ });
325
+ export type RunEventsSubscribeResult = z.infer<typeof RunEventsSubscribeResult>;
326
+
327
+ export const RunEventsUnsubscribeParams = z.object({
328
+ runId: RunId,
329
+ });
330
+ export type RunEventsUnsubscribeParams = z.infer<typeof RunEventsUnsubscribeParams>;
331
+
332
+ export const RunEventsUnsubscribeResult = z.object({
333
+ runId: RunId,
334
+ subscribed: z.boolean(),
335
+ });
336
+ export type RunEventsUnsubscribeResult = z.infer<typeof RunEventsUnsubscribeResult>;
337
+
338
+ // ---- run.cancel ----
339
+ export const RunCancelParams = z.object({
340
+ runId: RunId,
341
+ reason: z.string().optional(),
342
+ clientRequestId: z.string().optional(),
343
+ });
344
+ export type RunCancelParams = z.infer<typeof RunCancelParams>;
345
+
346
+ export const RunCancelResult = z.object({
347
+ runId: RunId,
348
+ status: RunStatus,
349
+ });
350
+ export type RunCancelResult = z.infer<typeof RunCancelResult>;
351
+
352
+ // ===========================================================================
353
+ // phonon → server 通知(镜像 + 推送)
354
+ // ===========================================================================
355
+
356
+ /** device 本地 schedule 变化主动同步给 server(含 cron 算出的新 nextRunAt)。 */
357
+ export const ScheduleChangedParams = z.object({
358
+ schedule: Schedule.optional(),
359
+ /** 删除场景:只带 scheduleId + deleted。 */
360
+ scheduleId: ScheduleId.optional(),
361
+ deleted: z.boolean().optional(),
362
+ });
363
+ export type ScheduleChangedParams = z.infer<typeof ScheduleChangedParams>;
364
+
365
+ /** run 开始。 */
366
+ export const RunStartedParams = z.object({
367
+ run: Run,
368
+ });
369
+ export type RunStartedParams = z.infer<typeof RunStartedParams>;
370
+
371
+ /**
372
+ * run 过程事件(订阅了才推)。本质是带 runId 标记的 session stream.event 转发。
373
+ * 单调 seq + ack 复用 outbox 思路。
374
+ */
375
+ export const RunEventParams = z.object({
376
+ runId: RunId,
377
+ scheduleId: ScheduleId,
378
+ seq: z.number().int().nonnegative(),
379
+ /** 原始 session stream.event(透传)。 */
380
+ event: z.record(z.unknown()),
381
+ });
382
+ export type RunEventParams = z.infer<typeof RunEventParams>;
383
+
384
+ /** run 终态 + 按 consent.push 决定的 payload。 */
385
+ export const RunFinishedParams = z.object({
386
+ run: Run,
387
+ /** 推送粒度(回显,便于 server 知道拿到的是哪一档)。 */
388
+ push: SchedulePushConsent,
389
+ });
390
+ export type RunFinishedParams = z.infer<typeof RunFinishedParams>;
391
+
392
+ /** server 确认收到 run.finished / run.event,phonon 清推送 outbox。 */
393
+ export const ScheduleAckParams = z.object({
394
+ runId: RunId,
395
+ /** 确认已收 seq≤lastSeq(run.event 流)。 */
396
+ lastSeq: z.number().int().nonnegative().optional(),
397
+ /** 确认 run.finished 已落库。 */
398
+ finished: z.boolean().optional(),
399
+ });
400
+ export type ScheduleAckParams = z.infer<typeof ScheduleAckParams>;