@zhushanwen/pi-canvas 0.4.2 → 0.4.4

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhushanwen/pi-canvas",
3
- "version": "0.4.2",
3
+ "version": "0.4.4",
4
4
  "description": "Problem-solving canvas toolface for pi: six deferred canvas_* tools bridging the agent to the taiji runtime CanvasService via the CANVAS_MARKER select channel (problem-solve-canvas)",
5
5
  "type": "module",
6
6
  "main": "index.ts",
@@ -55,10 +55,10 @@
55
55
  "vitest": "^4.1.8"
56
56
  },
57
57
  "dependencies": {
58
+ "@zhushanwen/extension-protocol": "0.24.0",
58
59
  "@zhushanwen/pi-ext-guards": "0.5.0",
59
- "@zhushanwen/extension-protocol": "0.23.0",
60
60
  "@zhushanwen/pi-extension-logger": "0.6.2",
61
- "@zhushanwen/subagent-core": "2.0.0"
61
+ "@zhushanwen/subagent-core": "2.1.0"
62
62
  },
63
63
  "scripts": {
64
64
  "typecheck": "npx tsc --noEmit",
@@ -64,5 +64,8 @@ describe("renderCanvasToolResult", () => {
64
64
  const sample = { content: [{ type: "text", text: "raw" }], details: { summary: 'Entered canvas "n"' } };
65
65
  expect(render(enter.renderResult, sample)).toContain('Entered canvas "n"');
66
66
  expect(render(list.renderResult, sample)).toContain('Entered canvas "n"');
67
- });
67
+ }, 30_000);
68
+ // ↑ timeout:harness 动态 import 重初始化(六工具注册链)裸跑已 ~4s,插桩(coverage)
69
+ // 口径超 vitest 默认 5s 预算——纯耗时贴边,非断言回归;DOWNSTREAM 联动门禁把本包
70
+ // 带进插桩跑后暴露(#52 先例:门禁拦的基线测试债分支内当场修复)。
68
71
  });
@@ -251,8 +251,9 @@ describe("runReviewRound(编排主体)", () => {
251
251
  });
252
252
  expect(outcome.round).toBe(1);
253
253
  expect(capturedTask).toContain("R1 全面审查");
254
- // 全 clean 轮:零条目不写回(append 不被调用)
255
- expect(append.calls).toHaveLength(0);
254
+ // 零发现轮也写回(空 entries 的 review-append):写回本身是「本轮 run 已完成」的
255
+ // 落盘事实(runtime lastRunRound),缺它轮次永不推进、maxRounds 预算不消耗
256
+ expect(append.calls).toEqual([{ canvasId: "c1", round: 1, entries: [] }]);
256
257
  });
257
258
 
