@zhushanwen/extension-protocol 0.12.0 → 0.13.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 渲染协议核心类型定义。
|
|
@@ -145,6 +145,46 @@ interface TreeItem {
|
|
|
145
145
|
children?: TreeItem[];
|
|
146
146
|
}
|
|
147
147
|
type TreeItemIcon = 'arrow' | 'check' | 'cross' | 'circle' | 'dot' | 'pause' | 'branch';
|
|
148
|
+
/**
|
|
149
|
+
* 计划产物文档元数据——agent 调 register-doc 登记的一份产物。
|
|
150
|
+
* version 由 extension 侧维护:修订后重写文档须重调 register-doc(version+1),
|
|
151
|
+
* 前端凭 version 变化重拉 file.read 刷新渲染。
|
|
152
|
+
*/
|
|
153
|
+
interface PlanDocMeta {
|
|
154
|
+
/** 文件名(drawer L2 文档 tab 标题,不含目录) */
|
|
155
|
+
fileName: string;
|
|
156
|
+
/** 文件绝对路径(前端 file.read RPC 带 sessionId 读取) */
|
|
157
|
+
absPath: string;
|
|
158
|
+
/** 来源技能名(挂载 --skills 时产出该文档的技能;模板流程产出时为空串) */
|
|
159
|
+
sourceSkill: string;
|
|
160
|
+
/** 修订版本,从 1 起,每次修订重登记 +1 */
|
|
161
|
+
version: number;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* submit-review 挂起审批时的 select payload(序列化为 options[0] JSON)。
|
|
165
|
+
* extension 侧解析失败走 E5(logger.warn + tool result 报错提示重挂,垃圾数据不进对话流)。
|
|
166
|
+
*/
|
|
167
|
+
interface PlanReviewRequest {
|
|
168
|
+
docs: PlanDocMeta[];
|
|
169
|
+
}
|
|
170
|
+
/** 审批两键裁决。revise 携带评论、approve 不携带(结构上不可混带)。 */
|
|
171
|
+
type PlanReviewDecision = 'approve' | 'revise';
|
|
172
|
+
/** 用户对某文档划选段落的一条评论:quote 是划选引文(agent 定位段落用),comment 是评语。 */
|
|
173
|
+
interface PlanReviewComment {
|
|
174
|
+
quote: string;
|
|
175
|
+
comment: string;
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* 审批条 respond 回传(判别联合:approve 无评论字段,revise 必带评论数组)。
|
|
179
|
+
* extension 消费:approve → 走现状 complete 执行方式 select;revise → 评论清单以
|
|
180
|
+
* 显式 deliverAs:'steer' 注入 + reviewState=revising。
|
|
181
|
+
*/
|
|
182
|
+
type PlanReviewResponse = {
|
|
183
|
+
decision: 'approve';
|
|
184
|
+
} | {
|
|
185
|
+
decision: 'revise';
|
|
186
|
+
comments: PlanReviewComment[];
|
|
187
|
+
};
|
|
148
188
|
|
|
149
189
|
/**
|
|
150
190
|
* 通用传输 marker。extension 用 guiSetWidget 编码进 string[],
|
|
@@ -153,6 +193,17 @@ type TreeItemIcon = 'arrow' | 'check' | 'cross' | 'circle' | 'dot' | 'pause' | '
|
|
|
153
193
|
* NUL 字符开头的 marker,不会出现在正常文本中。
|
|
154
194
|
*/
|
|
155
195
|
declare const GUI_WIDGET_MARKER = "\0TAIJI_GUI_WIDGET:";
|
|
196
|
+
/**
|
|
197
|
+
* plan 审阅请求的 select title marker。plan extension 的 submit-review 挂起审批时
|
|
198
|
+
* 以此 marker 为 title 发 ctx.ui.select(options[0] = PlanReviewRequest JSON,
|
|
199
|
+
* 类型见 core/types.ts),runtime event-adapter 检测后广播 extension_ui_request
|
|
200
|
+
* (planReview 标记,与 askUser 标记同构分流),前端审批条渲染三键审批而非
|
|
201
|
+
* 原始 dialog——marker 控制符 title 落入通用 dialog 会渲染成乱码。
|
|
202
|
+
*
|
|
203
|
+
* NUL 前缀确保不会与 extension 正常的 select title 冲突,
|
|
204
|
+
* 与 GUI_WIDGET_MARKER / ASK_USER_MARKER 同族(select 通道 marker)。
|
|
205
|
+
*/
|
|
206
|
+
declare const PLAN_REVIEW_MARKER = "\0TAIJI_PLAN_REVIEW:";
|
|
156
207
|
|
|
157
208
|
/**
|
|
158
209
|
* helpers 需要的 ctx 最小接口。
|
|
@@ -364,13 +415,15 @@ declare function callMarkerRpc(ctx: GuiContext, marker: string, payload: string,
|
|
|
364
415
|
/**
|
|
365
416
|
* ask-user extension 的富交互类型定义。
|
|
366
417
|
*
|
|
367
|
-
*
|
|
368
|
-
*
|
|
369
|
-
*
|
|
370
|
-
* 前端 AskUserOverlay 渲染富交互 UI。
|
|
418
|
+
* GUI 提问交互已迁移统一表单协议(../ui-form:FormQuestion 类型化问题集 +
|
|
419
|
+
* UI_FORM_MARKER + uiFormInteract,前端 FormOverlay 渲染)——ask_user 工具的
|
|
420
|
+
* 问题在 ask-user 包内归一 adapter 转为 FormQuestion(choice/text 形态)发送。
|
|
371
421
|
*
|
|
372
|
-
*
|
|
373
|
-
*
|
|
422
|
+
* 本模块保留的 AskUserQuestion / AskUserAnswers 是 ask-user 的 LLM 入参契约与
|
|
423
|
+
* TUI/解码消费形态:FormAnswers 的 choice/text 部分与 AskUserAnswers 逐字兼容
|
|
424
|
+
* (键位规则与多选序列化一致),解码 helper(getAskUserAnswer/getAskUserOther)
|
|
425
|
+
* 消费本类型。legacy 帧(ASK_USER_MARKER)由 runtime event-adapter 分支归一转
|
|
426
|
+
* FormQuestion(D7 兼容窗口,归一层在 runtime 侧),随窗口末清理退役。
|
|
374
427
|
*/
|
|
375
428
|
/**
|
|
376
429
|
* ask-user 富交互问题声明。
|
|
@@ -424,30 +477,18 @@ type AskUserAnswers = Record<string, string>;
|
|
|
424
477
|
declare const ASK_USER_MARKER = "\0TAIJI_ASK_USER";
|
|
425
478
|
|
|
426
479
|
/**
|
|
427
|
-
* ask-user extension 的富交互 helper
|
|
480
|
+
* ask-user extension 的富交互 helper(解码层)。
|
|
428
481
|
*
|
|
429
|
-
*
|
|
430
|
-
*
|
|
482
|
+
* GUI 提问交互已迁移统一表单协议(ui-form 模块的 uiFormInteract + UI_FORM_MARKER,
|
|
483
|
+
* 设计 ui-presentation-protocol D3)——askUserInteract 传输 helper 随之退役,不保留
|
|
484
|
+
* deprecated 别名;AskUserQuestion↔FormQuestion 的归一职责下沉 ask-user 包内 adapter
|
|
485
|
+
* (extensions/universal/ask-user/src/form-adapter.ts)。
|
|
431
486
|
*
|
|
432
|
-
*
|
|
433
|
-
*
|
|
487
|
+
* 本模块保留 answers 解码 helper(getAskUserAnswer / getAskUserOther):FormAnswers 的
|
|
488
|
+
* choice/text 部分与 AskUserAnswers 逐字兼容(键位规则:key = header ?? question,
|
|
489
|
+
* 多选 = JSON 数组,Other = `${key}__other`),解码契约在此单源。
|
|
434
490
|
*/
|
|
435
491
|
|
|
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
492
|
/**
|
|
452
493
|
* 从 answers 中提取某个问题的选中值(单选返回 string,多选返回 string[])。
|
|
453
494
|
*
|
|
@@ -459,10 +500,288 @@ declare function getAskUserAnswer(answers: AskUserAnswers, question: AskUserQues
|
|
|
459
500
|
declare function getAskUserOther(answers: AskUserAnswers, question: AskUserQuestion): string | undefined;
|
|
460
501
|
/**
|
|
461
502
|
* 类型守卫:验证 unknown 是否为合法的 AskUserQuestion。
|
|
462
|
-
*
|
|
503
|
+
* 供 legacy 帧消费方(runtime marker 分支侧形状参照 / 扩展自测)收窄 unknown[] 用。
|
|
463
504
|
*/
|
|
464
505
|
declare function isAskUserQuestion(value: unknown): value is AskUserQuestion;
|
|
465
506
|
|
|
507
|
+
/**
|
|
508
|
+
* scheduler 创建确认弹框的协议类型(Draft / FormResult)。
|
|
509
|
+
*
|
|
510
|
+
* GUI 创建确认已迁移统一表单协议(ui-form 模块的 uiFormInteract + UI_FORM_MARKER,
|
|
511
|
+
* 交互入口 extensions/universal/scheduler/src/tool.ts:Draft 经 initial 预填为
|
|
512
|
+
* ScheduleQuestion 单问整表单,前端 FormOverlay 的 schedule 渲染器回传
|
|
513
|
+
* ScheduleFormResult)——ScheduleDraft / ScheduleFormResult 即该表单的预填与
|
|
514
|
+
* 回传形状,TUI 侧 ScheduleCreateComponent 同用本类型。
|
|
515
|
+
*
|
|
516
|
+
* 取消不走 FormResult:interact 取消 resolve undefined,execute 返回
|
|
517
|
+
* cancelled result(D5),任务不创建。
|
|
518
|
+
*
|
|
519
|
+
* SCHEDULE_CREATE_MARKER / ScheduleCreateOverlay 为 legacy 窗口语义(旧 npm 帧
|
|
520
|
+
* 由 runtime event-adapter 识别 / 迁移溯源锚点),随 D7 窗口末清理退役。
|
|
521
|
+
*
|
|
522
|
+
* 这是 scheduler 创建路径的定制协议,不是通用富交互协议(与 ask-user 同理)。
|
|
523
|
+
*/
|
|
524
|
+
/**
|
|
525
|
+
* 执行模式:once = 一次性(schedule 为 dateToOnceCron 折叠的一次性 cron);
|
|
526
|
+
* recurring = 循环(duration 或 cron)。
|
|
527
|
+
*/
|
|
528
|
+
type ScheduleKind = 'once' | 'recurring';
|
|
529
|
+
/** 创建确认草稿(extension → 前端,LLM 参数即预填值) */
|
|
530
|
+
interface ScheduleDraft {
|
|
531
|
+
/** LLM 判定的执行模式,用户可改 */
|
|
532
|
+
kind: ScheduleKind;
|
|
533
|
+
/**
|
|
534
|
+
* recurring:duration(如 5m/2h)或 cron;once:已折叠的一次性 cron
|
|
535
|
+
* (唯一时间来源,表单时间初值由 onceCronToDate 还原,D2)
|
|
536
|
+
*/
|
|
537
|
+
schedule: string;
|
|
538
|
+
/** scoped model id;缺省 = 跟随会话当前模型 */
|
|
539
|
+
model?: string;
|
|
540
|
+
/** agent 起草的提示词 */
|
|
541
|
+
prompt: string;
|
|
542
|
+
/** 任务名(同现有工具参数,高级选项) */
|
|
543
|
+
name?: string;
|
|
544
|
+
/** 过期时间(同现有工具参数,高级选项) */
|
|
545
|
+
expires?: string;
|
|
546
|
+
/** scoped models 列表,前端不再自取 */
|
|
547
|
+
models: string[];
|
|
548
|
+
/** 会话当前模型 id(前端预选标记) */
|
|
549
|
+
currentModel?: string;
|
|
550
|
+
}
|
|
551
|
+
/**
|
|
552
|
+
* 创建确认回传(前端 → extension)。
|
|
553
|
+
* 取消不走此形状:select resolve undefined → execute 返回 cancelled result(D5)。
|
|
554
|
+
*/
|
|
555
|
+
interface ScheduleFormResult {
|
|
556
|
+
action: 'create';
|
|
557
|
+
kind: ScheduleKind;
|
|
558
|
+
/** 用户确认后的最终形态(once = 经 dateToOnceCron 折叠的一次性 cron) */
|
|
559
|
+
schedule: string;
|
|
560
|
+
/** 用户最终选择;未选 = 缺省(跟随会话当前模型) */
|
|
561
|
+
model?: string;
|
|
562
|
+
prompt: string;
|
|
563
|
+
name?: string;
|
|
564
|
+
expires?: string;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
/**
|
|
568
|
+
* scheduler 创建确认请求的 title marker。runtime event-adapter 和前端 useExtensionUI
|
|
569
|
+
* 检测此 marker 区分 schedule 创建确认请求与普通 select。
|
|
570
|
+
*
|
|
571
|
+
* NUL 前缀确保不会与 extension 正常的 select title 冲突。
|
|
572
|
+
* 与 ASK_USER_MARKER 同规范(ask-user 模块同包 marker.ts)。
|
|
573
|
+
*/
|
|
574
|
+
declare const SCHEDULE_CREATE_MARKER = "\0TAIJI_SCHEDULE_CREATE";
|
|
575
|
+
|
|
576
|
+
/**
|
|
577
|
+
* scheduler 创建确认的共享协议资产(定制交互 helper 已退役)。
|
|
578
|
+
*
|
|
579
|
+
* scheduleCreateInteract() 已随统一提问表单协议迁移退役(ui-presentation-protocol
|
|
580
|
+
* D3/u6,直接退役不保留 deprecated 别名):scheduler 的创建确认改走
|
|
581
|
+
* uiFormInteract() + UI_FORM_MARKER(ScheduleQuestion 形态,initial 预填草稿),
|
|
582
|
+
* 交互入口在 extensions/universal/scheduler/src/tool.ts。
|
|
583
|
+
*
|
|
584
|
+
* 本模块保留三类跨端共享资产:
|
|
585
|
+
* - isScheduleDraft / isScheduleFormResult 形状守卫:前者供 runtime event-adapter
|
|
586
|
+
* legacy 分支(SCHEDULE_CREATE_MARKER 帧 payload 判定)与 ui-form 守卫的
|
|
587
|
+
* schedule 分支(initial 深度校验)消费;后者供 scheduler 包回包判别
|
|
588
|
+
* (uiFormInteract answers[key] 解包后收窄为 ScheduleFormResult)。
|
|
589
|
+
* - dateToOnceCron / onceCronToDate 时间折叠单点(D2):GUI(ScheduleForm 渲染器)
|
|
590
|
+
* 与 TUI(ScheduleCreateComponent)共用本实现,禁止双端各写一份——时间语义
|
|
591
|
+
* 漂移正是本协议要关闭的失败模式(F1)。
|
|
592
|
+
*
|
|
593
|
+
* 时区语义:按本地墙钟时间(croner 对 cron 默认本地解释,datetime 控件与 TUI
|
|
594
|
+
* 掩码编辑均产出本地墙钟时间)。
|
|
595
|
+
*/
|
|
596
|
+
|
|
597
|
+
/**
|
|
598
|
+
* 类型守卫:验证 unknown 是否为合法的 ScheduleDraft。
|
|
599
|
+
* 用于 runtime event-adapter 判定 marker select 的 payload 是否为本协议 draft
|
|
600
|
+
* (检测失败降级普通 select),以及前端从 runtime 透传值中安全收窄。
|
|
601
|
+
* 字段白名单校验(与 isAskUserQuestion 同构):可选字段校验类型,未知附加字段忽略。
|
|
602
|
+
*/
|
|
603
|
+
declare function isScheduleDraft(value: unknown): value is ScheduleDraft;
|
|
604
|
+
/** 回传形状守卫(导出消费:scheduler 包对 uiFormInteract answers[key] 解包值收窄;
|
|
605
|
+
* FormOverlay schedule 渲染器提交的 value = JSON.stringify(ScheduleFormResult)) */
|
|
606
|
+
declare function isScheduleFormResult(value: unknown): value is ScheduleFormResult;
|
|
607
|
+
/**
|
|
608
|
+
* 本地墙钟时刻 → 一次性 cron(5 段 `分 时 日 月 *`)。
|
|
609
|
+
*
|
|
610
|
+
* 一次性 cron 语义:具体日 + 具体月 + 星期通配 → croner 天然只命中一次(S8/D2),
|
|
611
|
+
* scheduler 现有 parseSchedule 直接接受,service 层零感知。
|
|
612
|
+
*/
|
|
613
|
+
declare function dateToOnceCron(date: Date): string;
|
|
614
|
+
declare function onceCronToDate(cron: string, now?: Date): Date | null;
|
|
615
|
+
|
|
616
|
+
/**
|
|
617
|
+
* 统一提问表单协议(ui-form)的类型定义。
|
|
618
|
+
*
|
|
619
|
+
* 把 plan / scheduler / ask-user 三个 extension 的提问交互收口为一个表单协议:
|
|
620
|
+
* 问题即数据(类型化问题集),GUI 链路统一(wire 协议 + 渲染器)——多问 = 多 tab,
|
|
621
|
+
* 单问 = 单视图。extension 侧经 uiFormInteract() 序列化进 select 的 options[0],
|
|
622
|
+
* runtime event-adapter 检测 UI_FORM_MARKER 广播 form 帧,前端 FormOverlay
|
|
623
|
+
* 按问题 type 分派渲染器(choice / text / schedule)。
|
|
624
|
+
*
|
|
625
|
+
* 这是提问交互的通用协议(区别于 ask-user / scheduler-create 的定制协议)。
|
|
626
|
+
* formQuestions 在 TUI 无呈现语义——TUI 数据通路各 extension 自行渲染(设计 D8
|
|
627
|
+
* 有意取舍),消费方为 extension 自有组件。
|
|
628
|
+
*/
|
|
629
|
+
|
|
630
|
+
/**
|
|
631
|
+
* choice 问题:互斥/多选选项。
|
|
632
|
+
* 对应现 AskUserQuestion 有 options 的形态(multiSelect → multi 重命名)。
|
|
633
|
+
*/
|
|
634
|
+
interface ChoiceQuestion {
|
|
635
|
+
type: 'choice';
|
|
636
|
+
/** Tab 标签 / 简短标题。answers key 缺省 fallback 到 question 全文(与 askUserKey 同规则) */
|
|
637
|
+
header?: string;
|
|
638
|
+
/** 完整问题文本。也作为 answers 的 fallback key(header 缺失时) */
|
|
639
|
+
question: string;
|
|
640
|
+
/** 上下文摘要(可选)。显示在问题上方,帮用户理解背景 */
|
|
641
|
+
context?: string;
|
|
642
|
+
/** 互斥选项列表(无 options 的纯自由文本问题用 TextQuestion 表达) */
|
|
643
|
+
options: FormOption[];
|
|
644
|
+
/** 是否允许多选 */
|
|
645
|
+
multi?: boolean;
|
|
646
|
+
/** 是否允许自由文本输入(Other)。默认 true(同 ask-user 现状),设 false 则不追加 */
|
|
647
|
+
allowOther?: boolean;
|
|
648
|
+
}
|
|
649
|
+
/** 选项声明(label 即选中值:协议无独立 value 字段,回传统一用 label) */
|
|
650
|
+
interface FormOption {
|
|
651
|
+
label: string;
|
|
652
|
+
/** 描述(可选)。显示在 label 下方,解释 tradeoff */
|
|
653
|
+
description?: string;
|
|
654
|
+
}
|
|
655
|
+
/**
|
|
656
|
+
* text 问题:纯自由文本输入(无 options)。
|
|
657
|
+
* 协议值域完备性要求保留:AskUserQuestion.options 声明可选,归一层 type 推断
|
|
658
|
+
* 与消费端必须能表达「无 options」的合法旧形态(设计 D2)。
|
|
659
|
+
*/
|
|
660
|
+
interface TextQuestion {
|
|
661
|
+
type: 'text';
|
|
662
|
+
header?: string;
|
|
663
|
+
question: string;
|
|
664
|
+
context?: string;
|
|
665
|
+
}
|
|
666
|
+
/**
|
|
667
|
+
* schedule 问题:时间输入领域原语(非 scheduler 专属形状)。
|
|
668
|
+
* 渲染器 = scheduler 创建确认整表单(ScheduleForm,单 tab 整表单收编,设计 D2);
|
|
669
|
+
* 预填草稿经 initial 直传,打开即可一键确认。
|
|
670
|
+
*/
|
|
671
|
+
interface ScheduleQuestion {
|
|
672
|
+
type: 'schedule';
|
|
673
|
+
header?: string;
|
|
674
|
+
question: string;
|
|
675
|
+
context?: string;
|
|
676
|
+
/** 预填草稿(可选)。ScheduleDraft 复用 scheduler-create 模块定义(零新增依赖方向) */
|
|
677
|
+
initial?: ScheduleDraft;
|
|
678
|
+
}
|
|
679
|
+
/** 类型化问题集(判别联合:按 type 分派渲染器) */
|
|
680
|
+
type FormQuestion = ChoiceQuestion | TextQuestion | ScheduleQuestion;
|
|
681
|
+
/**
|
|
682
|
+
* 统一表单回传结果。key = header ?? question(fallback 规则与 askUserKey 一致)。
|
|
683
|
+
*
|
|
684
|
+
* 答案编码规则(choice/text 部分与现 AskUserAnswers 逐字兼容,含 Other 键规则
|
|
685
|
+
* 与多选序列化——ask-user 消费方 getAskUserAnswer / getAskUserOther 零改动):
|
|
686
|
+
* - choice 单选:value = 选中项的 label
|
|
687
|
+
* - choice 多选:value = JSON.stringify(选中项 label 数组),如 '["pg","mysql"]'
|
|
688
|
+
* - Other 文本:单独 key `${key}__other`,value = 自由文本(不混进选中项数组)
|
|
689
|
+
* - text:value = 自由文本,**键位 `${key}__other`**(逐字继承现状纯 other 形态——
|
|
690
|
+
* 无 options 题答案只写 __other 不写主 key,写主 key 会令 getAskUserAnswer 键位翻转)
|
|
691
|
+
* - schedule:value = JSON.stringify(ScheduleFormResult)(单问表单下 answers 恰一键)
|
|
692
|
+
*/
|
|
693
|
+
type FormAnswers = Record<string, string>;
|
|
694
|
+
|
|
695
|
+
/**
|
|
696
|
+
* 统一提问表单(ui-form)请求的 title marker。runtime event-adapter 和前端
|
|
697
|
+
* useExtensionUI 检测此 marker 区分表单请求与普通 select。
|
|
698
|
+
*
|
|
699
|
+
* NUL 前缀确保不会与 extension 正常的 select title 冲突。
|
|
700
|
+
* 与 ASK_USER_MARKER 同规范(ask-user 模块同包 marker.ts)。
|
|
701
|
+
*/
|
|
702
|
+
declare const UI_FORM_MARKER = "\0TAIJI_UI_FORM";
|
|
703
|
+
|
|
704
|
+
/**
|
|
705
|
+
* 统一提问表单(ui-form)协议 helper。
|
|
706
|
+
*
|
|
707
|
+
* uiFormInteract() 是 plan / scheduler / ask-user 三方提问在 RPC 模式下的统一入口
|
|
708
|
+
* (设计 ui-presentation-protocol D3)。走 select 通道 + marker(UI_FORM_MARKER),
|
|
709
|
+
* 传输核复用 callMarkerRpc 原语,四态判别返回而非抛错(除 RPC 门外):
|
|
710
|
+
* - cancelled / timeout → 调用方按「用户未作答」折叠;
|
|
711
|
+
* - channel-error → 通道契约破坏(含 echo 检测命中的旧宿主组合),调用方按各自
|
|
712
|
+
* 策略折叠(scheduler 禁用工具 + throw / plan 折 cancelled result 留 plan mode);
|
|
713
|
+
* - non-json → 协议版本错配类故障。
|
|
714
|
+
*
|
|
715
|
+
* TUI 模式不代劳渲染(formQuestions 在 TUI 无呈现语义,各 extension 自有组件
|
|
716
|
+
* 消费,设计 D8):误调抛错指明恢复动作,返回判别失败会与「用户取消」混淆。
|
|
717
|
+
*/
|
|
718
|
+
|
|
719
|
+
/** uiFormInteract 的判别结果:成功收窄为 FormAnswers;失败按 callMarkerRpc 四态折叠。
|
|
720
|
+
* message 仅 channel-error 的 echo 命中态携带(升级指引,见 uiFormInteract 内注释),
|
|
721
|
+
* 供调用方折叠时透出——各调用方对 channel-error 的折叠策略不同,文案由调用方决定是否使用 */
|
|
722
|
+
type UiFormInteractResult = {
|
|
723
|
+
ok: true;
|
|
724
|
+
answers: FormAnswers;
|
|
725
|
+
} | {
|
|
726
|
+
ok: false;
|
|
727
|
+
reason: 'cancelled' | 'timeout' | 'channel-error' | 'non-json';
|
|
728
|
+
message?: string;
|
|
729
|
+
};
|
|
730
|
+
interface UiFormInteractOptions {
|
|
731
|
+
/** 透传 select dialog:abort 后 pi 本地 resolve(undefined) → cancelled */
|
|
732
|
+
signal?: AbortSignal;
|
|
733
|
+
/** 前端是否显示取消按钮(默认 true,沿已退役的 askUserInteract 的默认值先例) */
|
|
734
|
+
allowCancel?: boolean;
|
|
735
|
+
/**
|
|
736
|
+
* 源元数据:本次表单提交后是否有 turn 预期(respond 分型判据,form-submit-busy-convergence D1)。
|
|
737
|
+
*
|
|
738
|
+
* 三态语义(D2):`true | undefined` = 有 turn 预期——提交型桥接 message_start 照旧
|
|
739
|
+
* (undefined 为缺省态:存量扩展与未声明者,桥接 = 现状,方向安全);
|
|
740
|
+
* `false` = 无 turn 预期——命令 handler 内 select(提交后结构性无 turn),respond 侧
|
|
741
|
+
* 提交即收尾,消除 30s 假忙。消费方判定必须是 `=== false` 显式式:truthy 简化
|
|
742
|
+
* (`!expectTurn`)会把 undefined 也当无 turn、误清桥接(D2 被否谱系)。
|
|
743
|
+
*
|
|
744
|
+
* 缺省(undefined)经 stripUndefined 剥键——未声明者的 wire payload 逐字节不变,
|
|
745
|
+
* 旧 runtime 读不到即忽略(JSON.parse 容忍未知键)。
|
|
746
|
+
*/
|
|
747
|
+
expectTurn?: boolean;
|
|
748
|
+
/** 失败留痕注入(echo 命中 / 形状错),日志策略归调用方 */
|
|
749
|
+
log?: (msg: string, detail?: object) => void;
|
|
750
|
+
}
|
|
751
|
+
/**
|
|
752
|
+
* 统一提问表单入口(RPC 模式专用)。
|
|
753
|
+
*
|
|
754
|
+
* RPC 模式:select 通道携带类型化问题集,前端 FormOverlay 渲染表单,回传 FormAnswers。
|
|
755
|
+
* TUI 模式:抛错,extension 自行渲染(ctx.ui.custom 或原生 select)。
|
|
756
|
+
*
|
|
757
|
+
* 确认交互不设墙钟超时(任务级正常路径无墙钟,AGENTS.md 超时默认原则):
|
|
758
|
+
* options 不提供 timeout 入口,timeout 态仅由「未 abort 的 undefined resolve」产生。
|
|
759
|
+
*/
|
|
760
|
+
declare function uiFormInteract(ctx: GuiContext, form: FormQuestion[], opts?: UiFormInteractOptions): Promise<UiFormInteractResult>;
|
|
761
|
+
|
|
762
|
+
/**
|
|
763
|
+
* 统一提问表单(ui-form)的形状守卫。
|
|
764
|
+
*
|
|
765
|
+
* isFormQuestion 按 type 判别分支逐项校验(白名单风格:必填字段类型校验、可选
|
|
766
|
+
* 字段校验、未知字段忽略——与 isAskUserQuestion / isScheduleDraft 同构语义)。
|
|
767
|
+
* 三个消费点失败策略各异(设计 D2):
|
|
768
|
+
* - 发送侧(uiFormInteract 内):不合法项抛错(调用方编码 bug,fail-fast);
|
|
769
|
+
* - event-adapter:逐项过滤,合法项 ≥1 才产 form 帧、全不合法降级普通 select;
|
|
770
|
+
* - renderer:复核守卫,不合法项跳过 + warn,全部不合法 → overlay 错误占位仅取消可点。
|
|
771
|
+
*
|
|
772
|
+
* isFormAnswers 校验回包形状(Record<string, string>):键动态(key = header ??
|
|
773
|
+
* question)无法枚举白名单,故只校验值域全 string;JSON 合法但非本形状由
|
|
774
|
+
* uiFormInteract 折叠 non-json(协议版本错配类故障)。
|
|
775
|
+
*/
|
|
776
|
+
|
|
777
|
+
/**
|
|
778
|
+
* 类型守卫:验证 unknown 是否为合法的 FormQuestion(choice / text / schedule 三分支)。
|
|
779
|
+
* 用于发送侧 fail-fast、event-adapter 逐项过滤与 renderer 复核(三消费点见文件头)。
|
|
780
|
+
*/
|
|
781
|
+
declare function isFormQuestion(value: unknown): value is FormQuestion;
|
|
782
|
+
/** 类型守卫:验证 unknown 是否为合法的 FormAnswers(值域全 string 的普通对象) */
|
|
783
|
+
declare function isFormAnswers(value: unknown): value is FormAnswers;
|
|
784
|
+
|
|
466
785
|
/**
|
|
467
786
|
* session-manager 请求的 title marker。runtime event-adapter 和 handler
|
|
468
787
|
* 检测此 marker 区分 session-manager 请求与普通 select。
|
|
@@ -851,4 +1170,4 @@ declare function collectActivePendingIds(entries: unknown[], opts?: CollectPendi
|
|
|
851
1170
|
type MappedPendingStatus = 'completed' | 'failed' | 'cancelled' | 'expired' | 'time_limited' | 'aborted';
|
|
852
1171
|
declare function mapReasonToStatus(reason: string): MappedPendingStatus;
|
|
853
1172
|
|
|
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, applyPendingDiff,
|
|
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 };
|
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,173 @@ 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-create/marker.ts
|
|
189
|
+
var SCHEDULE_CREATE_MARKER = "\0TAIJI_SCHEDULE_CREATE";
|
|
190
|
+
|
|
191
|
+
// src/extensions/scheduler-create/helpers.ts
|
|
192
|
+
function isPlainRecord(value) {
|
|
193
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
194
|
+
}
|
|
195
|
+
function isOptionalString(v) {
|
|
196
|
+
return v === void 0 || typeof v === "string";
|
|
197
|
+
}
|
|
198
|
+
function isStringArray(v) {
|
|
199
|
+
return Array.isArray(v) && v.every((m) => typeof m === "string");
|
|
200
|
+
}
|
|
201
|
+
function isScheduleDraft(value) {
|
|
202
|
+
if (!isPlainRecord(value)) return false;
|
|
203
|
+
const d = value;
|
|
204
|
+
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);
|
|
205
|
+
}
|
|
206
|
+
function isScheduleFormResult(value) {
|
|
207
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
|
|
208
|
+
const r = value;
|
|
209
|
+
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");
|
|
210
|
+
}
|
|
211
|
+
function dateToOnceCron(date) {
|
|
212
|
+
return `${date.getMinutes()} ${date.getHours()} ${date.getDate()} ${date.getMonth() + 1} *`;
|
|
213
|
+
}
|
|
214
|
+
var ONCE_CRON_FIELD_COUNT = 5;
|
|
215
|
+
var CRON_MINUTE_MAX = 59;
|
|
216
|
+
var CRON_HOUR_MAX = 23;
|
|
217
|
+
var CRON_DAY_MIN = 1;
|
|
218
|
+
var CRON_DAY_MAX = 31;
|
|
219
|
+
var CRON_MONTH_MIN = 1;
|
|
220
|
+
var CRON_MONTH_MAX = 12;
|
|
221
|
+
var ONCE_CRON_MAX_YEAR_LOOKAHEAD = 8;
|
|
222
|
+
function parseOnceCronNumbers(parts) {
|
|
223
|
+
const [minute, hour, day, month] = parts;
|
|
224
|
+
if (![minute, hour, day, month].every((seg) => /^\d+$/.test(seg))) return null;
|
|
225
|
+
return [Number(minute), Number(hour), Number(day), Number(month)];
|
|
226
|
+
}
|
|
227
|
+
function isOnceCronValueOutOfRange(m, h, d, mon) {
|
|
228
|
+
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;
|
|
229
|
+
}
|
|
230
|
+
function onceCronToDate(cron, now = /* @__PURE__ */ new Date()) {
|
|
231
|
+
const parts = cron.trim().split(/\s+/);
|
|
232
|
+
if (parts.length !== ONCE_CRON_FIELD_COUNT || parts[4] !== "*") return null;
|
|
233
|
+
const numbers = parseOnceCronNumbers(parts);
|
|
234
|
+
if (numbers === null) return null;
|
|
235
|
+
const [m, h, d, mon] = numbers;
|
|
236
|
+
if (isOnceCronValueOutOfRange(m, h, d, mon)) return null;
|
|
237
|
+
const startYear = now.getFullYear();
|
|
238
|
+
for (let year = startYear; year <= startYear + ONCE_CRON_MAX_YEAR_LOOKAHEAD; year++) {
|
|
239
|
+
const candidate = new Date(year, mon - 1, d, h, m, 0, 0);
|
|
240
|
+
if (candidate.getMonth() !== mon - 1 || candidate.getDate() !== d) continue;
|
|
241
|
+
if (candidate.getTime() >= now.getTime()) return candidate;
|
|
242
|
+
}
|
|
243
|
+
return null;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// src/extensions/ui-form/marker.ts
|
|
247
|
+
var UI_FORM_MARKER = "\0TAIJI_UI_FORM";
|
|
248
|
+
|
|
249
|
+
// src/extensions/ui-form/guards.ts
|
|
250
|
+
function isPlainRecord2(value) {
|
|
251
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
252
|
+
}
|
|
253
|
+
function isFormOption(value) {
|
|
254
|
+
if (!isPlainRecord2(value)) return false;
|
|
255
|
+
const o = value;
|
|
256
|
+
return typeof o.label === "string" && (o.description === void 0 || typeof o.description === "string");
|
|
257
|
+
}
|
|
258
|
+
function isFormOptionArray(value) {
|
|
259
|
+
return Array.isArray(value) && value.every(isFormOption);
|
|
260
|
+
}
|
|
261
|
+
function isFormQuestion(value) {
|
|
262
|
+
if (!isPlainRecord2(value)) return false;
|
|
263
|
+
const q = value;
|
|
264
|
+
if (typeof q.question !== "string") return false;
|
|
265
|
+
if (q.header !== void 0 && typeof q.header !== "string") return false;
|
|
266
|
+
if (q.context !== void 0 && typeof q.context !== "string") return false;
|
|
267
|
+
if (q.type === "choice") {
|
|
268
|
+
return isFormOptionArray(q.options) && (q.multi === void 0 || typeof q.multi === "boolean") && (q.allowOther === void 0 || typeof q.allowOther === "boolean");
|
|
269
|
+
}
|
|
270
|
+
if (q.type === "text") return true;
|
|
271
|
+
if (q.type === "schedule") {
|
|
272
|
+
return q.initial === void 0 || isScheduleDraft(q.initial);
|
|
273
|
+
}
|
|
274
|
+
return false;
|
|
275
|
+
}
|
|
276
|
+
function isFormAnswers(value) {
|
|
277
|
+
if (!isPlainRecord2(value)) return false;
|
|
278
|
+
return Object.values(value).every((v) => typeof v === "string");
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// src/extensions/ui-form/helpers.ts
|
|
282
|
+
var RESPONSE_PREVIEW_LENGTH2 = 200;
|
|
283
|
+
var ECHO_UPGRADE_HINT = "taiji host too old for form protocol \u2014 upgrade taiji or pin extension version";
|
|
284
|
+
function assertFormQuestionsValid(form) {
|
|
285
|
+
const invalidIndex = form.findIndex((q) => !isFormQuestion(q));
|
|
286
|
+
if (invalidIndex >= 0) {
|
|
287
|
+
throw new Error(
|
|
288
|
+
`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.`
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
function assertRpcModeAvailable(ctx) {
|
|
293
|
+
if (!(isGuiCapable(ctx) && ctx.ui?.select)) {
|
|
294
|
+
throw new Error(
|
|
295
|
+
"uiFormInteract() is only available in RPC mode. In TUI mode, use ctx.ui.custom() with your own Component directly."
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
function parseFormAnswersResponse(rpcValue, payload, opts) {
|
|
300
|
+
if (rpcValue === payload) {
|
|
301
|
+
opts?.log?.("ui-form response echoed the request payload (host does not understand UI_FORM_MARKER)", {
|
|
302
|
+
responseHead: rpcValue.slice(0, RESPONSE_PREVIEW_LENGTH2)
|
|
303
|
+
});
|
|
304
|
+
return { ok: false, reason: "channel-error", message: ECHO_UPGRADE_HINT };
|
|
305
|
+
}
|
|
306
|
+
let parsed;
|
|
307
|
+
try {
|
|
308
|
+
parsed = JSON.parse(rpcValue);
|
|
309
|
+
} catch {
|
|
310
|
+
return { ok: false, reason: "non-json" };
|
|
311
|
+
}
|
|
312
|
+
if (!isFormAnswers(parsed)) {
|
|
313
|
+
opts?.log?.("ui-form response is not a FormAnswers record", {
|
|
314
|
+
responseHead: rpcValue.slice(0, RESPONSE_PREVIEW_LENGTH2)
|
|
315
|
+
});
|
|
316
|
+
return { ok: false, reason: "non-json" };
|
|
317
|
+
}
|
|
318
|
+
return { ok: true, answers: parsed };
|
|
319
|
+
}
|
|
320
|
+
async function uiFormInteract(ctx, form, opts) {
|
|
321
|
+
if (form.length === 0) return { ok: true, answers: {} };
|
|
322
|
+
assertFormQuestionsValid(form);
|
|
323
|
+
assertRpcModeAvailable(ctx);
|
|
324
|
+
const payload = JSON.stringify(stripUndefined({
|
|
325
|
+
formQuestions: form,
|
|
326
|
+
allowCancel: opts?.allowCancel ?? true,
|
|
327
|
+
// expectTurn 原样透传、不设 ?? true 缺省:undefined 由 stripUndefined 剥键保持
|
|
328
|
+
// 存量 wire 逐字节不变,三态归 respond 分型按 === false 显式判定(D1/D2)
|
|
329
|
+
expectTurn: opts?.expectTurn
|
|
330
|
+
}));
|
|
331
|
+
const rpcResult = await callMarkerRpc(
|
|
332
|
+
ctx,
|
|
333
|
+
UI_FORM_MARKER,
|
|
334
|
+
payload,
|
|
335
|
+
{ signal: opts?.signal, log: opts?.log }
|
|
336
|
+
);
|
|
337
|
+
if (!rpcResult.ok) {
|
|
338
|
+
return { ok: false, reason: rpcResult.reason };
|
|
339
|
+
}
|
|
340
|
+
return parseFormAnswersResponse(rpcResult.value, payload, opts);
|
|
341
|
+
}
|
|
342
|
+
|
|
212
343
|
// src/extensions/session-manager/types.ts
|
|
213
344
|
function isRecord(v) {
|
|
214
345
|
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
215
346
|
}
|
|
216
|
-
function
|
|
347
|
+
function isOptionalString2(v) {
|
|
217
348
|
return v === void 0 || typeof v === "string";
|
|
218
349
|
}
|
|
219
350
|
function isSessionIdParams(v) {
|
|
220
351
|
return isRecord(v) && typeof v.sessionId === "string";
|
|
221
352
|
}
|
|
222
353
|
function isSessionManagerCreateParams(v) {
|
|
223
|
-
return isRecord(v) &&
|
|
354
|
+
return isRecord(v) && isOptionalString2(v.cwd) && isOptionalString2(v.label) && isOptionalString2(v.prompt);
|
|
224
355
|
}
|
|
225
356
|
function isSessionManagerSendParams(v) {
|
|
226
357
|
return isRecord(v) && typeof v.sessionId === "string" && typeof v.prompt === "string";
|
|
@@ -366,15 +497,18 @@ export {
|
|
|
366
497
|
MAX_TERMINAL_REGISTRY_ENTRIES,
|
|
367
498
|
PENDING_REGISTER_ENTRY_TYPE,
|
|
368
499
|
PENDING_UNREGISTER_ENTRY_TYPE,
|
|
500
|
+
PLAN_REVIEW_MARKER,
|
|
369
501
|
PROTOCOL_VERSION,
|
|
502
|
+
SCHEDULE_CREATE_MARKER,
|
|
370
503
|
SESSION_MANAGER_ACTIONS,
|
|
371
504
|
SESSION_MANAGER_MARKER,
|
|
372
505
|
SUBAGENTS_ENGINES_FILENAME,
|
|
373
506
|
SUBAGENT_INFLIGHT_MARKER,
|
|
507
|
+
UI_FORM_MARKER,
|
|
374
508
|
applyPendingDiff,
|
|
375
|
-
askUserInteract,
|
|
376
509
|
callMarkerRpc,
|
|
377
510
|
collectActivePendingIds,
|
|
511
|
+
dateToOnceCron,
|
|
378
512
|
extractGui,
|
|
379
513
|
firstContentText,
|
|
380
514
|
formatChannelErrorText,
|
|
@@ -391,10 +525,14 @@ export {
|
|
|
391
525
|
isBridgeSyncPayload,
|
|
392
526
|
isBridgeToolExecuteResponse,
|
|
393
527
|
isChannelErrorResult,
|
|
528
|
+
isFormAnswers,
|
|
529
|
+
isFormQuestion,
|
|
394
530
|
isGuiCapable,
|
|
395
531
|
isGuiComponent,
|
|
396
532
|
isGuiRenderResult,
|
|
397
533
|
isInFlightReportAck,
|
|
534
|
+
isScheduleDraft,
|
|
535
|
+
isScheduleFormResult,
|
|
398
536
|
isSessionManagerAbortParams,
|
|
399
537
|
isSessionManagerCreateParams,
|
|
400
538
|
isSessionManagerHistoryParams,
|
|
@@ -405,7 +543,9 @@ export {
|
|
|
405
543
|
isSyncedTool,
|
|
406
544
|
isTerminalBackgroundTaskState,
|
|
407
545
|
mapReasonToStatus,
|
|
546
|
+
onceCronToDate,
|
|
408
547
|
scanPendingEntries,
|
|
409
548
|
setWidgetDual,
|
|
549
|
+
uiFormInteract,
|
|
410
550
|
validateWidgetIconPaths
|
|
411
551
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhushanwen/extension-protocol",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.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",
|