@zhushanwen/pi-subagent-workflow 8.7.0 → 8.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/README.md +5 -11
  2. package/package.json +8 -11
  3. package/skills/workflow-script-format/SKILL.md +32 -13
  4. package/src/host/__tests__/pi-host.test.ts +23 -2
  5. package/src/host/pi-host.ts +61 -6
  6. package/src/index.ts +155 -528
  7. package/src/injectors/__tests__/engine-awareness.test.ts +5 -3
  8. package/src/injectors/__tests__/engine-section-stability.test.ts +48 -33
  9. package/src/injectors/__tests__/model-list-injector.test.ts +18 -21
  10. package/src/injectors/__tests__/subagent-list-injector.test.ts +54 -14
  11. package/src/injectors/__tests__/workflow-list-injector.test.ts +26 -12
  12. package/src/injectors/engine-awareness.ts +80 -8
  13. package/src/injectors/model-list-injector.ts +15 -58
  14. package/src/injectors/resource-list-injector.ts +209 -0
  15. package/src/injectors/subagent-list-injector.ts +53 -224
  16. package/src/injectors/workflow-list-injector.ts +36 -196
  17. package/src/interface/__tests__/detectors.test.ts +46 -35
  18. package/src/interface/__tests__/subagent-tool-path-guard.test.ts +42 -26
  19. package/src/interface/__tests__/subagent-tool-prompt.test.ts +14 -3
  20. package/src/interface/__tests__/tool-workflow-run-builtin-name.test.ts +216 -0
  21. package/src/interface/__tests__/tool-workflow-script-generate.test.ts +17 -6
  22. package/src/interface/__tests__/tool-workflow-throw-paths.test.ts +38 -2
  23. package/src/interface/bg-notify-render.ts +7 -17
  24. package/src/interface/command-actions.ts +5 -15
  25. package/src/interface/commands.ts +116 -76
  26. package/src/interface/format.ts +354 -245
  27. package/src/interface/gui-mappers.ts +18 -22
  28. package/src/interface/helpers.ts +9 -130
  29. package/src/interface/list-component.ts +164 -117
  30. package/src/interface/list-shared.ts +3 -3
  31. package/src/interface/list-view.ts +130 -73
  32. package/src/interface/subagent-actions.ts +50 -675
  33. package/src/interface/subagent-tool-schema.ts +7 -9
  34. package/src/interface/subagent-tool.ts +5 -4
  35. package/src/interface/subagents.ts +9 -8
  36. package/src/interface/tool-render.ts +5 -15
  37. package/src/interface/tool-workflow-script.ts +34 -75
  38. package/src/interface/tool-workflow.ts +62 -96
  39. package/src/interface/tui-kit.ts +305 -0
  40. package/src/interface/views/WorkflowsView.ts +270 -207
  41. package/src/interface/views/__tests__/WorkflowsView-signature.test.ts +3 -3
  42. package/src/interface/views/__tests__/detail-content-session-file.test.ts +7 -4
  43. package/src/interface/views/detail-content.ts +16 -14
  44. package/src/interface/views/view-constants.ts +19 -0
  45. package/src/jsonl-run-store.ts +144 -228
  46. package/agents/analyst.md +0 -61
  47. package/agents/coder.md +0 -70
  48. package/agents/debugger.md +0 -67
  49. package/agents/doc-reviewer.md +0 -50
  50. package/agents/explorer.md +0 -64
  51. package/agents/general-purpose.md +0 -32
  52. package/agents/orchestrator.md +0 -63
  53. package/agents/planner.md +0 -54
  54. package/agents/researcher.md +0 -65
  55. package/agents/reviewer.md +0 -74
  56. package/src/interface/views/format.ts +0 -320
@@ -1,4 +1,4 @@
1
- // src/tui/list-shared.ts
1
+ // src/interface/list-shared.ts
2
2
  //
3
3
  // list-view 与 list-component 共享的类型/常量/纯函数,避免两者循环依赖。
4
4
  // 原循环:list-view import 组件类 SubagentsListComponent;组件 import key 处理 + 状态类型。
@@ -6,8 +6,8 @@
6
6
  // 按键处理 processKey 仍留 list-view,由 list-view factory 注入给组件(KeyHandler 类型在此),
7
7
  // 故 list-component 无需 import list-view,循环消除。
