@zhushanwen/pi-subagent-workflow 8.8.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/package.json +8 -8
- package/src/host/pi-host.ts +5 -4
- package/src/index.ts +145 -464
- package/src/injectors/__tests__/engine-awareness.test.ts +4 -2
- package/src/injectors/__tests__/engine-section-stability.test.ts +42 -29
- package/src/injectors/engine-awareness.ts +80 -4
- package/src/injectors/resource-list-injector.ts +209 -0
- package/src/injectors/subagent-list-injector.ts +30 -143
- package/src/injectors/workflow-list-injector.ts +23 -150
- package/src/interface/__tests__/detectors.test.ts +1 -1
- package/src/interface/__tests__/subagent-tool-path-guard.test.ts +42 -26
- package/src/interface/__tests__/subagent-tool-prompt.test.ts +10 -2
- package/src/interface/__tests__/tool-workflow-throw-paths.test.ts +1 -1
- package/src/interface/bg-notify-render.ts +4 -4
- package/src/interface/commands.ts +115 -75
- package/src/interface/format.ts +339 -241
- package/src/interface/helpers.ts +2 -2
- package/src/interface/list-component.ts +163 -116
- package/src/interface/list-shared.ts +2 -2
- package/src/interface/list-view.ts +129 -72
- package/src/interface/subagent-actions.ts +5 -5
- package/src/interface/subagent-tool-schema.ts +6 -5
- package/src/interface/subagent-tool.ts +4 -3
- package/src/interface/subagents.ts +8 -7
- package/src/interface/tool-render.ts +2 -2
- package/src/interface/tool-workflow-script.ts +15 -14
- package/src/interface/tool-workflow.ts +14 -11
- package/src/interface/tui-kit.ts +305 -0
- package/src/interface/views/WorkflowsView.ts +267 -196
- 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 +11 -7
- package/src/interface/views/format.ts +0 -281
|
@@ -21,11 +21,12 @@
|
|
|
21
21
|
|
|
22
22
|
import type { ExtensionAPI, ExtensionCommandContext, Theme } from "@earendil-works/pi-coding-agent";
|
|
23
23
|
|
|
24
|
-
import type { LauncherDeps } from "@zhushanwen/subagent-core
|
|
25
|
-
import { abortRun } from "@zhushanwen/subagent-core
|
|
26
|
-
import type { WorkflowRun } from "@zhushanwen/subagent-core
|
|
27
|
-
import { parseWorkflowRpcCommand } from "./command-actions.ts";
|
|
24
|
+
import type { LauncherDeps } from "@zhushanwen/subagent-core";
|
|
25
|
+
import { abortRun } from "@zhushanwen/subagent-core";
|
|
26
|
+
import type { WorkflowRun } from "@zhushanwen/subagent-core";
|
|
27
|
+
import { parseWorkflowRpcCommand, type WorkflowRpcAction } from "./command-actions.ts";
|
|
28
28
|
import { createWorkflowsView, type ViewActions } from "./views/WorkflowsView.ts";
|
|
29
|
+
import { toErrorMessage } from "@zhushanwen/pi-ext-guards";
|
|
29
30
|
|
|
30
31
|
/** runId 截断长度(显示用)。 */
|
|
31
32
|
const RUNID_SHORT = 8;
|
|
@@ -97,40 +98,8 @@ export function registerWorkflowsCommand(
|
|
|
97
98
|
// ── RPC 模式(xyz-agent GUI):解析 lifecycle action 直接执行,不打开 TUI ──
|
|
98
99
|
// hasUI 在 TUI 和 RPC 都为 true,不能用于区分;用 ctx.mode === "rpc" 判定 GUI 通道。
|
|
99
100
|
if (ctx.mode === "rpc") {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
case "abort": {
|
|
103
|
-
try {
|
|
104
|
-
await abortRun(parsed.runId, deps);
|
|
105
|
-
// abort 单态后 pastTense 固定(原三态拼接随 pause/resume 删除)
|
|
106
|
-
ctx.ui.notify(`Workflow ${parsed.runId}: aborted`, "info");
|
|
107
|
-
} catch (err) {
|
|
108
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
109
|
-
ctx.ui.notify(`Failed to abort workflow ${parsed.runId}: ${msg}`, "warning");
|
|
110
|
-
}
|
|
111
|
-
return;
|
|
112
|
-
}
|
|
113
|
-
case "lifecycle-removed":
|
|
114
|
-
// 已移除的 lifecycle verb(pause/resume):run 一次性生命周期后不可挂起,
|
|
115
|
-
// 给定制指引而非 Usage(F3 定稿——提示语义优先于 missing-id)
|
|
116
|
-
ctx.ui.notify(
|
|
117
|
-
`Workflow ${parsed.verb} has been removed — runs are one-shot. To stop a run early: /workflows abort <runId>`,
|
|
118
|
-
"warning",
|
|
119
|
-
);
|
|
120
|
-
return;
|
|
121
|
-
case "lifecycle-missing-id":
|
|
122
|
-
ctx.ui.notify(`Usage: /workflows ${parsed.verb} <runId>`, "warning");
|
|
123
|
-
return;
|
|
124
|
-
case "noop":
|
|
125
|
-
// 无 action 或未知 action:GUI 端已屏蔽此 command 入口,此处兜底
|
|
126
|
-
ctx.ui.notify("View workflows in the sidebar Flows tab", "info");
|
|
127
|
-
return;
|
|
128
|
-
default: {
|
|
129
|
-
// exhaustiveness 断言:未来新增 action verb 忘加 case 时 tsc 报错
|
|
130
|
-
const _exhaustive: never = parsed;
|
|
131
|
-
throw new Error(`Unhandled workflow RPC action: ${String(_exhaustive)}`);
|
|
132
|
-
}
|
|
133
|
-
}
|
|
101
|
+
await handleRpcMode(parseWorkflowRpcCommand(args), ctx, deps);
|
|
102
|
+
return;
|
|
134
103
|
}
|
|
135
104
|
|
|
136
105
|
// ── print/json 模式(headless):不可交互 ──
|
|
@@ -139,54 +108,125 @@ export function registerWorkflowsCommand(
|
|
|
139
108
|
return;
|
|
140
109
|
}
|
|
141
110
|
|
|
142
|
-
|
|
111
|
+
// 直接按 runId / 前缀匹配打开
|
|
143
112
|
const directRunId = args.trim();
|
|
144
113
|
if (directRunId) {
|
|
145
|
-
|
|
146
|
-
// 精确匹配优先
|
|
147
|
-
const exact = all.find((r) => r.runId === directRunId);
|
|
148
|
-
if (exact) {
|
|
149
|
-
await openView(exact, ctx.ui.theme, ctx, deps);
|
|
150
|
-
return;
|
|
151
|
-
}
|
|
152
|
-
// 前缀匹配
|
|
153
|
-
const matched = all.filter((r) => r.runId.startsWith(directRunId));
|
|
154
|
-
if (matched.length === 1) {
|
|
155
|
-
await openView(matched[0], ctx.ui.theme, ctx, deps);
|
|
156
|
-
return;
|
|
157
|
-
}
|
|
158
|
-
ctx.ui.notify(`Workflow '${directRunId}' not found`, "error");
|
|
159
|
-
return;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
// 无参——列表选择
|
|
163
|
-
const all = sortedRuns(getRuns());
|
|
164
|
-
if (all.length === 0) {
|
|
165
|
-
ctx.ui.notify("No workflows in current session.", "info");
|
|
166
|
-
return;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
// 单 run 直开
|
|
170
|
-
if (all.length === 1) {
|
|
171
|
-
await openView(all[0], ctx.ui.theme, ctx, deps);
|
|
114
|
+
await openByRunId(directRunId, getRuns, ctx, deps);
|
|
172
115
|
return;
|
|
173
116
|
}
|
|
174
117
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
(r) => `${r.spec.scriptName} [${r.state.status}] (${r.runId.slice(0, RUNID_SHORT)})`,
|
|
178
|
-
);
|
|
179
|
-
const selected = await ctx.ui.select("Select workflow:", entries);
|
|
180
|
-
if (!selected) return;
|
|
181
|
-
const idx = entries.indexOf(selected);
|
|
182
|
-
if (idx === -1) return;
|
|
183
|
-
await openView(all[idx], ctx.ui.theme, ctx, deps);
|
|
118
|
+
// 无参——列表选择
|
|
119
|
+
await openFromList(getRuns, ctx, deps);
|
|
184
120
|
},
|
|
185
121
|
});
|
|
186
122
|
}
|
|
187
123
|
|
|
188
124
|
// ── Helpers ──────────────────────────────────────────────────
|
|
189
125
|
|
|
126
|
+
/**
|
|
127
|
+
* RPC 模式分支(xyz-agent GUI):解析后的 lifecycle action 直接执行,不打开 TUI。
|
|
128
|
+
*
|
|
129
|
+
* 各 action 语义:
|
|
130
|
+
* - abort → 调 lifecycle abortRun,成功/失败均 notify(不向上抛)
|
|
131
|
+
* - lifecycle-removed → 已移除的 verb(pause/resume),给定制指引而非 Usage
|
|
132
|
+
* (F3 定稿——提示语义优先于 missing-id:run 一次性生命周期后不可挂起)
|
|
133
|
+
* - lifecycle-missing-id → Usage 提示
|
|
134
|
+
* - noop → 无 action 或未知 action:GUI 端已屏蔽此 command 入口,此处兜底
|
|
135
|
+
*/
|
|
136
|
+
async function handleRpcMode(
|
|
137
|
+
parsed: WorkflowRpcAction,
|
|
138
|
+
ctx: ExtensionCommandContext,
|
|
139
|
+
deps: LauncherDeps,
|
|
140
|
+
): Promise<void> {
|
|
141
|
+
switch (parsed.action) {
|
|
142
|
+
case "abort": {
|
|
143
|
+
try {
|
|
144
|
+
await abortRun(parsed.runId, deps);
|
|
145
|
+
// abort 单态后 pastTense 固定(原三态拼接随 pause/resume 删除)
|
|
146
|
+
ctx.ui.notify(`Workflow ${parsed.runId}: aborted`, "info");
|
|
147
|
+
} catch (err) {
|
|
148
|
+
const msg = toErrorMessage(err);
|
|
149
|
+
ctx.ui.notify(`Failed to abort workflow ${parsed.runId}: ${msg}`, "warning");
|
|
150
|
+
}
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
case "lifecycle-removed":
|
|
154
|
+
ctx.ui.notify(
|
|
155
|
+
`Workflow ${parsed.verb} has been removed — runs are one-shot. To stop a run early: /workflows abort <runId>`,
|
|
156
|
+
"warning",
|
|
157
|
+
);
|
|
158
|
+
return;
|
|
159
|
+
case "lifecycle-missing-id":
|
|
160
|
+
ctx.ui.notify(`Usage: /workflows ${parsed.verb} <runId>`, "warning");
|
|
161
|
+
return;
|
|
162
|
+
case "noop":
|
|
163
|
+
ctx.ui.notify("View workflows in the sidebar Flows tab", "info");
|
|
164
|
+
return;
|
|
165
|
+
default: {
|
|
166
|
+
// exhaustiveness 断言:未来新增 action verb 忘加 case 时 tsc 报错
|
|
167
|
+
const _exhaustive: never = parsed;
|
|
168
|
+
throw new Error(`Unhandled workflow RPC action: ${String(_exhaustive)}`);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* TUI 模式:按 runId 直接打开对应 run 的 view(精确匹配优先,前缀唯一匹配次之)。
|
|
175
|
+
* 均未命中 → error notify。
|
|
176
|
+
*/
|
|
177
|
+
async function openByRunId(
|
|
178
|
+
directRunId: string,
|
|
179
|
+
getRuns: () => Map<string, WorkflowRun>,
|
|
180
|
+
ctx: ExtensionCommandContext,
|
|
181
|
+
deps: LauncherDeps,
|
|
182
|
+
): Promise<void> {
|
|
183
|
+
const all = sortedRuns(getRuns());
|
|
184
|
+
// 精确匹配优先
|
|
185
|
+
const exact = all.find((r) => r.runId === directRunId);
|
|
186
|
+
if (exact) {
|
|
187
|
+
await openView(exact, ctx.ui.theme, ctx, deps);
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
// 前缀匹配
|
|
191
|
+
const matched = all.filter((r) => r.runId.startsWith(directRunId));
|
|
192
|
+
if (matched.length === 1) {
|
|
193
|
+
await openView(matched[0], ctx.ui.theme, ctx, deps);
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
ctx.ui.notify(`Workflow '${directRunId}' not found`, "error");
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* TUI 模式无参分支:0 runs → notify;单 run 直开;多 run → select 选择后打开。
|
|
201
|
+
*/
|
|
202
|
+
async function openFromList(
|
|
203
|
+
getRuns: () => Map<string, WorkflowRun>,
|
|
204
|
+
ctx: ExtensionCommandContext,
|
|
205
|
+
deps: LauncherDeps,
|
|
206
|
+
): Promise<void> {
|
|
207
|
+
const all = sortedRuns(getRuns());
|
|
208
|
+
if (all.length === 0) {
|
|
209
|
+
ctx.ui.notify("No workflows in current session.", "info");
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// 单 run 直开
|
|
214
|
+
if (all.length === 1) {
|
|
215
|
+
await openView(all[0], ctx.ui.theme, ctx, deps);
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// 多 run——select 选择
|
|
220
|
+
const entries = all.map(
|
|
221
|
+
(r) => `${r.spec.scriptName} [${r.state.status}] (${r.runId.slice(0, RUNID_SHORT)})`,
|
|
222
|
+
);
|
|
223
|
+
const selected = await ctx.ui.select("Select workflow:", entries);
|
|
224
|
+
if (!selected) return;
|
|
225
|
+
const idx = entries.indexOf(selected);
|
|
226
|
+
if (idx === -1) return;
|
|
227
|
+
await openView(all[idx], ctx.ui.theme, ctx, deps);
|
|
228
|
+
}
|
|
229
|
+
|
|
190
230
|
/**
|
|
191
231
|
* 取 runs 按 status(running 优先)+ startedAt 倒序排序。
|
|
192
232
|
* 复用 main 的 UX 顺序(活跃态在前,新的在前)。
|