@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.
- package/README.md +5 -11
- package/package.json +8 -11
- package/skills/workflow-script-format/SKILL.md +32 -13
- package/src/host/__tests__/pi-host.test.ts +23 -2
- package/src/host/pi-host.ts +61 -6
- package/src/index.ts +155 -528
- package/src/injectors/__tests__/engine-awareness.test.ts +5 -3
- package/src/injectors/__tests__/engine-section-stability.test.ts +48 -33
- package/src/injectors/__tests__/model-list-injector.test.ts +18 -21
- package/src/injectors/__tests__/subagent-list-injector.test.ts +54 -14
- package/src/injectors/__tests__/workflow-list-injector.test.ts +26 -12
- package/src/injectors/engine-awareness.ts +80 -8
- package/src/injectors/model-list-injector.ts +15 -58
- package/src/injectors/resource-list-injector.ts +209 -0
- package/src/injectors/subagent-list-injector.ts +53 -224
- package/src/injectors/workflow-list-injector.ts +36 -196
- package/src/interface/__tests__/detectors.test.ts +46 -35
- package/src/interface/__tests__/subagent-tool-path-guard.test.ts +42 -26
- package/src/interface/__tests__/subagent-tool-prompt.test.ts +14 -3
- package/src/interface/__tests__/tool-workflow-run-builtin-name.test.ts +216 -0
- package/src/interface/__tests__/tool-workflow-script-generate.test.ts +17 -6
- package/src/interface/__tests__/tool-workflow-throw-paths.test.ts +38 -2
- package/src/interface/bg-notify-render.ts +7 -17
- package/src/interface/command-actions.ts +5 -15
- package/src/interface/commands.ts +116 -76
- package/src/interface/format.ts +354 -245
- package/src/interface/gui-mappers.ts +18 -22
- package/src/interface/helpers.ts +9 -130
- package/src/interface/list-component.ts +164 -117
- package/src/interface/list-shared.ts +3 -3
- package/src/interface/list-view.ts +130 -73
- package/src/interface/subagent-actions.ts +50 -675
- package/src/interface/subagent-tool-schema.ts +7 -9
- package/src/interface/subagent-tool.ts +5 -4
- package/src/interface/subagents.ts +9 -8
- package/src/interface/tool-render.ts +5 -15
- package/src/interface/tool-workflow-script.ts +34 -75
- package/src/interface/tool-workflow.ts +62 -96
- package/src/interface/tui-kit.ts +305 -0
- package/src/interface/views/WorkflowsView.ts +270 -207
- package/src/interface/views/__tests__/WorkflowsView-signature.test.ts +3 -3
- package/src/interface/views/__tests__/detail-content-session-file.test.ts +7 -4
- package/src/interface/views/detail-content.ts +16 -14
- package/src/interface/views/view-constants.ts +19 -0
- package/src/jsonl-run-store.ts +144 -228
- package/agents/analyst.md +0 -61
- package/agents/coder.md +0 -70
- package/agents/debugger.md +0 -67
- package/agents/doc-reviewer.md +0 -50
- package/agents/explorer.md +0 -64
- package/agents/general-purpose.md +0 -32
- package/agents/orchestrator.md +0 -63
- package/agents/planner.md +0 -54
- package/agents/researcher.md +0 -65
- package/agents/reviewer.md +0 -74
- package/src/interface/views/format.ts +0 -320
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// src/
|
|
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
|
|
10
|
-
import type { SubagentRecord } from "@zhushanwen/subagent-core
|
|
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/
|
|
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
|
|
39
|
-
import type { SubagentRecord } from "@zhushanwen/subagent-core
|
|
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、
|
|
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
|
-
*
|
|
225
|
+
* 按键处理入口。两阶段焦点(detailMode 控制),主函数只留阶段编排:
|
|
226
226
|
*
|
|
227
227
|
* ╔══════════════════════════════════════════════════════════════════╗
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
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
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
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
|
-
|
|
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
|
-
|
|
326
|
-
|
|
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
|
|