8
8
 
9
- import type { SubagentService } from "@zhushanwen/subagent-core/execution/subagent-service.ts";
10
- import type { SubagentRecord } from "@zhushanwen/subagent-core/execution/types.ts";
9
+ import type { SubagentService } from "@zhushanwen/subagent-core";
10
+ import type { SubagentRecord } from "@zhushanwen/subagent-core";
11
11
 
12
12
  // ============================================================
13
13
  // 常量
@@ -1,4 +1,4 @@
1
- // src/tui/list-view.ts
1
+ // src/interface/list-view.ts
2
2
  //
3
3
  // /subagents list 全屏带框左右分屏 overlay。
4
4
  // 左列:record 列表(状态图标 + agent + mode + 绝对时长)
@@ -35,8 +35,8 @@
35
35
  import { matchesKey } from "@earendil-works/pi-tui";
36
36
  import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
37
37
 
38
- import type { SubagentService } from "@zhushanwen/subagent-core/execution/subagent-service.ts";
39
- import type { SubagentRecord } from "@zhushanwen/subagent-core/execution/types.ts";
38
+ import type { SubagentService } from "@zhushanwen/subagent-core";
39
+ import type { SubagentRecord } from "@zhushanwen/subagent-core";
40
40
  import { type ThemeLike } from "./format.ts";
41
41
  import { SubagentsListComponent } from "./list-component.ts";
