@zhushanwen/extension-protocol 0.13.0 → 0.14.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/dist/index.d.ts +191 -1
- package/dist/index.js +182 -0
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -80,6 +80,26 @@ interface GuiComponentProps {
|
|
|
80
80
|
*/
|
|
81
81
|
sections?: GuiComponent[][];
|
|
82
82
|
};
|
|
83
|
+
/**
|
|
84
|
+
* 操作按钮栏——GuiComponent 协议首个交互原语(设计 AP-3)。
|
|
85
|
+
* 点击经宿主壳 provide 的执行器走既有命令链(commandRegistry.execute → WS
|
|
86
|
+
* plugin.executeCommand),协议不新增回调通道(设计 §3.3 D2)。
|
|
87
|
+
* 未含此 type 的旧宿主自动降级 ansi-text 可读文本,PROTOCOL_VERSION 不 bump
|
|
88
|
+
* (设计 §3.3 D8,降级契约 P5)。
|
|
89
|
+
*/
|
|
90
|
+
'action-bar': {
|
|
91
|
+
items: {
|
|
92
|
+
id: string;
|
|
93
|
+
label: string;
|
|
94
|
+
/** 视觉档位:只保留当前唯一实际消费的 danger(删除按钮);更多档位有真实消费方时再加 */
|
|
95
|
+
kind?: 'danger';
|
|
96
|
+
disabled?: boolean;
|
|
97
|
+
/** 缺省 = 纯展示项(不可点、弱化样式) */
|
|
98
|
+
commandId?: string;
|
|
99
|
+
/** 只许标量:args 原样传给插件 handler,禁嵌套对象防无 schema 结构化旁路(设计 AP-3 校验) */
|
|
100
|
+
args?: Record<string, string | number | boolean>;
|
|
101
|
+
}[];
|
|
102
|
+
};
|
|
83
103
|
/** 自定义组件——逃生口(仅限内置 extension 编译期注册) */
|
|
84
104
|
'custom': {
|
|
85
105
|
component: string;
|
|
@@ -504,6 +524,176 @@ declare function getAskUserOther(answers: AskUserAnswers, question: AskUserQuest
|
|
|
504
524
|
*/
|
|
505
525
|
declare function isAskUserQuestion(value: unknown): value is AskUserQuestion;
|
|
506
526
|
|
|
527
|
+
type ScheduleSpec = {
|
|
528
|
+
mode: 'cron';
|
|
529
|
+
cronExpression: string;
|
|
530
|
+
} | {
|
|
531
|
+
mode: 'interval';
|
|
532
|
+
intervalMs: number;
|
|
533
|
+
};
|
|
534
|
+
type TaskKind = 'once' | 'recurring';
|
|
535
|
+
type TaskStatus = 'success' | 'failed';
|
|
536
|
+
interface ScheduledTask {
|
|
537
|
+
id: string;
|
|
538
|
+
name: string;
|
|
539
|
+
prompt: string;
|
|
540
|
+
kind: TaskKind;
|
|
541
|
+
schedule: ScheduleSpec;
|
|
542
|
+
model?: string;
|
|
543
|
+
enabled: boolean;
|
|
544
|
+
createdAt: number;
|
|
545
|
+
nextRunAt: number;
|
|
546
|
+
expiresAt?: number;
|
|
547
|
+
runCount: number;
|
|
548
|
+
lastRunAt?: number;
|
|
549
|
+
lastStatus?: TaskStatus;
|
|
550
|
+
lastError?: string;
|
|
551
|
+
history: ExecutionRecord[];
|
|
552
|
+
ownerSessionFile?: string;
|
|
553
|
+
pending?: boolean;
|
|
554
|
+
}
|
|
555
|
+
interface ExecutionRecord {
|
|
556
|
+
at: number;
|
|
557
|
+
status: TaskStatus;
|
|
558
|
+
}
|
|
559
|
+
/**
|
|
560
|
+
* history 裁剪上限(ext-simplify-08 L5 单点):runtime 侧 dispatch 累积
|
|
561
|
+
* (onDispatchSuccess)与 replay 侧 advance 折叠共用同一上限——
|
|
562
|
+
* 双处各持字面量会漂移(曾 runtime.ts / replay.ts 各写一份 20),收敛到本文件单点导出。
|
|
563
|
+
*/
|
|
564
|
+
declare const HISTORY_LIMIT = 20;
|
|
565
|
+
/**
|
|
566
|
+
* 追加执行记录并按 HISTORY_LIMIT 裁剪(ext-simplify-17 B4 单点):runtime 侧 dispatch
|
|
567
|
+
* 累积与 replay 侧 advance 折叠共用的 push+trim——三处内联时与上限字面量同源存在
|
|
568
|
+
* 「漏改一处」漂移风险,随上限一并收敛。
|
|
569
|
+
*/
|
|
570
|
+
declare function appendExecutionRecord(task: ScheduledTask, at: number, status: TaskStatus): void;
|
|
571
|
+
/**
|
|
572
|
+
* pi.appendEntry 写入的 custom entry customType 标识(ext-simplify-17 B1 单点):
|
|
573
|
+
* backend.appendEntry 写入 / replay 折叠过滤 / importer 旧 store 导入三处共用——
|
|
574
|
+
* 各持字面量时「改一处漏两处」会使写入与重放识别脱节(写入新标识、重放不认,任务静默丢失)。
|
|
575
|
+
*/
|
|
576
|
+
declare const TASK_ENTRY_TYPE = "pi-scheduler:task";
|
|
577
|
+
/**
|
|
578
|
+
* upsert op 携带的全量任务快照(不含 ownerSessionFile / pending)。
|
|
579
|
+
* ownerSessionFile 放在 op 顶层(见 SchedulerEntryOp.upsert),
|
|
580
|
+
* pending 是运行时标记不持久化。其余字段与 ScheduledTask 对齐。
|
|
581
|
+
*/
|
|
582
|
+
interface TaskSnapshot {
|
|
583
|
+
id: string;
|
|
584
|
+
name: string;
|
|
585
|
+
prompt: string;
|
|
586
|
+
kind: TaskKind;
|
|
587
|
+
schedule: ScheduleSpec;
|
|
588
|
+
model?: string;
|
|
589
|
+
enabled: boolean;
|
|
590
|
+
createdAt: number;
|
|
591
|
+
nextRunAt: number;
|
|
592
|
+
expiresAt?: number;
|
|
593
|
+
runCount: number;
|
|
594
|
+
lastRunAt?: number;
|
|
595
|
+
lastStatus?: TaskStatus;
|
|
596
|
+
lastError?: string;
|
|
597
|
+
history: ExecutionRecord[];
|
|
598
|
+
}
|
|
599
|
+
/**
|
|
600
|
+
* pi.appendEntry(TASK_ENTRY_TYPE, SchedulerEntryOp) 写入 session JSONL 的 op 联合类型。
|
|
601
|
+
*
|
|
602
|
+
* advance.status 固定为 'success'(CL8):对齐现有 TaskStatus='success'|'failed',
|
|
603
|
+
* 而非继承的 DM-SCHEDULER-OP 词表 'ok'。唯一 emit 值——按 CL7,advance 仅在 dispatch
|
|
604
|
+
* 成功(nextRunAt 实际推进)时 append,失败 dispatch 不 append(transient 失败重试语义)。
|
|
605
|
+
* fold 的 `task.lastStatus = entry.status` 直接赋值合法('success' 属 TaskStatus)。
|
|
606
|
+
*/
|
|
607
|
+
type SchedulerEntryOp = {
|
|
608
|
+
op: 'upsert';
|
|
609
|
+
taskId: string;
|
|
610
|
+
ownerSessionFile: string;
|
|
611
|
+
task: TaskSnapshot;
|
|
612
|
+
} | {
|
|
613
|
+
op: 'advance';
|
|
614
|
+
taskId: string;
|
|
615
|
+
nextRunAt: number;
|
|
616
|
+
at: number;
|
|
617
|
+
status: 'success';
|
|
618
|
+
} | {
|
|
619
|
+
op: 'toggle';
|
|
620
|
+
taskId: string;
|
|
621
|
+
enabled: boolean;
|
|
622
|
+
nextRunAt?: number;
|
|
623
|
+
} | {
|
|
624
|
+
op: 'delete';
|
|
625
|
+
taskId: string;
|
|
626
|
+
};
|
|
627
|
+
/**
|
|
628
|
+
* TaskSnapshot → ScheduledTask(replay 侧重建):pending 不持久化故不恢复,
|
|
629
|
+
* ownerSessionFile 由调用方(replay applyUpsert)从 op 顶层补回。history 逐项深拷贝——
|
|
630
|
+
* 避免恢复出的运行时 task 与快照共享数组/元素引用(task 后续被 mutate 时不污染快照)。
|
|
631
|
+
*/
|
|
632
|
+
declare function snapshotToTask(snapshot: TaskSnapshot): ScheduledTask;
|
|
633
|
+
|
|
634
|
+
/**
|
|
635
|
+
* 折叠期降级日志注入点:包内不持有任何 logger 依赖(零 pi 依赖收敛),由调用方注入。
|
|
636
|
+
* 省略时降级路径静默(折叠行为不受影响——返回值与注入无关)。
|
|
637
|
+
*/
|
|
638
|
+
interface ReplayFoldOptions {
|
|
639
|
+
warn?: (message: string, context?: unknown) => void;
|
|
640
|
+
}
|
|
641
|
+
/**
|
|
642
|
+
* CustomEntry 的最小可识别形状(duck-typed,不依赖 @earendil-works/pi-coding-agent 的
|
|
643
|
+
* 具体 SessionEntry 类型)。与 pending-notifications/state.ts、goal/ports.ts 同款:
|
|
644
|
+
* 纯函数可独立单测,调用方传入真实 SessionEntry[] 时按结构兼容。
|
|
645
|
+
*/
|
|
646
|
+
interface SchedulerEntryLike {
|
|
647
|
+
type: string;
|
|
648
|
+
customType?: string;
|
|
649
|
+
data?: unknown;
|
|
650
|
+
}
|
|
651
|
+
/**
|
|
652
|
+
* 从 CustomEntry 序列折叠恢复 per-task 末态(event sourcing)。
|
|
653
|
+
*
|
|
654
|
+
* 两类调用方:
|
|
655
|
+
* - pi 扩展 session_start 重放:PiSchedulerBackend.loadTasks() 委托本函数,把当前 session 的
|
|
656
|
+
* TASK_ENTRY_TYPE custom entries 折叠成运行时任务 Map。
|
|
657
|
+
* - taiji plugin 条目镜像:对 readEntries 拉取的累计 entries 调用本函数折叠出任务表
|
|
658
|
+
* (与扩展同一实现,插件与扩展折叠结果构造性一致)。
|
|
659
|
+
*
|
|
660
|
+
* 两步:
|
|
661
|
+
* ① 全量折叠(foldEntries)——遍历 customType===TASK_ENTRY_TYPE 的 entries,按 op 类型折叠:
|
|
662
|
+
* - upsert → 用快照重建任务(ownerSessionFile 来自 op 顶层)
|
|
663
|
+
* - advance → nextRunAt=新值 / lastRunAt=at / runCount++ / history.push(裁20) / lastStatus=status(gap2)
|
|
664
|
+
* - toggle → enabled=新值
|
|
665
|
+
* - delete → 从 Map 移除(后续同 taskId 的 advance/toggle 对不存在 taskId 为 no-op,安全)
|
|
666
|
+
* advance/toggle/delete 对不存在的 taskId 为 no-op,因此 fork 继承的完整序列必须先全量折叠,
|
|
667
|
+
* 不能在处理 upsert 时按 owner 跳过(否则后续无 owner 字段的 op 会作用于「不存在的 taskId」丢失)。
|
|
668
|
+
* ② 折叠完成后按 task.ownerSessionFile !== currentSessionFile 整体过滤(gap1,filterNonOwnerTasks):
|
|
669
|
+
* 移除 fork 继承的非 owner 任务——fork 出的 session B 继承了 owner=A 的 upsert+advance 序列,
|
|
670
|
+
* 但 B 不应拥有 A 的任务,重放后整体过滤掉。
|
|
671
|
+
*
|
|
672
|
+
* append-only 时序保证 nextRunAt 不回退(D1):advance entry 按写入顺序折叠,自然取到最后推进值。
|
|
673
|
+
*
|
|
674
|
+
* getEntries 解析/迭代异常 → options.warn + 返回空 Map(gap4):降级为无任务而非崩溃调用方。
|
|
675
|
+
*/
|
|
676
|
+
declare function replayFoldEntries(entries: Iterable<SchedulerEntryLike>, currentSessionFile: string | undefined, options?: ReplayFoldOptions): Map<string, ScheduledTask>;
|
|
677
|
+
|
|
678
|
+
declare const MS_PER_DAY = 86400000;
|
|
679
|
+
declare const MS_PER_HOUR = 3600000;
|
|
680
|
+
declare const MS_PER_MINUTE = 60000;
|
|
681
|
+
declare const MS_PER_SECOND = 1000;
|
|
682
|
+
/** 格式化毫秒数为可读 duration 字符串。优先使用最大单位:300000 → "5m",不是 "300s"。 */
|
|
683
|
+
declare function formatDuration(ms: number): string;
|
|
684
|
+
/** Format ScheduleSpec to readable string. kind 区分 once/recurring(once 显示 'once in X' 而非误导性的 'every X')。 */
|
|
685
|
+
declare function formatSchedule(spec: ScheduleSpec, kind?: TaskKind): string;
|
|
686
|
+
/**
|
|
687
|
+
* 格式化时间戳为相对时间字符串。
|
|
688
|
+
* 未来: "in 5m"
|
|
689
|
+
* 过去: "5m ago"
|
|
690
|
+
* 当前(+-5s): "now"
|
|
691
|
+
*
|
|
692
|
+
* now 可选参数:基准时间戳,默认 Date.now()。测试可传固定值快进/锁定,
|
|
693
|
+
* 生产调用方无需传(参数可选,行为不变)。
|
|
694
|
+
*/
|
|
695
|
+
declare function formatRelativeTime(timestamp: number, now?: number): string;
|
|
696
|
+
|
|
507
697
|
/**
|
|
508
698
|
* scheduler 创建确认弹框的协议类型(Draft / FormResult)。
|
|
509
699
|
*
|
|
@@ -1170,4 +1360,4 @@ declare function collectActivePendingIds(entries: unknown[], opts?: CollectPendi
|
|
|
1170
1360
|
type MappedPendingStatus = 'completed' | 'failed' | 'cancelled' | 'expired' | 'time_limited' | 'aborted';
|
|
1171
1361
|
declare function mapReasonToStatus(reason: string): MappedPendingStatus;
|
|
1172
1362
|
|
|
1173
|
-
export { ASK_USER_MARKER, type AskUserAnswers, type AskUserOption, type AskUserQuestion, BRIDGE_MARKER, BRIDGE_METHODS, type BridgeErrorResponse, type BridgeInterceptResponse, type BridgeMethod, type BridgeRequest, type BridgeSyncPayload, type BridgeToolExecuteResponse, type ChannelErrorResult, type ChoiceQuestion, type CollectPendingIdsOptions, type DualWidgetContent, type FormAnswers, type FormOption, type FormQuestion, GUI_WIDGET_MARKER, type GuiComponent, type GuiComponentProps, type GuiComponentType, type GuiContext, type GuiRenderResult, INFLIGHT_REPORT_ACK, type MappedPendingStatus, type MarkerRpcOptions, type MarkerRpcResult, PENDING_REGISTER_ENTRY_TYPE, PENDING_UNREGISTER_ENTRY_TYPE, PLAN_REVIEW_MARKER, PROTOCOL_VERSION, type PendingEntriesScan, type PlanDocMeta, type PlanReviewComment, type PlanReviewDecision, type PlanReviewRequest, type PlanReviewResponse, SCHEDULE_CREATE_MARKER, SESSION_MANAGER_ACTIONS, SESSION_MANAGER_MARKER, SUBAGENTS_ENGINES_FILENAME, SUBAGENT_INFLIGHT_MARKER, type ScheduleDraft, type ScheduleFormResult, type ScheduleKind, type ScheduleQuestion, type SessionManagerAbortParams, type SessionManagerAbortResult, type SessionManagerAction, type SessionManagerCreateParams, type SessionManagerCreateResult, type SessionManagerErrorResult, type SessionManagerHistoryParams, type SessionManagerHistoryResult, type SessionManagerListParams, type SessionManagerListResult, type SessionManagerParams, type SessionManagerSendParams, type SessionManagerSendResult, type SessionManagerSessionSummary, type SessionManagerStatusParams, type SessionManagerStatusResult, type StatItem, type SubagentEngineConfigView, type SubagentEnginesFile, type SubagentInFlightReport, type TextQuestion, type TreeItem, type TreeItemIcon, UI_FORM_MARKER, type UiFormInteractOptions, type UiFormInteractResult, type WidgetIconPathsRejection, type WidgetIconPathsValidation, type WidgetMeta, applyPendingDiff, callMarkerRpc, collectActivePendingIds, dateToOnceCron, extractGui, firstContentText, formatChannelErrorText, getAskUserAnswer, getAskUserOther, guiComponent, guiResult, guiSetWidget, isAskUserQuestion, isBridgeErrorResponse, isBridgeInterceptResponse, isBridgeSyncPayload, isBridgeToolExecuteResponse, isChannelErrorResult, isFormAnswers, isFormQuestion, isGuiCapable, isGuiComponent, isGuiRenderResult, isInFlightReportAck, isScheduleDraft, isScheduleFormResult, isSessionManagerAbortParams, isSessionManagerCreateParams, isSessionManagerHistoryParams, isSessionManagerListParams, isSessionManagerSendParams, isSessionManagerStatusParams, isSubagentInFlightReport, isSyncedTool, mapReasonToStatus, onceCronToDate, scanPendingEntries, setWidgetDual, uiFormInteract, validateWidgetIconPaths };
|
|
1363
|
+
export { ASK_USER_MARKER, type AskUserAnswers, type AskUserOption, type AskUserQuestion, BRIDGE_MARKER, BRIDGE_METHODS, type BridgeErrorResponse, type BridgeInterceptResponse, type BridgeMethod, type BridgeRequest, type BridgeSyncPayload, type BridgeToolExecuteResponse, type ChannelErrorResult, type ChoiceQuestion, type CollectPendingIdsOptions, type DualWidgetContent, type ExecutionRecord, type FormAnswers, type FormOption, type FormQuestion, GUI_WIDGET_MARKER, type GuiComponent, type GuiComponentProps, type GuiComponentType, type GuiContext, type GuiRenderResult, HISTORY_LIMIT, INFLIGHT_REPORT_ACK, MS_PER_DAY, MS_PER_HOUR, MS_PER_MINUTE, MS_PER_SECOND, type MappedPendingStatus, type MarkerRpcOptions, type MarkerRpcResult, PENDING_REGISTER_ENTRY_TYPE, PENDING_UNREGISTER_ENTRY_TYPE, PLAN_REVIEW_MARKER, PROTOCOL_VERSION, type PendingEntriesScan, type PlanDocMeta, type PlanReviewComment, type PlanReviewDecision, type PlanReviewRequest, type PlanReviewResponse, type ReplayFoldOptions, SCHEDULE_CREATE_MARKER, SESSION_MANAGER_ACTIONS, SESSION_MANAGER_MARKER, SUBAGENTS_ENGINES_FILENAME, SUBAGENT_INFLIGHT_MARKER, type ScheduleDraft, type ScheduleFormResult, type ScheduleKind, type ScheduleQuestion, type ScheduleSpec, type ScheduledTask, type SchedulerEntryLike, type SchedulerEntryOp, type SessionManagerAbortParams, type SessionManagerAbortResult, type SessionManagerAction, type SessionManagerCreateParams, type SessionManagerCreateResult, type SessionManagerErrorResult, type SessionManagerHistoryParams, type SessionManagerHistoryResult, type SessionManagerListParams, type SessionManagerListResult, type SessionManagerParams, type SessionManagerSendParams, type SessionManagerSendResult, type SessionManagerSessionSummary, type SessionManagerStatusParams, type SessionManagerStatusResult, type StatItem, type SubagentEngineConfigView, type SubagentEnginesFile, type SubagentInFlightReport, TASK_ENTRY_TYPE, type TaskKind, type TaskSnapshot, type TaskStatus, type TextQuestion, type TreeItem, type TreeItemIcon, UI_FORM_MARKER, type UiFormInteractOptions, type UiFormInteractResult, type WidgetIconPathsRejection, type WidgetIconPathsValidation, type WidgetMeta, appendExecutionRecord, applyPendingDiff, callMarkerRpc, collectActivePendingIds, dateToOnceCron, extractGui, firstContentText, formatChannelErrorText, formatDuration, formatRelativeTime, formatSchedule, getAskUserAnswer, getAskUserOther, guiComponent, guiResult, guiSetWidget, isAskUserQuestion, isBridgeErrorResponse, isBridgeInterceptResponse, isBridgeSyncPayload, isBridgeToolExecuteResponse, isChannelErrorResult, isFormAnswers, isFormQuestion, isGuiCapable, isGuiComponent, isGuiRenderResult, isInFlightReportAck, isScheduleDraft, isScheduleFormResult, isSessionManagerAbortParams, isSessionManagerCreateParams, isSessionManagerHistoryParams, isSessionManagerListParams, isSessionManagerSendParams, isSessionManagerStatusParams, isSubagentInFlightReport, isSyncedTool, mapReasonToStatus, onceCronToDate, replayFoldEntries, scanPendingEntries, setWidgetDual, snapshotToTask, uiFormInteract, validateWidgetIconPaths };
|
package/dist/index.js
CHANGED
|
@@ -185,6 +185,176 @@ function isAskUserQuestion(value) {
|
|
|
185
185
|
return typeof q.question === "string" && (q.header === void 0 || typeof q.header === "string") && (q.options === void 0 || Array.isArray(q.options)) && (q.multiSelect === void 0 || typeof q.multiSelect === "boolean") && (q.allowOther === void 0 || typeof q.allowOther === "boolean");
|
|
186
186
|
}
|
|
187
187
|
|
|
188
|
+
// src/extensions/scheduler/types.ts
|
|
189
|
+
var HISTORY_LIMIT = 20;
|
|
190
|
+
function appendExecutionRecord(task, at, status) {
|
|
191
|
+
task.history.push({ at, status });
|
|
192
|
+
if (task.history.length > HISTORY_LIMIT) task.history.shift();
|
|
193
|
+
}
|
|
194
|
+
var TASK_ENTRY_TYPE = "pi-scheduler:task";
|
|
195
|
+
function snapshotToTask(snapshot) {
|
|
196
|
+
return {
|
|
197
|
+
id: snapshot.id,
|
|
198
|
+
name: snapshot.name,
|
|
199
|
+
prompt: snapshot.prompt,
|
|
200
|
+
kind: snapshot.kind,
|
|
201
|
+
schedule: snapshot.schedule,
|
|
202
|
+
model: snapshot.model,
|
|
203
|
+
enabled: snapshot.enabled,
|
|
204
|
+
createdAt: snapshot.createdAt,
|
|
205
|
+
nextRunAt: snapshot.nextRunAt,
|
|
206
|
+
expiresAt: snapshot.expiresAt,
|
|
207
|
+
runCount: snapshot.runCount,
|
|
208
|
+
lastRunAt: snapshot.lastRunAt,
|
|
209
|
+
lastStatus: snapshot.lastStatus,
|
|
210
|
+
lastError: snapshot.lastError,
|
|
211
|
+
history: snapshot.history.map((h) => ({ ...h }))
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// src/extensions/scheduler/replay.ts
|
|
216
|
+
function toErrorMessage(e) {
|
|
217
|
+
return e instanceof Error ? e.message : String(e);
|
|
218
|
+
}
|
|
219
|
+
function replayFoldEntries(entries, currentSessionFile, options) {
|
|
220
|
+
try {
|
|
221
|
+
const tasks = /* @__PURE__ */ new Map();
|
|
222
|
+
foldEntries(entries, tasks, options);
|
|
223
|
+
filterNonOwnerTasks(tasks, currentSessionFile);
|
|
224
|
+
return tasks;
|
|
225
|
+
} catch (err) {
|
|
226
|
+
options?.warn?.("replayFoldEntries failed", { error: toErrorMessage(err) });
|
|
227
|
+
return /* @__PURE__ */ new Map();
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
function foldEntries(entries, tasks, options) {
|
|
231
|
+
for (const entry of entries) {
|
|
232
|
+
if (entry.type !== "custom" || entry.customType !== TASK_ENTRY_TYPE) continue;
|
|
233
|
+
if (!isSchedulerEntryOp(entry.data)) continue;
|
|
234
|
+
const op = entry.data;
|
|
235
|
+
try {
|
|
236
|
+
applyOp(tasks, op);
|
|
237
|
+
} catch (err) {
|
|
238
|
+
options?.warn?.("skipping corrupted scheduler entry", { error: toErrorMessage(err) });
|
|
239
|
+
continue;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
function applyOp(tasks, op) {
|
|
244
|
+
switch (op.op) {
|
|
245
|
+
case "upsert":
|
|
246
|
+
applyUpsert(tasks, op);
|
|
247
|
+
break;
|
|
248
|
+
case "advance":
|
|
249
|
+
applyAdvance(tasks, op);
|
|
250
|
+
break;
|
|
251
|
+
case "toggle":
|
|
252
|
+
applyToggle(tasks, op);
|
|
253
|
+
break;
|
|
254
|
+
case "delete":
|
|
255
|
+
applyDelete(tasks, op);
|
|
256
|
+
break;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
function applyUpsert(tasks, op) {
|
|
260
|
+
tasks.set(op.taskId, { ...snapshotToTask(op.task), ownerSessionFile: op.ownerSessionFile });
|
|
261
|
+
}
|
|
262
|
+
function applyAdvance(tasks, op) {
|
|
263
|
+
const task = tasks.get(op.taskId);
|
|
264
|
+
if (!task) return;
|
|
265
|
+
task.nextRunAt = op.nextRunAt;
|
|
266
|
+
task.lastRunAt = op.at;
|
|
267
|
+
task.runCount += 1;
|
|
268
|
+
appendExecutionRecord(task, op.at, op.status);
|
|
269
|
+
task.lastStatus = op.status;
|
|
270
|
+
}
|
|
271
|
+
function applyToggle(tasks, op) {
|
|
272
|
+
const task = tasks.get(op.taskId);
|
|
273
|
+
if (!task) return;
|
|
274
|
+
task.enabled = op.enabled;
|
|
275
|
+
if (op.nextRunAt !== void 0) task.nextRunAt = op.nextRunAt;
|
|
276
|
+
}
|
|
277
|
+
function applyDelete(tasks, op) {
|
|
278
|
+
tasks.delete(op.taskId);
|
|
279
|
+
}
|
|
280
|
+
function filterNonOwnerTasks(tasks, currentSessionFile) {
|
|
281
|
+
for (const [id, task] of tasks) {
|
|
282
|
+
if (task.ownerSessionFile !== currentSessionFile) {
|
|
283
|
+
tasks.delete(id);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
function isSchedulerEntryOp(data) {
|
|
288
|
+
if (!data || typeof data !== "object") return false;
|
|
289
|
+
const record = data;
|
|
290
|
+
const op = record.op;
|
|
291
|
+
if (op === "upsert") {
|
|
292
|
+
return typeof record.taskId === "string" && typeof record.task === "object" && record.task !== null;
|
|
293
|
+
}
|
|
294
|
+
if (op === "advance") {
|
|
295
|
+
return typeof record.taskId === "string" && typeof record.nextRunAt === "number" && typeof record.at === "number" && record.status === "success";
|
|
296
|
+
}
|
|
297
|
+
if (op === "toggle") {
|
|
298
|
+
return typeof record.taskId === "string" && typeof record.enabled === "boolean" && (record.nextRunAt === void 0 || typeof record.nextRunAt === "number");
|
|
299
|
+
}
|
|
300
|
+
if (op === "delete") {
|
|
301
|
+
return typeof record.taskId === "string";
|
|
302
|
+
}
|
|
303
|
+
return false;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// src/extensions/scheduler/format.ts
|
|
307
|
+
var MS_PER_DAY = 864e5;
|
|
308
|
+
var MS_PER_HOUR = 36e5;
|
|
309
|
+
var MS_PER_MINUTE = 6e4;
|
|
310
|
+
var MS_PER_SECOND = 1e3;
|
|
311
|
+
var NOW_THRESHOLD_MS = 5e3;
|
|
312
|
+
function formatDuration(ms) {
|
|
313
|
+
if (ms <= 0) return "0s";
|
|
314
|
+
const units = [
|
|
315
|
+
["d", MS_PER_DAY],
|
|
316
|
+
["h", MS_PER_HOUR],
|
|
317
|
+
["m", MS_PER_MINUTE],
|
|
318
|
+
["s", MS_PER_SECOND]
|
|
319
|
+
];
|
|
320
|
+
for (const [suffix, divisor] of units) {
|
|
321
|
+
if (ms >= divisor && ms % divisor === 0) {
|
|
322
|
+
return `${ms / divisor}${suffix}`;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
return `${Math.round(ms / MS_PER_SECOND)}s`;
|
|
326
|
+
}
|
|
327
|
+
function formatSchedule(spec, kind) {
|
|
328
|
+
if (spec.mode === "interval") {
|
|
329
|
+
return kind === "once" ? `once in ${formatDuration(spec.intervalMs)}` : `every ${formatDuration(spec.intervalMs)}`;
|
|
330
|
+
}
|
|
331
|
+
return spec.cronExpression;
|
|
332
|
+
}
|
|
333
|
+
function formatRelativeTime(timestamp, now) {
|
|
334
|
+
const currentTime = now ?? Date.now();
|
|
335
|
+
const diff = timestamp - currentTime;
|
|
336
|
+
if (Math.abs(diff) < NOW_THRESHOLD_MS) return "now";
|
|
337
|
+
const absDiff = Math.abs(diff);
|
|
338
|
+
const units = [
|
|
339
|
+
["d", MS_PER_DAY],
|
|
340
|
+
["h", MS_PER_HOUR],
|
|
341
|
+
["m", MS_PER_MINUTE],
|
|
342
|
+
["s", MS_PER_SECOND]
|
|
343
|
+
];
|
|
344
|
+
let formatted = "";
|
|
345
|
+
for (const [suffix, divisor] of units) {
|
|
346
|
+
if (absDiff >= divisor) {
|
|
347
|
+
const value = Math.floor(absDiff / divisor);
|
|
348
|
+
formatted = `${value}${suffix}`;
|
|
349
|
+
break;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
if (!formatted) {
|
|
353
|
+
formatted = `${Math.round(absDiff / MS_PER_SECOND)}s`;
|
|
354
|
+
}
|
|
355
|
+
return diff > 0 ? `in ${formatted}` : `${formatted} ago`;
|
|
356
|
+
}
|
|
357
|
+
|
|
188
358
|
// src/extensions/scheduler-create/marker.ts
|
|
189
359
|
var SCHEDULE_CREATE_MARKER = "\0TAIJI_SCHEDULE_CREATE";
|
|
190
360
|
|
|
@@ -493,8 +663,13 @@ export {
|
|
|
493
663
|
BRIDGE_MARKER,
|
|
494
664
|
BRIDGE_METHODS,
|
|
495
665
|
GUI_WIDGET_MARKER,
|
|
666
|
+
HISTORY_LIMIT,
|
|
496
667
|
INFLIGHT_REPORT_ACK,
|
|
497
668
|
MAX_TERMINAL_REGISTRY_ENTRIES,
|
|
669
|
+
MS_PER_DAY,
|
|
670
|
+
MS_PER_HOUR,
|
|
671
|
+
MS_PER_MINUTE,
|
|
672
|
+
MS_PER_SECOND,
|
|
498
673
|
PENDING_REGISTER_ENTRY_TYPE,
|
|
499
674
|
PENDING_UNREGISTER_ENTRY_TYPE,
|
|
500
675
|
PLAN_REVIEW_MARKER,
|
|
@@ -504,7 +679,9 @@ export {
|
|
|
504
679
|
SESSION_MANAGER_MARKER,
|
|
505
680
|
SUBAGENTS_ENGINES_FILENAME,
|
|
506
681
|
SUBAGENT_INFLIGHT_MARKER,
|
|
682
|
+
TASK_ENTRY_TYPE,
|
|
507
683
|
UI_FORM_MARKER,
|
|
684
|
+
appendExecutionRecord,
|
|
508
685
|
applyPendingDiff,
|
|
509
686
|
callMarkerRpc,
|
|
510
687
|
collectActivePendingIds,
|
|
@@ -512,6 +689,9 @@ export {
|
|
|
512
689
|
extractGui,
|
|
513
690
|
firstContentText,
|
|
514
691
|
formatChannelErrorText,
|
|
692
|
+
formatDuration,
|
|
693
|
+
formatRelativeTime,
|
|
694
|
+
formatSchedule,
|
|
515
695
|
getAskUserAnswer,
|
|
516
696
|
getAskUserOther,
|
|
517
697
|
guiComponent,
|
|
@@ -544,8 +724,10 @@ export {
|
|
|
544
724
|
isTerminalBackgroundTaskState,
|
|
545
725
|
mapReasonToStatus,
|
|
546
726
|
onceCronToDate,
|
|
727
|
+
replayFoldEntries,
|
|
547
728
|
scanPendingEntries,
|
|
548
729
|
setWidgetDual,
|
|
730
|
+
snapshotToTask,
|
|
549
731
|
uiFormInteract,
|
|
550
732
|
validateWidgetIconPaths
|
|
551
733
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhushanwen/extension-protocol",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
4
4
|
"description": "Cross-layer pi extension contract package: dual-mode (TUI/GUI) rendering helpers, session/background-task/pending-entries protocols, and shared behavior primitives",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|