@zhushanwen/extension-protocol 0.12.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.
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
* guard 写 exited 终态。写入协议:原子写 temp+rename + 统一文件锁内 RMW;
|
|
8
8
|
* 解析失败/形状非法 → 重命名 .corrupt 保留现场 + 按空表重建。
|
|
9
9
|
* - 读侧:taiji runtime 后台任务收殓器——按属主判定处置孤儿(见 ownerPiPid
|
|
10
|
-
* 字段注释),orphaned 终态由 runtime 写入(收殓下沉,设计
|
|
11
|
-
* file-lock-unification-and-reaper-sink.md D2)。
|
|
10
|
+
* 字段注释),orphaned 终态由 runtime 写入(收殓下沉,设计
|
|
11
|
+
* docs/architecture/file-lock-unification-and-reaper-sink.md D2)。
|
|
12
12
|
*
|
|
13
13
|
* 目录布局(per-session 隔离):
|
|
14
14
|
* <agentDir>/base-tool-enhance/<sessionId>/registry.json
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { B as BackgroundTaskState, a as BackgroundTaskRegistryEntry } from './background-task-
|
|
1
|
+
import { B as BackgroundTaskState, a as BackgroundTaskRegistryEntry } from './background-task-B6pdI5nq.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* 后台任务进程原语(pid 判活 / 进程树处置 / start time 身份判据)——跨端单一实现。
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { b as BACKGROUND_TASK_ID_PREFIX, c as BACKGROUND_TASK_REGISTRY_FILENAME, d as BACKGROUND_TASK_REGISTRY_VERSION, e as BASE_TOOL_ENHANCE_DIRNAME, f as BackgroundTaskEndReason, a as BackgroundTaskRegistryEntry, g as BackgroundTaskRegistryFile, B as BackgroundTaskState, M as MAX_TERMINAL_REGISTRY_ENTRIES, i as isActiveBackgroundTaskState, h as isBackgroundTaskRegistryEntry, j as isTerminalBackgroundTaskState } from './background-task-
|
|
1
|
+
export { b as BACKGROUND_TASK_ID_PREFIX, c as BACKGROUND_TASK_REGISTRY_FILENAME, d as BACKGROUND_TASK_REGISTRY_VERSION, e as BASE_TOOL_ENHANCE_DIRNAME, f as BackgroundTaskEndReason, a as BackgroundTaskRegistryEntry, g as BackgroundTaskRegistryFile, B as BackgroundTaskState, M as MAX_TERMINAL_REGISTRY_ENTRIES, i as isActiveBackgroundTaskState, h as isBackgroundTaskRegistryEntry, j as isTerminalBackgroundTaskState } from './background-task-B6pdI5nq.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* GUI 渲染协议核心类型定义。
|
|
@@ -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;
|
|
@@ -145,6 +165,46 @@ interface TreeItem {
|
|
|
145
165
|
children?: TreeItem[];
|
|
146
166
|
}
|
|
147
167
|
type TreeItemIcon = 'arrow' | 'check' | 'cross' | 'circle' | 'dot' | 'pause' | 'branch';
|
|
168
|
+
/**
|
|
169
|
+
* 计划产物文档元数据——agent 调 register-doc 登记的一份产物。
|
|
170
|
+
* version 由 extension 侧维护:修订后重写文档须重调 register-doc(version+1),
|
|
171
|
+
* 前端凭 version 变化重拉 file.read 刷新渲染。
|
|
172
|
+
*/
|
|
173
|
+
interface PlanDocMeta {
|
|
174
|
+
/** 文件名(drawer L2 文档 tab 标题,不含目录) */
|
|
175
|
+
fileName: string;
|
|
176
|
+
/** 文件绝对路径(前端 file.read RPC 带 sessionId 读取) */
|
|
177
|
+
absPath: string;
|
|
178
|
+
/** 来源技能名(挂载 --skills 时产出该文档的技能;模板流程产出时为空串) */
|
|
179
|
+
sourceSkill: string;
|
|
180
|
+
/** 修订版本,从 1 起,每次修订重登记 +1 */
|
|
181
|
+
version: number;
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* submit-review 挂起审批时的 select payload(序列化为 options[0] JSON)。
|
|
185
|
+
* extension 侧解析失败走 E5(logger.warn + tool result 报错提示重挂,垃圾数据不进对话流)。
|
|
186
|
+
*/
|
|
187
|
+
interface PlanReviewRequest {
|
|
188
|
+
docs: PlanDocMeta[];
|
|
189
|
+
}
|
|
190
|
+
/** 审批两键裁决。revise 携带评论、approve 不携带(结构上不可混带)。 */
|
|
191
|
+
type PlanReviewDecision = 'approve' | 'revise';
|
|
192
|
+
/** 用户对某文档划选段落的一条评论:quote 是划选引文(agent 定位段落用),comment 是评语。 */
|
|
193
|
+
interface PlanReviewComment {
|
|
194
|
+
quote: string;
|
|
195
|
+
comment: string;
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* 审批条 respond 回传(判别联合:approve 无评论字段,revise 必带评论数组)。
|
|
199
|
+
* extension 消费:approve → 走现状 complete 执行方式 select;revise → 评论清单以
|
|
200
|
+
* 显式 deliverAs:'steer' 注入 + reviewState=revising。
|
|
201
|
+
*/
|
|
202
|
+
type PlanReviewResponse = {
|
|
203
|
+
decision: 'approve';
|
|
204
|
+
} | {
|
|
205
|
+
decision: 'revise';
|
|
206
|
+
comments: PlanReviewComment[];
|
|
207
|
+
};
|
|
148
208
|
|
|
149
209
|
/**
|
|
150
210
|
* 通用传输 marker。extension 用 guiSetWidget 编码进 string[],
|
|
@@ -153,6 +213,17 @@ type TreeItemIcon = 'arrow' | 'check' | 'cross' | 'circle' | 'dot' | 'pause' | '
|
|
|
153
213
|
* NUL 字符开头的 marker,不会出现在正常文本中。
|
|
154
214
|
*/
|
|
155
215
|
declare const GUI_WIDGET_MARKER = "\0TAIJI_GUI_WIDGET:";
|
|
216
|
+
/**
|
|
217
|
+
* plan 审阅请求的 select title marker。plan extension 的 submit-review 挂起审批时
|
|
218
|
+
* 以此 marker 为 title 发 ctx.ui.select(options[0] = PlanReviewRequest JSON,
|
|
219
|
+
* 类型见 core/types.ts),runtime event-adapter 检测后广播 extension_ui_request
|
|
220
|
+
* (planReview 标记,与 askUser 标记同构分流),前端审批条渲染三键审批而非
|
|
221
|
+
* 原始 dialog——marker 控制符 title 落入通用 dialog 会渲染成乱码。
|
|
222
|
+
*
|
|
223
|
+
* NUL 前缀确保不会与 extension 正常的 select title 冲突,
|
|
224
|
+
* 与 GUI_WIDGET_MARKER / ASK_USER_MARKER 同族(select 通道 marker)。
|
|
225
|
+
*/
|
|
226
|
+
declare const PLAN_REVIEW_MARKER = "\0TAIJI_PLAN_REVIEW:";
|
|
156
227
|
|
|
157
228
|
/**
|
|
158
229
|
* helpers 需要的 ctx 最小接口。
|
|
@@ -364,13 +435,15 @@ declare function callMarkerRpc(ctx: GuiContext, marker: string, payload: string,
|
|
|
364
435
|
/**
|
|
365
436
|
* ask-user extension 的富交互类型定义。
|
|
366
437
|
*
|
|
367
|
-
*
|
|
368
|
-
*
|
|
369
|
-
*
|
|
370
|
-
* 前端 AskUserOverlay 渲染富交互 UI。
|
|
438
|
+
* GUI 提问交互已迁移统一表单协议(../ui-form:FormQuestion 类型化问题集 +
|
|
439
|
+
* UI_FORM_MARKER + uiFormInteract,前端 FormOverlay 渲染)——ask_user 工具的
|
|
440
|
+
* 问题在 ask-user 包内归一 adapter 转为 FormQuestion(choice/text 形态)发送。
|
|
371
441
|
*
|
|
372
|
-
*
|
|
373
|
-
*
|
|
442
|
+
* 本模块保留的 AskUserQuestion / AskUserAnswers 是 ask-user 的 LLM 入参契约与
|
|
443
|
+
* TUI/解码消费形态:FormAnswers 的 choice/text 部分与 AskUserAnswers 逐字兼容
|
|
444
|
+
* (键位规则与多选序列化一致),解码 helper(getAskUserAnswer/getAskUserOther)
|
|
445
|
+
* 消费本类型。legacy 帧(ASK_USER_MARKER)由 runtime event-adapter 分支归一转
|
|
446
|
+
* FormQuestion(D7 兼容窗口,归一层在 runtime 侧),随窗口末清理退役。
|
|
374
447
|
*/
|
|
375
448
|
/**
|
|
376
449
|
* ask-user 富交互问题声明。
|
|
@@ -424,30 +497,18 @@ type AskUserAnswers = Record<string, string>;
|
|
|
424
497
|
declare const ASK_USER_MARKER = "\0TAIJI_ASK_USER";
|
|
425
498
|
|
|
426
499
|
/**
|
|
427
|
-
* ask-user extension 的富交互 helper
|
|
500
|
+
* ask-user extension 的富交互 helper(解码层)。
|
|
428
501
|
*
|
|
429
|
-
*
|
|
430
|
-
*
|
|
502
|
+
* GUI 提问交互已迁移统一表单协议(ui-form 模块的 uiFormInteract + UI_FORM_MARKER,
|
|
503
|
+
* 设计 ui-presentation-protocol D3)——askUserInteract 传输 helper 随之退役,不保留
|
|
504
|
+
* deprecated 别名;AskUserQuestion↔FormQuestion 的归一职责下沉 ask-user 包内 adapter
|
|
505
|
+
* (extensions/universal/ask-user/src/form-adapter.ts)。
|
|
431
506
|
*
|
|
432
|
-
*
|
|
433
|
-
*
|
|
507
|
+
* 本模块保留 answers 解码 helper(getAskUserAnswer / getAskUserOther):FormAnswers 的
|
|
508
|
+
* choice/text 部分与 AskUserAnswers 逐字兼容(键位规则:key = header ?? question,
|
|
509
|
+
* 多选 = JSON 数组,Other = `${key}__other`),解码契约在此单源。
|
|
434
510
|
*/
|
|
435
511
|
|
|
436
|
-
/**
|
|
437
|
-
* ask-user 富交互入口(RPC 模式专用)。
|
|
438
|
-
*
|
|
439
|
-
* RPC 模式:用 select 通道携带 questions 数据,前端渲染富交互 UI。
|
|
440
|
-
* TUI 模式:抛错。extension 必须自行调 ctx.ui.custom()。
|
|
441
|
-
*
|
|
442
|
-
* @param ctx ExtensionContext(pi 提供)
|
|
443
|
-
* @param questions 交互问题声明
|
|
444
|
-
* @param options 可选:signal(abort)、allowCancel(前端是否显示取消按钮)
|
|
445
|
-
* @returns answers(key=header/question, value=JSON编码的答案),用户取消返回 null
|
|
446
|
-
*/
|
|
447
|
-
declare function askUserInteract(ctx: GuiContext, questions: AskUserQuestion[], options?: {
|
|
448
|
-
signal?: AbortSignal;
|
|
449
|
-
allowCancel?: boolean;
|
|
450
|
-
}): Promise<AskUserAnswers | null>;
|
|
451
512
|
/**
|
|
452
513
|
* 从 answers 中提取某个问题的选中值(单选返回 string,多选返回 string[])。
|
|
453
514
|
*
|
|
@@ -459,10 +520,458 @@ declare function getAskUserAnswer(answers: AskUserAnswers, question: AskUserQues
|
|
|
459
520
|
declare function getAskUserOther(answers: AskUserAnswers, question: AskUserQuestion): string | undefined;
|
|
460
521
|
/**
|
|
461
522
|
* 类型守卫:验证 unknown 是否为合法的 AskUserQuestion。
|
|
462
|
-
*
|
|
523
|
+
* 供 legacy 帧消费方(runtime marker 分支侧形状参照 / 扩展自测)收窄 unknown[] 用。
|
|
463
524
|
*/
|
|
464
525
|
declare function isAskUserQuestion(value: unknown): value is AskUserQuestion;
|
|
465
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
|
+
|
|
697
|
+
/**
|
|
698
|
+
* scheduler 创建确认弹框的协议类型(Draft / FormResult)。
|
|
699
|
+
*
|
|
700
|
+
* GUI 创建确认已迁移统一表单协议(ui-form 模块的 uiFormInteract + UI_FORM_MARKER,
|
|
701
|
+
* 交互入口 extensions/universal/scheduler/src/tool.ts:Draft 经 initial 预填为
|
|
702
|
+
* ScheduleQuestion 单问整表单,前端 FormOverlay 的 schedule 渲染器回传
|
|
703
|
+
* ScheduleFormResult)——ScheduleDraft / ScheduleFormResult 即该表单的预填与
|
|
704
|
+
* 回传形状,TUI 侧 ScheduleCreateComponent 同用本类型。
|
|
705
|
+
*
|
|
706
|
+
* 取消不走 FormResult:interact 取消 resolve undefined,execute 返回
|
|
707
|
+
* cancelled result(D5),任务不创建。
|
|
708
|
+
*
|
|
709
|
+
* SCHEDULE_CREATE_MARKER / ScheduleCreateOverlay 为 legacy 窗口语义(旧 npm 帧
|
|
710
|
+
* 由 runtime event-adapter 识别 / 迁移溯源锚点),随 D7 窗口末清理退役。
|
|
711
|
+
*
|
|
712
|
+
* 这是 scheduler 创建路径的定制协议,不是通用富交互协议(与 ask-user 同理)。
|
|
713
|
+
*/
|
|
714
|
+
/**
|
|
715
|
+
* 执行模式:once = 一次性(schedule 为 dateToOnceCron 折叠的一次性 cron);
|
|
716
|
+
* recurring = 循环(duration 或 cron)。
|
|
717
|
+
*/
|
|
718
|
+
type ScheduleKind = 'once' | 'recurring';
|
|
719
|
+
/** 创建确认草稿(extension → 前端,LLM 参数即预填值) */
|
|
720
|
+
interface ScheduleDraft {
|
|
721
|
+
/** LLM 判定的执行模式,用户可改 */
|
|
722
|
+
kind: ScheduleKind;
|
|
723
|
+
/**
|
|
724
|
+
* recurring:duration(如 5m/2h)或 cron;once:已折叠的一次性 cron
|
|
725
|
+
* (唯一时间来源,表单时间初值由 onceCronToDate 还原,D2)
|
|
726
|
+
*/
|
|
727
|
+
schedule: string;
|
|
728
|
+
/** scoped model id;缺省 = 跟随会话当前模型 */
|
|
729
|
+
model?: string;
|
|
730
|
+
/** agent 起草的提示词 */
|
|
731
|
+
prompt: string;
|
|
732
|
+
/** 任务名(同现有工具参数,高级选项) */
|
|
733
|
+
name?: string;
|
|
734
|
+
/** 过期时间(同现有工具参数,高级选项) */
|
|
735
|
+
expires?: string;
|
|
736
|
+
/** scoped models 列表,前端不再自取 */
|
|
737
|
+
models: string[];
|
|
738
|
+
/** 会话当前模型 id(前端预选标记) */
|
|
739
|
+
currentModel?: string;
|
|
740
|
+
}
|
|
741
|
+
/**
|
|
742
|
+
* 创建确认回传(前端 → extension)。
|
|
743
|
+
* 取消不走此形状:select resolve undefined → execute 返回 cancelled result(D5)。
|
|
744
|
+
*/
|
|
745
|
+
interface ScheduleFormResult {
|
|
746
|
+
action: 'create';
|
|
747
|
+
kind: ScheduleKind;
|
|
748
|
+
/** 用户确认后的最终形态(once = 经 dateToOnceCron 折叠的一次性 cron) */
|
|
749
|
+
schedule: string;
|
|
750
|
+
/** 用户最终选择;未选 = 缺省(跟随会话当前模型) */
|
|
751
|
+
model?: string;
|
|
752
|
+
prompt: string;
|
|
753
|
+
name?: string;
|
|
754
|
+
expires?: string;
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
/**
|
|
758
|
+
* scheduler 创建确认请求的 title marker。runtime event-adapter 和前端 useExtensionUI
|
|
759
|
+
* 检测此 marker 区分 schedule 创建确认请求与普通 select。
|
|
760
|
+
*
|
|
761
|
+
* NUL 前缀确保不会与 extension 正常的 select title 冲突。
|
|
762
|
+
* 与 ASK_USER_MARKER 同规范(ask-user 模块同包 marker.ts)。
|
|
763
|
+
*/
|
|
764
|
+
declare const SCHEDULE_CREATE_MARKER = "\0TAIJI_SCHEDULE_CREATE";
|
|
765
|
+
|
|
766
|
+
/**
|
|
767
|
+
* scheduler 创建确认的共享协议资产(定制交互 helper 已退役)。
|
|
768
|
+
*
|
|
769
|
+
* scheduleCreateInteract() 已随统一提问表单协议迁移退役(ui-presentation-protocol
|
|
770
|
+
* D3/u6,直接退役不保留 deprecated 别名):scheduler 的创建确认改走
|
|
771
|
+
* uiFormInteract() + UI_FORM_MARKER(ScheduleQuestion 形态,initial 预填草稿),
|
|
772
|
+
* 交互入口在 extensions/universal/scheduler/src/tool.ts。
|
|
773
|
+
*
|
|
774
|
+
* 本模块保留三类跨端共享资产:
|
|
775
|
+
* - isScheduleDraft / isScheduleFormResult 形状守卫:前者供 runtime event-adapter
|
|
776
|
+
* legacy 分支(SCHEDULE_CREATE_MARKER 帧 payload 判定)与 ui-form 守卫的
|
|
777
|
+
* schedule 分支(initial 深度校验)消费;后者供 scheduler 包回包判别
|
|
778
|
+
* (uiFormInteract answers[key] 解包后收窄为 ScheduleFormResult)。
|
|
779
|
+
* - dateToOnceCron / onceCronToDate 时间折叠单点(D2):GUI(ScheduleForm 渲染器)
|
|
780
|
+
* 与 TUI(ScheduleCreateComponent)共用本实现,禁止双端各写一份——时间语义
|
|
781
|
+
* 漂移正是本协议要关闭的失败模式(F1)。
|
|
782
|
+
*
|
|
783
|
+
* 时区语义:按本地墙钟时间(croner 对 cron 默认本地解释,datetime 控件与 TUI
|
|
784
|
+
* 掩码编辑均产出本地墙钟时间)。
|
|
785
|
+
*/
|
|
786
|
+
|
|
787
|
+
/**
|
|
788
|
+
* 类型守卫:验证 unknown 是否为合法的 ScheduleDraft。
|
|
789
|
+
* 用于 runtime event-adapter 判定 marker select 的 payload 是否为本协议 draft
|
|
790
|
+
* (检测失败降级普通 select),以及前端从 runtime 透传值中安全收窄。
|
|
791
|
+
* 字段白名单校验(与 isAskUserQuestion 同构):可选字段校验类型,未知附加字段忽略。
|
|
792
|
+
*/
|
|
793
|
+
declare function isScheduleDraft(value: unknown): value is ScheduleDraft;
|
|
794
|
+
/** 回传形状守卫(导出消费:scheduler 包对 uiFormInteract answers[key] 解包值收窄;
|
|
795
|
+
* FormOverlay schedule 渲染器提交的 value = JSON.stringify(ScheduleFormResult)) */
|
|
796
|
+
declare function isScheduleFormResult(value: unknown): value is ScheduleFormResult;
|
|
797
|
+
/**
|
|
798
|
+
* 本地墙钟时刻 → 一次性 cron(5 段 `分 时 日 月 *`)。
|
|
799
|
+
*
|
|
800
|
+
* 一次性 cron 语义:具体日 + 具体月 + 星期通配 → croner 天然只命中一次(S8/D2),
|
|
801
|
+
* scheduler 现有 parseSchedule 直接接受,service 层零感知。
|
|
802
|
+
*/
|
|
803
|
+
declare function dateToOnceCron(date: Date): string;
|
|
804
|
+
declare function onceCronToDate(cron: string, now?: Date): Date | null;
|
|
805
|
+
|
|
806
|
+
/**
|
|
807
|
+
* 统一提问表单协议(ui-form)的类型定义。
|
|
808
|
+
*
|
|
809
|
+
* 把 plan / scheduler / ask-user 三个 extension 的提问交互收口为一个表单协议:
|
|
810
|
+
* 问题即数据(类型化问题集),GUI 链路统一(wire 协议 + 渲染器)——多问 = 多 tab,
|
|
811
|
+
* 单问 = 单视图。extension 侧经 uiFormInteract() 序列化进 select 的 options[0],
|
|
812
|
+
* runtime event-adapter 检测 UI_FORM_MARKER 广播 form 帧,前端 FormOverlay
|
|
813
|
+
* 按问题 type 分派渲染器(choice / text / schedule)。
|
|
814
|
+
*
|
|
815
|
+
* 这是提问交互的通用协议(区别于 ask-user / scheduler-create 的定制协议)。
|
|
816
|
+
* formQuestions 在 TUI 无呈现语义——TUI 数据通路各 extension 自行渲染(设计 D8
|
|
817
|
+
* 有意取舍),消费方为 extension 自有组件。
|
|
818
|
+
*/
|
|
819
|
+
|
|
820
|
+
/**
|
|
821
|
+
* choice 问题:互斥/多选选项。
|
|
822
|
+
* 对应现 AskUserQuestion 有 options 的形态(multiSelect → multi 重命名)。
|
|
823
|
+
*/
|
|
824
|
+
interface ChoiceQuestion {
|
|
825
|
+
type: 'choice';
|
|
826
|
+
/** Tab 标签 / 简短标题。answers key 缺省 fallback 到 question 全文(与 askUserKey 同规则) */
|
|
827
|
+
header?: string;
|
|
828
|
+
/** 完整问题文本。也作为 answers 的 fallback key(header 缺失时) */
|
|
829
|
+
question: string;
|
|
830
|
+
/** 上下文摘要(可选)。显示在问题上方,帮用户理解背景 */
|
|
831
|
+
context?: string;
|
|
832
|
+
/** 互斥选项列表(无 options 的纯自由文本问题用 TextQuestion 表达) */
|
|
833
|
+
options: FormOption[];
|
|
834
|
+
/** 是否允许多选 */
|
|
835
|
+
multi?: boolean;
|
|
836
|
+
/** 是否允许自由文本输入(Other)。默认 true(同 ask-user 现状),设 false 则不追加 */
|
|
837
|
+
allowOther?: boolean;
|
|
838
|
+
}
|
|
839
|
+
/** 选项声明(label 即选中值:协议无独立 value 字段,回传统一用 label) */
|
|
840
|
+
interface FormOption {
|
|
841
|
+
label: string;
|
|
842
|
+
/** 描述(可选)。显示在 label 下方,解释 tradeoff */
|
|
843
|
+
description?: string;
|
|
844
|
+
}
|
|
845
|
+
/**
|
|
846
|
+
* text 问题:纯自由文本输入(无 options)。
|
|
847
|
+
* 协议值域完备性要求保留:AskUserQuestion.options 声明可选,归一层 type 推断
|
|
848
|
+
* 与消费端必须能表达「无 options」的合法旧形态(设计 D2)。
|
|
849
|
+
*/
|
|
850
|
+
interface TextQuestion {
|
|
851
|
+
type: 'text';
|
|
852
|
+
header?: string;
|
|
853
|
+
question: string;
|
|
854
|
+
context?: string;
|
|
855
|
+
}
|
|
856
|
+
/**
|
|
857
|
+
* schedule 问题:时间输入领域原语(非 scheduler 专属形状)。
|
|
858
|
+
* 渲染器 = scheduler 创建确认整表单(ScheduleForm,单 tab 整表单收编,设计 D2);
|
|
859
|
+
* 预填草稿经 initial 直传,打开即可一键确认。
|
|
860
|
+
*/
|
|
861
|
+
interface ScheduleQuestion {
|
|
862
|
+
type: 'schedule';
|
|
863
|
+
header?: string;
|
|
864
|
+
question: string;
|
|
865
|
+
context?: string;
|
|
866
|
+
/** 预填草稿(可选)。ScheduleDraft 复用 scheduler-create 模块定义(零新增依赖方向) */
|
|
867
|
+
initial?: ScheduleDraft;
|
|
868
|
+
}
|
|
869
|
+
/** 类型化问题集(判别联合:按 type 分派渲染器) */
|
|
870
|
+
type FormQuestion = ChoiceQuestion | TextQuestion | ScheduleQuestion;
|
|
871
|
+
/**
|
|
872
|
+
* 统一表单回传结果。key = header ?? question(fallback 规则与 askUserKey 一致)。
|
|
873
|
+
*
|
|
874
|
+
* 答案编码规则(choice/text 部分与现 AskUserAnswers 逐字兼容,含 Other 键规则
|
|
875
|
+
* 与多选序列化——ask-user 消费方 getAskUserAnswer / getAskUserOther 零改动):
|
|
876
|
+
* - choice 单选:value = 选中项的 label
|
|
877
|
+
* - choice 多选:value = JSON.stringify(选中项 label 数组),如 '["pg","mysql"]'
|
|
878
|
+
* - Other 文本:单独 key `${key}__other`,value = 自由文本(不混进选中项数组)
|
|
879
|
+
* - text:value = 自由文本,**键位 `${key}__other`**(逐字继承现状纯 other 形态——
|
|
880
|
+
* 无 options 题答案只写 __other 不写主 key,写主 key 会令 getAskUserAnswer 键位翻转)
|
|
881
|
+
* - schedule:value = JSON.stringify(ScheduleFormResult)(单问表单下 answers 恰一键)
|
|
882
|
+
*/
|
|
883
|
+
type FormAnswers = Record<string, string>;
|
|
884
|
+
|
|
885
|
+
/**
|
|
886
|
+
* 统一提问表单(ui-form)请求的 title marker。runtime event-adapter 和前端
|
|
887
|
+
* useExtensionUI 检测此 marker 区分表单请求与普通 select。
|
|
888
|
+
*
|
|
889
|
+
* NUL 前缀确保不会与 extension 正常的 select title 冲突。
|
|
890
|
+
* 与 ASK_USER_MARKER 同规范(ask-user 模块同包 marker.ts)。
|
|
891
|
+
*/
|
|
892
|
+
declare const UI_FORM_MARKER = "\0TAIJI_UI_FORM";
|
|
893
|
+
|
|
894
|
+
/**
|
|
895
|
+
* 统一提问表单(ui-form)协议 helper。
|
|
896
|
+
*
|
|
897
|
+
* uiFormInteract() 是 plan / scheduler / ask-user 三方提问在 RPC 模式下的统一入口
|
|
898
|
+
* (设计 ui-presentation-protocol D3)。走 select 通道 + marker(UI_FORM_MARKER),
|
|
899
|
+
* 传输核复用 callMarkerRpc 原语,四态判别返回而非抛错(除 RPC 门外):
|
|
900
|
+
* - cancelled / timeout → 调用方按「用户未作答」折叠;
|
|
901
|
+
* - channel-error → 通道契约破坏(含 echo 检测命中的旧宿主组合),调用方按各自
|
|
902
|
+
* 策略折叠(scheduler 禁用工具 + throw / plan 折 cancelled result 留 plan mode);
|
|
903
|
+
* - non-json → 协议版本错配类故障。
|
|
904
|
+
*
|
|
905
|
+
* TUI 模式不代劳渲染(formQuestions 在 TUI 无呈现语义,各 extension 自有组件
|
|
906
|
+
* 消费,设计 D8):误调抛错指明恢复动作,返回判别失败会与「用户取消」混淆。
|
|
907
|
+
*/
|
|
908
|
+
|
|
909
|
+
/** uiFormInteract 的判别结果:成功收窄为 FormAnswers;失败按 callMarkerRpc 四态折叠。
|
|
910
|
+
* message 仅 channel-error 的 echo 命中态携带(升级指引,见 uiFormInteract 内注释),
|
|
911
|
+
* 供调用方折叠时透出——各调用方对 channel-error 的折叠策略不同,文案由调用方决定是否使用 */
|
|
912
|
+
type UiFormInteractResult = {
|
|
913
|
+
ok: true;
|
|
914
|
+
answers: FormAnswers;
|
|
915
|
+
} | {
|
|
916
|
+
ok: false;
|
|
917
|
+
reason: 'cancelled' | 'timeout' | 'channel-error' | 'non-json';
|
|
918
|
+
message?: string;
|
|
919
|
+
};
|
|
920
|
+
interface UiFormInteractOptions {
|
|
921
|
+
/** 透传 select dialog:abort 后 pi 本地 resolve(undefined) → cancelled */
|
|
922
|
+
signal?: AbortSignal;
|
|
923
|
+
/** 前端是否显示取消按钮(默认 true,沿已退役的 askUserInteract 的默认值先例) */
|
|
924
|
+
allowCancel?: boolean;
|
|
925
|
+
/**
|
|
926
|
+
* 源元数据:本次表单提交后是否有 turn 预期(respond 分型判据,form-submit-busy-convergence D1)。
|
|
927
|
+
*
|
|
928
|
+
* 三态语义(D2):`true | undefined` = 有 turn 预期——提交型桥接 message_start 照旧
|
|
929
|
+
* (undefined 为缺省态:存量扩展与未声明者,桥接 = 现状,方向安全);
|
|
930
|
+
* `false` = 无 turn 预期——命令 handler 内 select(提交后结构性无 turn),respond 侧
|
|
931
|
+
* 提交即收尾,消除 30s 假忙。消费方判定必须是 `=== false` 显式式:truthy 简化
|
|
932
|
+
* (`!expectTurn`)会把 undefined 也当无 turn、误清桥接(D2 被否谱系)。
|
|
933
|
+
*
|
|
934
|
+
* 缺省(undefined)经 stripUndefined 剥键——未声明者的 wire payload 逐字节不变,
|
|
935
|
+
* 旧 runtime 读不到即忽略(JSON.parse 容忍未知键)。
|
|
936
|
+
*/
|
|
937
|
+
expectTurn?: boolean;
|
|
938
|
+
/** 失败留痕注入(echo 命中 / 形状错),日志策略归调用方 */
|
|
939
|
+
log?: (msg: string, detail?: object) => void;
|
|
940
|
+
}
|
|
941
|
+
/**
|
|
942
|
+
* 统一提问表单入口(RPC 模式专用)。
|
|
943
|
+
*
|
|
944
|
+
* RPC 模式:select 通道携带类型化问题集,前端 FormOverlay 渲染表单,回传 FormAnswers。
|
|
945
|
+
* TUI 模式:抛错,extension 自行渲染(ctx.ui.custom 或原生 select)。
|
|
946
|
+
*
|
|
947
|
+
* 确认交互不设墙钟超时(任务级正常路径无墙钟,AGENTS.md 超时默认原则):
|
|
948
|
+
* options 不提供 timeout 入口,timeout 态仅由「未 abort 的 undefined resolve」产生。
|
|
949
|
+
*/
|
|
950
|
+
declare function uiFormInteract(ctx: GuiContext, form: FormQuestion[], opts?: UiFormInteractOptions): Promise<UiFormInteractResult>;
|
|
951
|
+
|
|
952
|
+
/**
|
|
953
|
+
* 统一提问表单(ui-form)的形状守卫。
|
|
954
|
+
*
|
|
955
|
+
* isFormQuestion 按 type 判别分支逐项校验(白名单风格:必填字段类型校验、可选
|
|
956
|
+
* 字段校验、未知字段忽略——与 isAskUserQuestion / isScheduleDraft 同构语义)。
|
|
957
|
+
* 三个消费点失败策略各异(设计 D2):
|
|
958
|
+
* - 发送侧(uiFormInteract 内):不合法项抛错(调用方编码 bug,fail-fast);
|
|
959
|
+
* - event-adapter:逐项过滤,合法项 ≥1 才产 form 帧、全不合法降级普通 select;
|
|
960
|
+
* - renderer:复核守卫,不合法项跳过 + warn,全部不合法 → overlay 错误占位仅取消可点。
|
|
961
|
+
*
|
|
962
|
+
* isFormAnswers 校验回包形状(Record<string, string>):键动态(key = header ??
|
|
963
|
+
* question)无法枚举白名单,故只校验值域全 string;JSON 合法但非本形状由
|
|
964
|
+
* uiFormInteract 折叠 non-json(协议版本错配类故障)。
|
|
965
|
+
*/
|
|
966
|
+
|
|
967
|
+
/**
|
|
968
|
+
* 类型守卫:验证 unknown 是否为合法的 FormQuestion(choice / text / schedule 三分支)。
|
|
969
|
+
* 用于发送侧 fail-fast、event-adapter 逐项过滤与 renderer 复核(三消费点见文件头)。
|
|
970
|
+
*/
|
|
971
|
+
declare function isFormQuestion(value: unknown): value is FormQuestion;
|
|
972
|
+
/** 类型守卫:验证 unknown 是否为合法的 FormAnswers(值域全 string 的普通对象) */
|
|
973
|
+
declare function isFormAnswers(value: unknown): value is FormAnswers;
|
|
974
|
+
|
|
466
975
|
/**
|
|
467
976
|
* session-manager 请求的 title marker。runtime event-adapter 和 handler
|
|
468
977
|
* 检测此 marker 区分 session-manager 请求与普通 select。
|
|
@@ -851,4 +1360,4 @@ declare function collectActivePendingIds(entries: unknown[], opts?: CollectPendi
|
|
|
851
1360
|
type MappedPendingStatus = 'completed' | 'failed' | 'cancelled' | 'expired' | 'time_limited' | 'aborted';
|
|
852
1361
|
declare function mapReasonToStatus(reason: string): MappedPendingStatus;
|
|
853
1362
|
|
|
854
|
-
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 CollectPendingIdsOptions, type DualWidgetContent, 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, PROTOCOL_VERSION, type PendingEntriesScan, SESSION_MANAGER_ACTIONS, SESSION_MANAGER_MARKER, SUBAGENTS_ENGINES_FILENAME, SUBAGENT_INFLIGHT_MARKER, 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 TreeItem, type TreeItemIcon, type WidgetIconPathsRejection, type WidgetIconPathsValidation, type WidgetMeta,
|
|
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
|
@@ -14,6 +14,7 @@ var PROTOCOL_VERSION = 1;
|
|
|
14
14
|
|
|
15
15
|
// src/core/markers.ts
|
|
16
16
|
var GUI_WIDGET_MARKER = "\0TAIJI_GUI_WIDGET:";
|
|
17
|
+
var PLAN_REVIEW_MARKER = "\0TAIJI_PLAN_REVIEW:";
|
|
17
18
|
|
|
18
19
|
// src/core/helpers.ts
|
|
19
20
|
function isGuiCapable(ctx) {
|
|
@@ -158,31 +159,6 @@ async function callMarkerRpc(ctx, marker, payload, opts) {
|
|
|
158
159
|
var ASK_USER_MARKER = "\0TAIJI_ASK_USER";
|
|
159
160
|
|
|
160
161
|
// src/extensions/ask-user/helpers.ts
|
|
161
|
-
async function askUserInteract(ctx, questions, options) {
|
|
162
|
-
if (questions.length === 0) return {};
|
|
163
|
-
if (isGuiCapable(ctx) && ctx.ui?.select) {
|
|
164
|
-
const payload = JSON.stringify(stripUndefined({
|
|
165
|
-
questions,
|
|
166
|
-
allowCancel: options?.allowCancel ?? true
|
|
167
|
-
}));
|
|
168
|
-
const value = await ctx.ui.select(
|
|
169
|
-
ASK_USER_MARKER,
|
|
170
|
-
// title = marker,runtime/前端据此识别
|
|
171
|
-
[payload],
|
|
172
|
-
// options[0] = JSON payload(runtime 解析)
|
|
173
|
-
{ signal: options?.signal }
|
|
174
|
-
);
|
|
175
|
-
if (value === void 0) return null;
|
|
176
|
-
try {
|
|
177
|
-
return JSON.parse(value);
|
|
178
|
-
} catch {
|
|
179
|
-
return null;
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
throw new Error(
|
|
183
|
-
"askUserInteract() is only available in RPC mode. In TUI mode, use ctx.ui.custom() with your own Component directly."
|
|
184
|
-
);
|
|
185
|
-
}
|
|
186
162
|
function askUserKey(question) {
|
|
187
163
|
return question.header ?? question.question;
|
|
188
164
|
}
|
|
@@ -209,18 +185,343 @@ function isAskUserQuestion(value) {
|
|
|
209
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");
|
|
210
186
|
}
|
|
211
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
|
+
|
|
358
|
+
// src/extensions/scheduler-create/marker.ts
|
|
359
|
+
var SCHEDULE_CREATE_MARKER = "\0TAIJI_SCHEDULE_CREATE";
|
|
360
|
+
|
|
361
|
+
// src/extensions/scheduler-create/helpers.ts
|
|
362
|
+
function isPlainRecord(value) {
|
|
363
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
364
|
+
}
|
|
365
|
+
function isOptionalString(v) {
|
|
366
|
+
return v === void 0 || typeof v === "string";
|
|
367
|
+
}
|
|
368
|
+
function isStringArray(v) {
|
|
369
|
+
return Array.isArray(v) && v.every((m) => typeof m === "string");
|
|
370
|
+
}
|
|
371
|
+
function isScheduleDraft(value) {
|
|
372
|
+
if (!isPlainRecord(value)) return false;
|
|
373
|
+
const d = value;
|
|
374
|
+
return (d.kind === "once" || d.kind === "recurring") && typeof d.schedule === "string" && isOptionalString(d.model) && typeof d.prompt === "string" && isOptionalString(d.name) && isOptionalString(d.expires) && isStringArray(d.models) && isOptionalString(d.currentModel);
|
|
375
|
+
}
|
|
376
|
+
function isScheduleFormResult(value) {
|
|
377
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
|
|
378
|
+
const r = value;
|
|
379
|
+
return r.action === "create" && (r.kind === "once" || r.kind === "recurring") && typeof r.schedule === "string" && (r.model === void 0 || typeof r.model === "string") && typeof r.prompt === "string" && (r.name === void 0 || typeof r.name === "string") && (r.expires === void 0 || typeof r.expires === "string");
|
|
380
|
+
}
|
|
381
|
+
function dateToOnceCron(date) {
|
|
382
|
+
return `${date.getMinutes()} ${date.getHours()} ${date.getDate()} ${date.getMonth() + 1} *`;
|
|
383
|
+
}
|
|
384
|
+
var ONCE_CRON_FIELD_COUNT = 5;
|
|
385
|
+
var CRON_MINUTE_MAX = 59;
|
|
386
|
+
var CRON_HOUR_MAX = 23;
|
|
387
|
+
var CRON_DAY_MIN = 1;
|
|
388
|
+
var CRON_DAY_MAX = 31;
|
|
389
|
+
var CRON_MONTH_MIN = 1;
|
|
390
|
+
var CRON_MONTH_MAX = 12;
|
|
391
|
+
var ONCE_CRON_MAX_YEAR_LOOKAHEAD = 8;
|
|
392
|
+
function parseOnceCronNumbers(parts) {
|
|
393
|
+
const [minute, hour, day, month] = parts;
|
|
394
|
+
if (![minute, hour, day, month].every((seg) => /^\d+$/.test(seg))) return null;
|
|
395
|
+
return [Number(minute), Number(hour), Number(day), Number(month)];
|
|
396
|
+
}
|
|
397
|
+
function isOnceCronValueOutOfRange(m, h, d, mon) {
|
|
398
|
+
return m > CRON_MINUTE_MAX || h > CRON_HOUR_MAX || d < CRON_DAY_MIN || d > CRON_DAY_MAX || mon < CRON_MONTH_MIN || mon > CRON_MONTH_MAX;
|
|
399
|
+
}
|
|
400
|
+
function onceCronToDate(cron, now = /* @__PURE__ */ new Date()) {
|
|
401
|
+
const parts = cron.trim().split(/\s+/);
|
|
402
|
+
if (parts.length !== ONCE_CRON_FIELD_COUNT || parts[4] !== "*") return null;
|
|
403
|
+
const numbers = parseOnceCronNumbers(parts);
|
|
404
|
+
if (numbers === null) return null;
|
|
405
|
+
const [m, h, d, mon] = numbers;
|
|
406
|
+
if (isOnceCronValueOutOfRange(m, h, d, mon)) return null;
|
|
407
|
+
const startYear = now.getFullYear();
|
|
408
|
+
for (let year = startYear; year <= startYear + ONCE_CRON_MAX_YEAR_LOOKAHEAD; year++) {
|
|
409
|
+
const candidate = new Date(year, mon - 1, d, h, m, 0, 0);
|
|
410
|
+
if (candidate.getMonth() !== mon - 1 || candidate.getDate() !== d) continue;
|
|
411
|
+
if (candidate.getTime() >= now.getTime()) return candidate;
|
|
412
|
+
}
|
|
413
|
+
return null;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// src/extensions/ui-form/marker.ts
|
|
417
|
+
var UI_FORM_MARKER = "\0TAIJI_UI_FORM";
|
|
418
|
+
|
|
419
|
+
// src/extensions/ui-form/guards.ts
|
|
420
|
+
function isPlainRecord2(value) {
|
|
421
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
422
|
+
}
|
|
423
|
+
function isFormOption(value) {
|
|
424
|
+
if (!isPlainRecord2(value)) return false;
|
|
425
|
+
const o = value;
|
|
426
|
+
return typeof o.label === "string" && (o.description === void 0 || typeof o.description === "string");
|
|
427
|
+
}
|
|
428
|
+
function isFormOptionArray(value) {
|
|
429
|
+
return Array.isArray(value) && value.every(isFormOption);
|
|
430
|
+
}
|
|
431
|
+
function isFormQuestion(value) {
|
|
432
|
+
if (!isPlainRecord2(value)) return false;
|
|
433
|
+
const q = value;
|
|
434
|
+
if (typeof q.question !== "string") return false;
|
|
435
|
+
if (q.header !== void 0 && typeof q.header !== "string") return false;
|
|
436
|
+
if (q.context !== void 0 && typeof q.context !== "string") return false;
|
|
437
|
+
if (q.type === "choice") {
|
|
438
|
+
return isFormOptionArray(q.options) && (q.multi === void 0 || typeof q.multi === "boolean") && (q.allowOther === void 0 || typeof q.allowOther === "boolean");
|
|
439
|
+
}
|
|
440
|
+
if (q.type === "text") return true;
|
|
441
|
+
if (q.type === "schedule") {
|
|
442
|
+
return q.initial === void 0 || isScheduleDraft(q.initial);
|
|
443
|
+
}
|
|
444
|
+
return false;
|
|
445
|
+
}
|
|
446
|
+
function isFormAnswers(value) {
|
|
447
|
+
if (!isPlainRecord2(value)) return false;
|
|
448
|
+
return Object.values(value).every((v) => typeof v === "string");
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// src/extensions/ui-form/helpers.ts
|
|
452
|
+
var RESPONSE_PREVIEW_LENGTH2 = 200;
|
|
453
|
+
var ECHO_UPGRADE_HINT = "taiji host too old for form protocol \u2014 upgrade taiji or pin extension version";
|
|
454
|
+
function assertFormQuestionsValid(form) {
|
|
455
|
+
const invalidIndex = form.findIndex((q) => !isFormQuestion(q));
|
|
456
|
+
if (invalidIndex >= 0) {
|
|
457
|
+
throw new Error(
|
|
458
|
+
`uiFormInteract(): form[${invalidIndex}] is not a valid FormQuestion (type must be 'choice' | 'text' | 'schedule' with required fields present) \u2014 fix the question definition at the call site.`
|
|
459
|
+
);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
function assertRpcModeAvailable(ctx) {
|
|
463
|
+
if (!(isGuiCapable(ctx) && ctx.ui?.select)) {
|
|
464
|
+
throw new Error(
|
|
465
|
+
"uiFormInteract() is only available in RPC mode. In TUI mode, use ctx.ui.custom() with your own Component directly."
|
|
466
|
+
);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
function parseFormAnswersResponse(rpcValue, payload, opts) {
|
|
470
|
+
if (rpcValue === payload) {
|
|
471
|
+
opts?.log?.("ui-form response echoed the request payload (host does not understand UI_FORM_MARKER)", {
|
|
472
|
+
responseHead: rpcValue.slice(0, RESPONSE_PREVIEW_LENGTH2)
|
|
473
|
+
});
|
|
474
|
+
return { ok: false, reason: "channel-error", message: ECHO_UPGRADE_HINT };
|
|
475
|
+
}
|
|
476
|
+
let parsed;
|
|
477
|
+
try {
|
|
478
|
+
parsed = JSON.parse(rpcValue);
|
|
479
|
+
} catch {
|
|
480
|
+
return { ok: false, reason: "non-json" };
|
|
481
|
+
}
|
|
482
|
+
if (!isFormAnswers(parsed)) {
|
|
483
|
+
opts?.log?.("ui-form response is not a FormAnswers record", {
|
|
484
|
+
responseHead: rpcValue.slice(0, RESPONSE_PREVIEW_LENGTH2)
|
|
485
|
+
});
|
|
486
|
+
return { ok: false, reason: "non-json" };
|
|
487
|
+
}
|
|
488
|
+
return { ok: true, answers: parsed };
|
|
489
|
+
}
|
|
490
|
+
async function uiFormInteract(ctx, form, opts) {
|
|
491
|
+
if (form.length === 0) return { ok: true, answers: {} };
|
|
492
|
+
assertFormQuestionsValid(form);
|
|
493
|
+
assertRpcModeAvailable(ctx);
|
|
494
|
+
const payload = JSON.stringify(stripUndefined({
|
|
495
|
+
formQuestions: form,
|
|
496
|
+
allowCancel: opts?.allowCancel ?? true,
|
|
497
|
+
// expectTurn 原样透传、不设 ?? true 缺省:undefined 由 stripUndefined 剥键保持
|
|
498
|
+
// 存量 wire 逐字节不变,三态归 respond 分型按 === false 显式判定(D1/D2)
|
|
499
|
+
expectTurn: opts?.expectTurn
|
|
500
|
+
}));
|
|
501
|
+
const rpcResult = await callMarkerRpc(
|
|
502
|
+
ctx,
|
|
503
|
+
UI_FORM_MARKER,
|
|
504
|
+
payload,
|
|
505
|
+
{ signal: opts?.signal, log: opts?.log }
|
|
506
|
+
);
|
|
507
|
+
if (!rpcResult.ok) {
|
|
508
|
+
return { ok: false, reason: rpcResult.reason };
|
|
509
|
+
}
|
|
510
|
+
return parseFormAnswersResponse(rpcResult.value, payload, opts);
|
|
511
|
+
}
|
|
512
|
+
|
|
212
513
|
// src/extensions/session-manager/types.ts
|
|
213
514
|
function isRecord(v) {
|
|
214
515
|
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
215
516
|
}
|
|
216
|
-
function
|
|
517
|
+
function isOptionalString2(v) {
|
|
217
518
|
return v === void 0 || typeof v === "string";
|
|
218
519
|
}
|
|
219
520
|
function isSessionIdParams(v) {
|
|
220
521
|
return isRecord(v) && typeof v.sessionId === "string";
|
|
221
522
|
}
|
|
222
523
|
function isSessionManagerCreateParams(v) {
|
|
223
|
-
return isRecord(v) &&
|
|
524
|
+
return isRecord(v) && isOptionalString2(v.cwd) && isOptionalString2(v.label) && isOptionalString2(v.prompt);
|
|
224
525
|
}
|
|
225
526
|
function isSessionManagerSendParams(v) {
|
|
226
527
|
return isRecord(v) && typeof v.sessionId === "string" && typeof v.prompt === "string";
|
|
@@ -362,22 +663,35 @@ export {
|
|
|
362
663
|
BRIDGE_MARKER,
|
|
363
664
|
BRIDGE_METHODS,
|
|
364
665
|
GUI_WIDGET_MARKER,
|
|
666
|
+
HISTORY_LIMIT,
|
|
365
667
|
INFLIGHT_REPORT_ACK,
|
|
366
668
|
MAX_TERMINAL_REGISTRY_ENTRIES,
|
|
669
|
+
MS_PER_DAY,
|
|
670
|
+
MS_PER_HOUR,
|
|
671
|
+
MS_PER_MINUTE,
|
|
672
|
+
MS_PER_SECOND,
|
|
367
673
|
PENDING_REGISTER_ENTRY_TYPE,
|
|
368
674
|
PENDING_UNREGISTER_ENTRY_TYPE,
|
|
675
|
+
PLAN_REVIEW_MARKER,
|
|
369
676
|
PROTOCOL_VERSION,
|
|
677
|
+
SCHEDULE_CREATE_MARKER,
|
|
370
678
|
SESSION_MANAGER_ACTIONS,
|
|
371
679
|
SESSION_MANAGER_MARKER,
|
|
372
680
|
SUBAGENTS_ENGINES_FILENAME,
|
|
373
681
|
SUBAGENT_INFLIGHT_MARKER,
|
|
682
|
+
TASK_ENTRY_TYPE,
|
|
683
|
+
UI_FORM_MARKER,
|
|
684
|
+
appendExecutionRecord,
|
|
374
685
|
applyPendingDiff,
|
|
375
|
-
askUserInteract,
|
|
376
686
|
callMarkerRpc,
|
|
377
687
|
collectActivePendingIds,
|
|
688
|
+
dateToOnceCron,
|
|
378
689
|
extractGui,
|
|
379
690
|
firstContentText,
|
|
380
691
|
formatChannelErrorText,
|
|
692
|
+
formatDuration,
|
|
693
|
+
formatRelativeTime,
|
|
694
|
+
formatSchedule,
|
|
381
695
|
getAskUserAnswer,
|
|
382
696
|
getAskUserOther,
|
|
383
697
|
guiComponent,
|
|
@@ -391,10 +705,14 @@ export {
|
|
|
391
705
|
isBridgeSyncPayload,
|
|
392
706
|
isBridgeToolExecuteResponse,
|
|
393
707
|
isChannelErrorResult,
|
|
708
|
+
isFormAnswers,
|
|
709
|
+
isFormQuestion,
|
|
394
710
|
isGuiCapable,
|
|
395
711
|
isGuiComponent,
|
|
396
712
|
isGuiRenderResult,
|
|
397
713
|
isInFlightReportAck,
|
|
714
|
+
isScheduleDraft,
|
|
715
|
+
isScheduleFormResult,
|
|
398
716
|
isSessionManagerAbortParams,
|
|
399
717
|
isSessionManagerCreateParams,
|
|
400
718
|
isSessionManagerHistoryParams,
|
|
@@ -405,7 +723,11 @@ export {
|
|
|
405
723
|
isSyncedTool,
|
|
406
724
|
isTerminalBackgroundTaskState,
|
|
407
725
|
mapReasonToStatus,
|
|
726
|
+
onceCronToDate,
|
|
727
|
+
replayFoldEntries,
|
|
408
728
|
scanPendingEntries,
|
|
409
729
|
setWidgetDual,
|
|
730
|
+
snapshotToTask,
|
|
731
|
+
uiFormInteract,
|
|
410
732
|
validateWidgetIconPaths
|
|
411
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",
|