258
259
  it("R2 run(runtime 已推进 round=2)→ 报告标 R2,scope 收窄 + 上轮处置表进 task", async () => {
@@ -281,7 +282,30 @@ describe("runReviewRound(编排主体)", () => {
281
282
  expect(capturedTask).toContain("R2 复审");
282
283
  expect(capturedTask).toContain("只审处置表");
283
284
  expect(capturedTask).toContain("[rv-1]");
284
- expect(append.calls).toHaveLength(0);
285
+ expect(append.calls).toEqual([{ canvasId: "c1", round: 2, entries: [] }]);
286
+ });
287
+
288
+ it("全 dropped(reviewer 条目全畸形被丢弃)→ 同样以空 entries 写回(零发现轮 = 完成轮,事实照常落盘)", async () => {
289
+ const append = makeAppend();
290
+ const dispatch: ReviewerDispatchPort = {
291
+ executeAndAwait: async () => ({
292
+ parsedOutput: {
293
+ entries: [
294
+ { id: "rv-x", dimension: "style", severity: "must-fix", targetId: "n1", note: "词表外维度" },
295
+ "not-an-object",
296
+ ],
297
+ },
298
+ }),
299
+ };
300
+ const outcome = await runReviewRound({
301
+ state: makeState(),
302
+ dispatch,
303
+ appendWriteBack: append.append,
304
+ });
305
+ expect(outcome.appended).toBe(0);
306
+ expect(outcome.dropped).toBe(2);
307
+ expect(outcome.summary).toContain("写回 0 条");
308
+ expect(append.calls).toEqual([{ canvasId: "c1", round: 1, entries: [] }]);
285
309
  });
286
310
 
287
311
  it("reviewer 执行失败(error 信封)→ 显式 throw(review_dispatch_failed + 恢复动作)", async () => {
package/src/render.ts CHANGED
@@ -5,19 +5,12 @@
5
5
  */
6
6
  import type { Theme } from "@earendil-works/pi-coding-agent";
7
7
  import { Text } from "@earendil-works/pi-tui";
8
- import { firstContentText } from "@zhushanwen/extension-protocol";
9
-
10
- /** pi toolResult 通用形状(AgentToolResult 的结构最小面,renderResult 入参收窄用)。 */
11
- interface ToolResultShape { // oe-exempt:20261017:framework:details 数据形状窄 view(单实现消费,非抽象接口)
12
- content: Array<{ type: string; text?: string }>;
13
- details?: { summary?: unknown };
14
- }
8
+ import { readDetailsSummary, resultContentText } from "@zhushanwen/extension-protocol";
15
9
 
16
10
  export function renderCanvasToolResult(result: unknown, options: { expanded: boolean }, theme: Theme): Text {
17
- const r = result as ToolResultShape;
18
- const summary = r.details?.summary;
19
- if (options.expanded === false && typeof summary === "string" && summary !== "") {
11
+ const summary = readDetailsSummary(result);
12
+ if (options.expanded === false && summary !== undefined) {
20
13
  return new Text(theme.fg("dim", summary), 0, 0);
21
14
  }
22
- return new Text(firstContentText(r), 0, 0);
15
+ return new Text(resultContentText(result), 0, 0);
23
16
  }
package/src/review.ts CHANGED
@@ -125,8 +125,9 @@ export function isCanvasStateReply(v: unknown): v is CanvasState {
125
125
  }
126
126
 
127
127
  /** 本轮审查轮次:run 链路的 read 发生在 review-request 之后,review.round 已是本轮
128
- * 轮次(runtime 侧一轮 = 一循环:当前轮无报告条目时再发起不推进轮次——GUI 双跳的
129
- * 第二跳与重发都不虚增轮次);从未审查(read 无 review 聚合)= R1。 */
128
+ * 轮次(runtime 侧一轮 = 一循环:当前轮无报告写回时再发起不推进轮次——GUI 双跳的
129
+ * 第二跳与重发都不虚增轮次;零发现轮的写回也算完成);从未审查(read 无 review
130
+ * 聚合)= R1。 */
130
131
  export function currentReviewRound(state: CanvasState): number {
131
132
  return state.review?.round ?? 1;
132
133
  }
@@ -218,9 +219,10 @@ export async function runReviewRound(input: {
218
219
  );
219
220
  }
220
221
  const { entries, dropped } = collectValidEntries(input.state.canvasId, round, result.parsedOutput ?? result.content);
221
- if (entries.length > 0) {
222
- await input.appendWriteBack({ canvasId: input.state.canvasId, round, entries });
223
- }
222
+ // 零发现轮也写回(空 entries 的 review-append):写回本身就是「本轮 run 已完成」的
223
+ // 落盘事实(runtime 侧落 review.lastRunRound),跳过会使零发现轮永不推进轮次、
224
+ // maxRounds 预算永不消耗(max-rounds 停回保护对该形态失灵)。
225
+ await input.appendWriteBack({ canvasId: input.state.canvasId, round, entries });
224
226
  const parsedSummary = recordOf(result.parsedOutput)?.summary;
225
227
  const summary =
226
228
  (typeof parsedSummary === "string" ? parsedSummary : "") ||