@zhushanwen/pi-goal 0.5.1 → 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 +3 -3
- package/src/__tests__/command-adapter.test.ts +1 -1
- package/src/__tests__/event-adapter.test.ts +5 -5
- package/src/__tests__/goal-control-rpc.test.ts +1 -1
- package/src/__tests__/index.test.ts +1 -1
- package/src/adapters/command-adapter.ts +3 -1
- package/src/adapters/event-handlers/agent-end.ts +1 -1
- package/src/adapters/event-handlers/before-agent-start.ts +14 -3
- package/src/adapters/event-handlers/message-end.ts +1 -1
- package/src/adapters/event-handlers/session-start.ts +1 -1
- package/src/adapters/event-handlers/turn-end.ts +1 -1
- package/src/adapters/goal-control-adapter.ts +2 -2
- package/src/adapters/ports.ts +5 -2
- package/src/index.ts +17 -2
- package/src/ports.ts +2 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhushanwen/pi-goal",
|
|
3
|
-
"version": "0.
|
|
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",
|
|
@@ -24,8 +24,8 @@
|
|
|
24
24
|
"peerDependencies": {
|
|
25
25
|
"@earendil-works/pi-ai": "*",
|
|
26
26
|
"@earendil-works/pi-tui": "*",
|
|
27
|
-
"@
|
|
28
|
-
"
|
|
27
|
+
"@earendil-works/pi-coding-agent": "*",
|
|
28
|
+
"typebox": "*"
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
31
|
"@xyz-agent/extension-protocol": "^0.2.0"
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* 用 fake pi + fake ctx(不 import Pi SDK 真实实现)。
|
|
10
10
|
* handleGoalCommand(pi, session, args, ctx) → Promise<void>。
|
|
11
11
|
*/
|
|
12
|
-
import type { ExtensionAPI, ExtensionContext } from "@
|
|
12
|
+
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
13
13
|
import { describe, expect, it } from "vitest";
|
|
14
14
|
|
|
15
15
|
import { handleGoalCommand } from "../adapters/command-adapter";
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
*
|
|
10
10
|
* 用 fake pi + fake ctx(不 import Pi SDK)。handler 签名 (pi, session, ctx) → void/result。
|
|
11
11
|
*/
|
|
12
|
-
import type { ExtensionAPI, ExtensionContext } from "@
|
|
12
|
+
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
13
13
|
import { describe, expect, it, vi } from "vitest";
|
|
14
14
|
|
|
15
15
|
import { handleAgentEnd } from "../adapters/event-handlers/agent-end";
|
|
@@ -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,
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* 不 mock handleCreate:走真实 createGoal 让 session.state 含完整字段(slug/objective/budget),
|
|
16
16
|
* 避免 hand-rolled state 与生产路径不一致。pi/ctx 用最小 fake(对齐 index.test.ts makeFactoryFixture)。
|
|
17
17
|
*/
|
|
18
|
-
import type { ExtensionAPI, ExtensionContext } from "@
|
|
18
|
+
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
19
19
|
import { describe, expect, it } from "vitest";
|
|
20
20
|
|
|
21
21
|
import { registerGoalControlTool, type GoalControlDetails } from "../adapters/goal-control-adapter";
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*
|
|
7
7
|
* 用最小 fake pi + fake ctx 实例化 goalExtension 工厂,再调 pi.__goalInit。
|
|
8
8
|
*/
|
|
9
|
-
import type { ExtensionAPI, ExtensionContext } from "@
|
|
9
|
+
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
10
10
|
import { describe, expect, it } from "vitest";
|
|
11
11
|
|
|
12
12
|
import goalExtension from "../index";
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* adapters 层可 import Pi 类型(桥接 Pi 和 service)。
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
import type { CustomEntry, ExtensionAPI, ExtensionContext } from "@
|
|
17
|
+
import type { CustomEntry, ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
18
18
|
|
|
19
19
|
import { parseGoalArgs } from "../commands";
|
|
20
20
|
import {
|
|
@@ -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;
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
* 检查之后——终态 goal 仍走终态 notify(不被 ESC 影响),非 active 状态直接返回。
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
|
-
import type { ExtensionAPI, ExtensionContext } from "@
|
|
21
|
+
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
22
22
|
|
|
23
23
|
import { checkBudgetOnTurnEnd } from "../../engine/budget";
|
|
24
24
|
import { isActiveStatus, isTerminalStatus } from "../../engine/goal";
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* contextInjectionPrompt 恒定建议 plan mode(AI 自行决定是否用)。
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
-
import type { ExtensionAPI, ExtensionContext } from "@
|
|
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
|
-
|
|
82
|
+
customEntries.filter((e) => e.customType === "pending:unregister").map((e) => (e.data as Record<string, unknown>)?.id),
|
|
72
83
|
);
|
|
73
|
-
const activePending =
|
|
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;
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* 不 persist / updateWidget。
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import type { ExtensionContext } from "@
|
|
10
|
+
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
11
11
|
|
|
12
12
|
import { applyEvent } from "../../service";
|
|
13
13
|
import type { GoalSession } from "../../session";
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* 调 reconstructGoalState 重建持久化状态 + updateWidget。
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import type { ExtensionAPI, ExtensionContext } from "@
|
|
7
|
+
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
8
8
|
|
|
9
9
|
import { updateWidget } from "../../projection/widget";
|
|
10
10
|
import type { GoalSession } from "../../session";
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* 不 persist(与旧 index.ts 行为对齐——turn_end 只内存变更 + widget)。
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import type { ExtensionAPI, ExtensionContext } from "@
|
|
13
|
+
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
14
14
|
|
|
15
15
|
import { updateWidget } from "../../projection/widget";
|
|
16
16
|
import { applyEvent } from "../../service";
|
|
@@ -25,8 +25,8 @@
|
|
|
25
25
|
*/
|
|
26
26
|
|
|
27
27
|
import { StringEnum } from "@earendil-works/pi-ai";
|
|
28
|
-
import type { ExtensionAPI, ExtensionContext, Theme } from "@
|
|
29
|
-
import { Text } from "@
|
|
28
|
+
import type { ExtensionAPI, ExtensionContext, Theme } from "@earendil-works/pi-coding-agent";
|
|
29
|
+
import { Text } from "@earendil-works/pi-tui";
|
|
30
30
|
import { type GuiComponent, guiComponent, type GuiRenderResult,guiResult } from "@xyz-agent/extension-protocol";
|
|
31
31
|
import { type Static, Type } from "typebox";
|
|
32
32
|
|
package/src/adapters/ports.ts
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* - session: ctx.sessionManager.getEntries + ctx.getContextUsage + ctx.signal
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import type { ExtensionAPI, ExtensionContext } from "@
|
|
12
|
+
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
13
13
|
|
|
14
14
|
import { ENTRY_TYPE, HISTORY_ENTRY_TYPE } from "../persistence";
|
|
15
15
|
import type { MessagingPort, PersistencePort, SessionPort, UiPort } from "../ports";
|
|
@@ -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
|
-
|
|
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
|
@@ -17,8 +17,8 @@
|
|
|
17
17
|
* FR-6.7:移除 pendingPause(ESC 改用 ctx.signal.aborted 守卫,在 event-adapter)。
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
|
-
import type { ExtensionAPI, ExtensionCommandContext, ExtensionContext, Theme } from "@
|
|
21
|
-
import { Text } from "@
|
|
20
|
+
import type { ExtensionAPI, ExtensionCommandContext, ExtensionContext, Theme } from "@earendil-works/pi-coding-agent";
|
|
21
|
+
import { Text } from "@earendil-works/pi-tui";
|
|
22
22
|
|
|
23
23
|
import { handleGoalCommand } from "./adapters/command-adapter";
|
|
24
24
|
import {
|
|
@@ -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
|
-
|
|
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
|
}
|