42
42
  import {
@@ -48,21 +48,21 @@ import {
48
48
  type TuiLike,
49
49
  type ViewState,
50
50
  } from "./list-shared.ts";
51
+ import { PAGE_SCROLL_DEFAULT } from "./tui-kit.ts";
51
52
 
52
53
  // ============================================================
53
54
  // 常量
54
55
  // ============================================================
55
56
 
56
57
  // 布局/边框常量(LEFT_COL_RATIO、COL_*、BORDER_WIDTH、PAD_*、MIN_*、TITLE_*、
57
- // SPLIT_FIXED_LINES、TERM_ROWS_FALLBACK、DETAIL_LEN_PROBE_WIDTH、VERT_CENTER_DIVISOR、
58
- // SPINNER_FRAME_MS、PREVIEW_RECENT_LINES)已随 SubagentsListComponent 移至 list-component.ts。
58
+ // SPLIT_FIXED_LINES、DETAIL_LEN_PROBE_WIDTH、VERT_CENTER_DIVISOR、
59
+ // SPINNER_FRAME_MS、PREVIEW_RECENT_LINES)已随 SubagentsListComponent 移至 list-component.ts;
60
+ // 终端兜底零件(终端行数兜底/termRows/边框家族)单定义在 ./tui-kit.ts(C4)。
59
61
  // 共享类型/常量/纯函数(LIST_LIMIT/ViewState/DetailKeyContext/TuiLike/NotifyFn/applyFilter/
60
62
  // KeyResult/KeyHandler)已移至 list-shared.ts(消除 list-view ↔ list-component 循环依赖)。
61
63
 
62
64
  /** 详情区 eventLog 翻屏步长(方向键单步)。 */
63
65
  const DETAIL_SCROLL_STEP = 1;
64
- /** 详情区 PgUp/PgDn 默认步长(无 viewport 信息时)。 */
65
- const PAGE_SCROLL_DEFAULT = 10;
66
66
 
67
67
  /** overlay 动画刷新间隔(spinner 换帧 + elapsed 跳动)。同 tool-render.ts SPINNER_INTERVAL_MS。 */
68
68
  const OVERLAY_REFRESH_MS = 250;
@@ -88,7 +88,7 @@ let activeView: { close: () => void } | null = null;
88
88
  * ╔══════════════════════════════════════════════════════════════════╗
89
89
  * ║ 1. G-017 防叠加:activeView?.close() ║
90
90
  * ║ 2. ctx.ui.custom((tui, theme, kb, done) => { ║
91
- * ║ unsubscribe = service.onChange(() => tui.requestRender()) ║
91
+ * ║ unsubscribe = service.queries.onChange(() => tui.requestRender()) ║
92
92
  * ║ activeView = { close: wrappedDone } ║
93
93
  * ║ return new SubagentsListComponent(...) ║
94
94
  * ║ }, { overlay:true, overlayOptions:{margin:0, width:"100%"}}) ║
@@ -112,7 +112,7 @@ export async function createSubagentsView(
112
112
 
113
113
  // directId 提示
114
114
  if (directId) {
115
- const all = service.collectRecords(LIST_LIMIT);
115
+ const all = service.queries.collectRecords(LIST_LIMIT);
116
116
  if (!all.some((r) => r.id === directId)) {
117
117
  notify(`No record found for id "${directId}", showing all`, "warning");
118
118
  }
@@ -143,7 +143,7 @@ export async function createSubagentsView(
143
143
  // 易产生 cell 残留(视觉重影)。节流到 ONCHANGE_DEBOUNCE_MS,animTimer(250ms)
144
144
  // 兜底刷新。终态变化(record 完成)延迟可接受。
145
145
  let renderDebounce: ReturnType<typeof setTimeout> | undefined;
146
- const unsubscribe = service.onChange(() => {
146
+ const unsubscribe = service.queries.onChange(() => {
147
147
  if (state.disposed) return;
148
148
  if (renderDebounce) clearTimeout(renderDebounce);
149
149
  renderDebounce = setTimeout(() => {
@@ -156,7 +156,7 @@ export async function createSubagentsView(
156
156
 
157
157
  // directId 命中 → 进详情模式(右侧就地展开,底部对齐)
158
158
  if (directId) {
159
- const records = service.collectRecords(LIST_LIMIT);
159
+ const records = service.queries.collectRecords(LIST_LIMIT);
160
160
  const idx = records.findIndex((r) => r.id === directId);
161
161
  if (idx >= 0) {
162
162
  state.selectedIdx = idx;
@@ -218,27 +218,28 @@ export async function createSubagentsView(
218
218
  }
219
219
 
220
220
  // ============================================================
221
- // 按键处理(纯函数,可单测)
221
+ // 按键处理(纯函数,可单测)——阶段化提取(形态 1)
222
222
  // ============================================================
223
223
 
224
224
  /**
225
- * 按键处理。两阶段焦点(detailMode 控制):
225
+ * 按键处理入口。两阶段焦点(detailMode 控制),主函数只留阶段编排:
226
226
  *
227
227
  * ╔══════════════════════════════════════════════════════════════════╗
228
- // ║ 阶段 1(list 焦点,detailMode=false): ║
229
- // ║ Esc 退出(有 filter 先清)/ ↑↓ 导航左列 / Enter 进阶段 2 ║
230
- // ║ Backspace 删 filter / 可打印字符直接 filter ║
231
- // ║ ║
232
- // ║ 阶段 2(detail 焦点,detailMode=true):左侧锚定,滚右侧详情 ║
233
- // ║ Esc 返回阶段 1 / ↑↓ PgUp/PgDn Home End 滚右侧 eventLog ║
234
- // ║ x 停止:background → service.cancel(id)(真正 abort) ║
235
- // ╚══════════════════════════════════════════════════════════════════╝
228
+ * ║ 阶段 1(list 焦点,detailMode=false): ║
229
+ * ║ Esc 退出(有 filter 先清)/ ↑↓ 导航左列 / Enter 进阶段 2 ║
230
+ * ║ Backspace 删 filter / 可打印字符直接 filter ║
231
+ * ║ ║
232
+ * ║ 阶段 2(detail 焦点,detailMode=true):左侧锚定,滚右侧详情 ║
233
+ * ║ Esc 返回阶段 1 / ↑↓ PgUp/PgDn Home End 滚右侧 eventLog ║
234
+ * ║ x 停止:background → service.cancel(id)(真正 abort) ║
235
+ * ╚══════════════════════════════════════════════════════════════════╝
236
236
  *
237
237
  * 返回 KeyResult:changed 表示状态变更需重绘;exit 表示调用方应关闭 overlay。
238
238
  * 二者正交——Esc 在阶段 1 无 filter 时 changed=false + exit=true。
239
239
  *
240
240
  * 注:KeyResult/KeyHandler 类型定义在 list-shared.ts(list-component 也用 KeyHandler 做构造
241
241
  * 参数类型,避免组件 import list-view)。本函数由 list-view factory 注入组件(依赖注入)。
242
+ * 各阶段子函数按原 if 链顺序匹配,命中短路返回(与原单函数版时序逐一对齐)。
242
243
  */
243
244
  export const processKey: KeyHandler = (
244
245
  data,
@@ -249,50 +250,118 @@ export const processKey: KeyHandler = (
249
250
  detailCtx,
250
251
  notify,
251
252
  ): KeyResult => {
252
- // ── 阶段 2(detail 焦点,detailMode=true):左侧锚定,滚右侧详情 ──
253
253
  if (state.detailMode) {
254
- if (matchesKey(data, "escape")) {
255
- state.detailMode = false;
256
- state.scrollOffset = 0;
257
- return { changed: true, exit: false };
258
- }
259
- if (matchesKey(data, "up")) {
260
- state.scrollOffset = Math.max(0, state.scrollOffset - DETAIL_SCROLL_STEP);
261
- return { changed: true, exit: false };
262
- }
263
- if (matchesKey(data, "down")) {
264
- const max = detailScrollMax(detailCtx);
265
- state.scrollOffset = Math.min(max, state.scrollOffset + DETAIL_SCROLL_STEP);
266
- return { changed: true, exit: false };
267
- }
268
- if (matchesKey(data, "pageUp")) {
269
- const step = detailCtx?.viewportHeight ?? PAGE_SCROLL_DEFAULT;
270
- state.scrollOffset = Math.max(0, state.scrollOffset - step);
271
- return { changed: true, exit: false };
272
- }
273
- if (matchesKey(data, "pageDown")) {
274
- const step = detailCtx?.viewportHeight ?? PAGE_SCROLL_DEFAULT;
275
- const max = detailScrollMax(detailCtx);
276
- state.scrollOffset = Math.min(max, state.scrollOffset + step);
277
- return { changed: true, exit: false };
278
- }
279
- if (matchesKey(data, "home")) {
280
- state.scrollOffset = 0;
281
- return { changed: true, exit: false };
282
- }
283
- if (matchesKey(data, "end")) {
284
- state.scrollOffset = detailScrollMax(detailCtx);
254
+ return processDetailModeKey(data, state, selected, service, detailCtx, notify);
255
+ }
256
+ return processListModeKey(data, records, state, selected);
257
+ };
258
+
259
+ // ============================================================
260
+ // 阶段 2(detail 焦点,detailMode=true):左侧锚定,滚右侧详情
261
+ // ============================================================
262
+
263
+ /** 阶段 2 滚动/返回键(Esc/↑/↓/PgUp/PgDn/Home/End,匹配顺序同原 if 链)。未命中返回 undefined。 */
264
+ function detailScrollKey(
265
+ data: string,
266
+ state: ViewState,
267
+ detailCtx: DetailKeyContext | undefined,
268
+ ): KeyResult | undefined {
269
+ if (matchesKey(data, "escape")) {
270
+ state.detailMode = false;
271
+ state.scrollOffset = 0;
272
+ return { changed: true, exit: false };
273
+ }
274
+ if (matchesKey(data, "up")) {
275
+ state.scrollOffset = Math.max(0, state.scrollOffset - DETAIL_SCROLL_STEP);
276
+ return { changed: true, exit: false };
277
+ }
278
+ if (matchesKey(data, "down")) {
279
+ const max = detailScrollMax(detailCtx);
280
+ state.scrollOffset = Math.min(max, state.scrollOffset + DETAIL_SCROLL_STEP);
281
+ return { changed: true, exit: false };
282
+ }
283
+ if (matchesKey(data, "pageUp")) {
284
+ const step = detailCtx?.viewportHeight ?? PAGE_SCROLL_DEFAULT;
285
+ state.scrollOffset = Math.max(0, state.scrollOffset - step);
286
+ return { changed: true, exit: false };
287
+ }
288
+ if (matchesKey(data, "pageDown")) {
289
+ const step = detailCtx?.viewportHeight ?? PAGE_SCROLL_DEFAULT;
290
+ const max = detailScrollMax(detailCtx);
291
+ state.scrollOffset = Math.min(max, state.scrollOffset + step);
292
+ return { changed: true, exit: false };
293
+ }
294
+ if (matchesKey(data, "home")) {
295
+ state.scrollOffset = 0;
296
+ return { changed: true, exit: false };
297
+ }
298
+ if (matchesKey(data, "end")) {
299
+ state.scrollOffset = detailScrollMax(detailCtx);
300
+ return { changed: true, exit: false };
301
+ }
302
+ return undefined;
303
+ }
304
+
305
+ /** 阶段 2 停止键(x):background → service.cancel(id) 真正 abort。未命中返回 undefined。 */
306
+ function detailStopKey(
307
+ data: string,
308
+ selected: SubagentRecord | null,
309
+ service: SubagentService | null,
310
+ notify: NotifyFn | undefined,
311
+ ): KeyResult | undefined {
312
+ if (data === "x" && selected) {
313
+ const changed = handleCancel(selected, service, notify);
314
+ return { changed, exit: false };
315
+ }
316
+ return undefined;
317
+ }
318
+
319
+ /** 阶段 2 编排:滚动/返回 → 停止 → 落空(与原阶段 2 if 链 + 兜底 return 逐一对齐)。 */
320
+ function processDetailModeKey(
321
+ data: string,
322
+ state: ViewState,
323
+ selected: SubagentRecord | null,
324
+ service: SubagentService | null,
325
+ detailCtx: DetailKeyContext | undefined,
326
+ notify: NotifyFn | undefined,
327
+ ): KeyResult {
328
+ const scroll = detailScrollKey(data, state, detailCtx);
329
+ if (scroll) return scroll;
330
+ const stop = detailStopKey(data, selected, service, notify);
331
+ if (stop) return stop;
332
+ return { changed: false, exit: false };
333
+ }
334
+
335
+ // ============================================================
336
+ // 阶段 1(list 焦点,detailMode=false):↑↓ 导航左列 + filter 编辑
337
+ // ============================================================
338
+
339
+ /** 阶段 1 filter 编辑键(backspace 删末字符 / 可打印 ASCII 字符追加)。未命中返回 undefined。 */
340
+ function listFilterKey(data: string, state: ViewState): KeyResult | undefined {
341
+ if (matchesKey(data, "backspace")) {
342
+ if (state.filterText.length > 0) {
343
+ state.filterText = state.filterText.slice(0, -1);
344
+ state.selectedIdx = 0;
285
345
  return { changed: true, exit: false };
286
346
  }
287
- // x:停止当前 record
288
- if (data === "x" && selected) {
289
- const changed = handleCancel(selected, service, notify);
290
- return { changed, exit: false };
291
- }
292
347
  return { changed: false, exit: false };
293
348
  }
349
+ // 可打印字符 → filter(单字符 ASCII 可见区)
350
+ if (data.length === 1 && data >= " " && data <= "~") {
351
+ state.filterText += data;
352
+ state.selectedIdx = 0;
353
+ return { changed: true, exit: false };
354
+ }
355
+ return undefined;
356
+ }
294
357
 
295
- // ── 阶段 1(list 焦点,detailMode=false):↑↓ 导航左列 ──
358
+ /** 阶段 1 编排:Esc 清 filter/退出 → 导航 → 进详情 → filter 编辑 → 落空(匹配顺序同原 if 链)。 */
359
+ function processListModeKey(
360
+ data: string,
361
+ records: SubagentRecord[],
362
+ state: ViewState,
363
+ selected: SubagentRecord | null,
364
+ ): KeyResult {
296
365
  if (matchesKey(data, "escape")) {
297
366
  // 有 filter 先清(changed);无 filter → 退出 overlay(exit)
298
367
  if (state.filterText.length > 0) {
@@ -322,20 +391,8 @@ export const processKey: KeyHandler = (
322
391
  }
323
392
  return { changed: false, exit: false };
324
393
  }
325
- if (matchesKey(data, "backspace")) {
326
- if (state.filterText.length > 0) {
327
- state.filterText = state.filterText.slice(0, -1);
328
- state.selectedIdx = 0;
329
- return { changed: true, exit: false };
330
- }
331
- return { changed: false, exit: false };
332
- }
333
- // 可打印字符 → filter(单字符 ASCII 可见区)
334
- if (data.length === 1 && data >= " " && data <= "~") {
335
- state.filterText += data;
336
- state.selectedIdx = 0;
337
- return { changed: true, exit: false };
338
- }
394
+ const filter = listFilterKey(data, state);
395
+ if (filter) return filter;
339
396
  return { changed: false, exit: false };
340
397
  }
341
398