@zhushanwen/pi-goal 0.5.2 → 0.6.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhushanwen/pi-goal",
3
- "version": "0.5.2",
3
+ "version": "0.6.0",
4
4
  "description": "Codex-style /goal command for Pi — persistent goal-driven autonomous loop with evidence-based completion, token/time budgets, blocked detection, and steering templates.",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -25,7 +25,7 @@
25
25
  "@earendil-works/pi-ai": "*",
26
26
  "@earendil-works/pi-tui": "*",
27
27
  "@earendil-works/pi-coding-agent": "*",
28
- "@sinclair/typebox": "*"
28
+ "typebox": "*"
29
29
  },
30
30
  "dependencies": {
31
31
  "@xyz-agent/extension-protocol": "^0.2.0"
@@ -343,8 +343,8 @@ describe("handleBeforeAgentStart", () => {
343
343
  const { pi } = makeFakePi();
344
344
  // 创建带 pending:register entries 的 ctx
345
345
  const entries = [
346
- { customType: "pending:register", data: { id: "wf-1", type: "workflow", name: "test-wf" } },
347
- { customType: "pending:register", data: { id: "bg-1", type: "subagent", name: "worker" } },
346
+ { type: "custom", customType: "pending:register", data: { id: "wf-1", type: "workflow", name: "test-wf" } },
347
+ { type: "custom", customType: "pending:register", data: { id: "bg-1", type: "subagent", name: "worker" } },
348
348
  ];
349
349
  const ctx = {
350
350
  hasUI: true,
@@ -371,8 +371,8 @@ describe("handleBeforeAgentStart", () => {
371
371
  it("有 pending register 但已 unregister → 不注入提示", async () => {
372
372
  const { pi } = makeFakePi();
373
373
  const entries = [
374
- { customType: "pending:register", data: { id: "wf-1", type: "workflow", name: "test-wf" } },
375
- { customType: "pending:unregister", data: { id: "wf-1" } },
374
+ { type: "custom", customType: "pending:register", data: { id: "wf-1", type: "workflow", name: "test-wf" } },
375
+ { type: "custom", customType: "pending:unregister", data: { id: "wf-1" } },
376
376
  ];
377
377
  const ctx = {
378
378
  hasUI: true,
@@ -179,6 +179,8 @@ function handleResume(pi: ExtensionAPI, session: GoalSession, ctx: ExtensionCont
179
179
  interface GoalHistoryData {
180
180
  goalId: string;
181
181
  objective: string;
182
+ /** widget/history 标题用(fallback objective 截断)。旧 entry 无此字段。 */
183
+ slug?: string;
182
184
  status: string;
183
185
  completedTasks: number;
184
186
  totalTasks: number;
@@ -15,7 +15,7 @@
15
15
  * contextInjectionPrompt 恒定建议 plan mode(AI 自行决定是否用)。
16
16
  */
17
17
 
18
- import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
18
+ import type { CustomEntry, ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
19
19
 
20
20
  import { AUTO_CLEAR_TURNS, CONTEXT_USAGE_RATIO_LIMIT } from "../../constants";
21
21
  import { isActiveStatus, isTerminalStatus } from "../../engine/goal";
@@ -33,6 +33,14 @@ interface BeforeAgentStartResult {
33
33
  };
34
34
  }
35
35
 
36
+ /** 类型守卫:把 SDK 的 SessionEntry 联合收窄到 CustomEntry。
37
+ * SessionEntry 现为联合类型(SessionMessageEntry | CompactionEntry | CustomEntry | ...),
38
+ * 只有 CustomEntry 带 customType/data(pending:register/unregister 由 pi.appendEntry 落地,
39
+ * type 恒为 "custom")。读取前必须先收窄。 */
40
+ function isCustomEntry(e: { type: string }): e is CustomEntry {
41
+ return e.type === "custom";
42
+ }
43
+
36
44
  export async function handleBeforeAgentStart(
37
45
  pi: ExtensionAPI,
38
46
  session: GoalSession,
@@ -67,10 +75,13 @@ export async function handleBeforeAgentStart(
67
75
  // 但同一 session 内超时未 unregister 的 entry(如崩溃恢复遗漏)需此处完补以防误报 active。
68
76
  const now = Date.now();
69
77
  const entries = ctx.sessionManager.getEntries();
78
+ // pending:register/unregister 由 pi.appendEntry 落地为 CustomEntry(type:"custom")。
79
+ // 先用 isCustomEntry 收窄联合类型,再读 customType/data。
80
+ const customEntries = entries.filter(isCustomEntry);
70
81
  const pendingUnregisters = new Set(
71
- entries.filter((e) => e.customType === "pending:unregister").map((e) => (e.data as Record<string, unknown>)?.id),
82
+ customEntries.filter((e) => e.customType === "pending:unregister").map((e) => (e.data as Record<string, unknown>)?.id),
72
83
  );
73
- const activePending = entries.filter((e) => {
84
+ const activePending = customEntries.filter((e) => {
74
85
  if (e.customType !== "pending:register") return false;
75
86
  const id = (e.data as Record<string, unknown>)?.id;
76
87
  if (pendingUnregisters.has(id)) return false;
@@ -37,7 +37,10 @@ export function buildPorts(pi: ExtensionAPI, ctx: ExtensionContext): ServicePort
37
37
 
38
38
  const uiPort = {
39
39
  setWidget(name: string, content: string[] | string | undefined): void {
40
- ctx.ui.setWidget(name, content);
40
+ // SDK setWidget 重载只接受 string[] | undefined(或 factory)。本地 UiPort 允许单 string,
41
+ // 此处归一化:string 包一层成 [string],再透传。
42
+ const lines = typeof content === "string" ? [content] : content;
43
+ ctx.ui.setWidget(name, lines);
41
44
  },
42
45
  setStatus(name: string, text: string | undefined): void {
43
46
  ctx.ui.setStatus(name, text);
package/src/index.ts CHANGED
@@ -87,6 +87,21 @@ export default function goalExtension(pi: ExtensionAPI) {
87
87
  handler: async (args: string | undefined, ctx: ExtensionCommandContext) => {
88
88
  await handleGoalCommand(pi, session, args, ctx);
89
89
  },
90
+ getArgumentCompletions(prefix: string) {
91
+ // 只补全一级静态子命令;update 后跟自由文本(目标描述),其他自由文本作为新目标,均不补全
92
+ const parts = prefix.trimStart().split(/\s+/).filter(Boolean);
93
+ if (parts.length > 1) return null;
94
+ const trimmed = (parts[0] ?? "").toLowerCase();
95
+ const opts = [
96
+ { label: "status", value: "status", description: "查看当前 goal 状态" },
97
+ { label: "resume", value: "resume", description: "恢复暂停/阻塞的 goal" },
98
+ { label: "pause", value: "pause", description: "暂停活跃的 goal" },
99
+ { label: "clear", value: "clear", description: "强制清除当前 goal" },
100
+ { label: "history", value: "history", description: "查看历史 goal" },
101
+ { label: "update", value: "update ", description: "重设目标(reshape)" },
102
+ ];
103
+ return trimmed === "" ? opts : opts.filter((o) => o.label.startsWith(trimmed));
104
+ },
90
105
  });
91
106
 
92
107
  // ── Tool: goal_control(complete / report_blocked,#3 替代已删 goal_manager)──
package/src/ports.ts CHANGED
@@ -63,6 +63,7 @@ export interface SessionEntryLike {
63
63
 
64
64
  export interface SessionPort {
65
65
  getEntries(): SessionEntryLike[];
66
- getContextUsage(): { tokens?: number; contextWindow?: number } | null;
66
+ // tokens 与 Pi SDK 的 ContextUsage.tokens 对齐:number | null(null = 未知,如压缩后未到下次 LLM 响应)。
67
+ getContextUsage(): { tokens: number | null; contextWindow: number } | null;
67
68
  readonly signal: AbortSignal | undefined;
68
69
  }