@zhushanwen/pi-goal 0.8.0 → 0.8.2
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.8.
|
|
3
|
+
"version": "0.8.2",
|
|
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",
|
|
@@ -106,6 +106,14 @@ describe("handleComplete — active 守卫 + evidence + finalizeAndPersist", ()
|
|
|
106
106
|
handleComplete({ action: "complete", evidence: " " }, session, makeFakePorts()),
|
|
107
107
|
).toThrow(/evidence/);
|
|
108
108
|
});
|
|
109
|
+
|
|
110
|
+
it("evidence undefined → throw(schema 降级后 handler 唯一兜底:'required' 错误)", () => {
|
|
111
|
+
const session = createGoalSession();
|
|
112
|
+
session.state = activeState();
|
|
113
|
+
expect(() => handleComplete({ action: "complete" }, session, makeFakePorts())).toThrow(
|
|
114
|
+
/evidence.*required/,
|
|
115
|
+
);
|
|
116
|
+
});
|
|
109
117
|
});
|
|
110
118
|
|
|
111
119
|
// ── handleCreate ─────────────────────────────────────
|
|
@@ -142,6 +150,13 @@ describe("handleCreate — slug+objective 必填 + 非终态守卫 + createGoal"
|
|
|
142
150
|
).toThrow(/objective/);
|
|
143
151
|
});
|
|
144
152
|
|
|
153
|
+
it("objective undefined → throw(schema 降级后 handler 唯一兜底:'required' 错误)", () => {
|
|
154
|
+
const session = createGoalSession();
|
|
155
|
+
expect(() => handleCreate({ action: "create", successCriteria: "y" }, session, makeFakePorts())).toThrow(
|
|
156
|
+
/objective.*required/,
|
|
157
|
+
);
|
|
158
|
+
});
|
|
159
|
+
|
|
145
160
|
it("successCriteria 空 → throw(即使有 slug + objective)", () => {
|
|
146
161
|
const session = createGoalSession();
|
|
147
162
|
expect(() =>
|
|
@@ -153,6 +168,13 @@ describe("handleCreate — slug+objective 必填 + 非终态守卫 + createGoal"
|
|
|
153
168
|
).toThrow(/successCriteria/);
|
|
154
169
|
});
|
|
155
170
|
|
|
171
|
+
it("successCriteria undefined → throw(schema 降级后 handler 唯一兜底:'required' 错误)", () => {
|
|
172
|
+
const session = createGoalSession();
|
|
173
|
+
expect(() => handleCreate({ action: "create", objective: "x" }, session, makeFakePorts())).toThrow(
|
|
174
|
+
/successCriteria.*required/,
|
|
175
|
+
);
|
|
176
|
+
});
|
|
177
|
+
|
|
156
178
|
const NON_TERMINAL_STATUSES: GoalRuntimeState["status"][] = ["active", "paused", "blocked"];
|
|
157
179
|
it.each(NON_TERMINAL_STATUSES)("已有 %s goal → throw(D25 非终态守卫)", (status) => {
|
|
158
180
|
const session = createGoalSession();
|
|
@@ -250,4 +272,12 @@ describe("handleReportBlocked — active 守卫 + tick + transition + persist",
|
|
|
250
272
|
handleReportBlocked({ action: "report_blocked", reason: "" }, session, makeFakePorts()),
|
|
251
273
|
).toThrow(/reason/);
|
|
252
274
|
});
|
|
275
|
+
|
|
276
|
+
it("reason undefined → throw(schema 降级后 handler 唯一兜底:'required' 错误)", () => {
|
|
277
|
+
const session = createGoalSession();
|
|
278
|
+
session.state = activeState();
|
|
279
|
+
expect(() => handleReportBlocked({ action: "report_blocked" }, session, makeFakePorts())).toThrow(
|
|
280
|
+
/reason.*required/,
|
|
281
|
+
);
|
|
282
|
+
});
|
|
253
283
|
});
|
|
@@ -92,3 +92,20 @@ describe("goal_control 描述修正(A1/slug optional)", () => {
|
|
|
92
92
|
expect(ADAPTER_SRC).not.toMatch(/'slug' is required/);
|
|
93
93
|
});
|
|
94
94
|
});
|
|
95
|
+
|
|
96
|
+
describe("goal_control budget 默认策略锁定(S-8:三层信号冗余,防回滚无声)", () => {
|
|
97
|
+
// 新增的 budget-policy prompt 文本散落在三处(description 段 / promptGuidelines 项 /
|
|
98
|
+
// tokenBudget 参数 description),任一处被回滚都应触发测试失败——这是 prompt-lock 的核心诉求。
|
|
99
|
+
it("description 模板含中文 '默认不设 tokenBudget' 预算策略段", () => {
|
|
100
|
+
expect(DESCRIPTION).toContain("默认不设 tokenBudget");
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it("promptGuidelines budget 项含英文 'never set tokenBudget on your own initiative'", () => {
|
|
104
|
+
expect(ADAPTER_SRC).toContain("never set tokenBudget on your own initiative");
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("tokenBudget 参数 description 含 '默认不设' + '切勿自行决定设置预算'", () => {
|
|
108
|
+
// 参数 schema description 是第三层信号(与上面两层对齐,三层冗余,见 adapter 源码注释)
|
|
109
|
+
expect(ADAPTER_SRC).toMatch(/tokenBudget:[\s\S]*?默认不设[\s\S]*?切勿自行决定设置预算/);
|
|
110
|
+
});
|
|
111
|
+
});
|
|
@@ -1,17 +1,30 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* goal_control schema —
|
|
2
|
+
* goal_control schema — 扁平 Object 校验测试(OpenAI 兼容)
|
|
3
3
|
*
|
|
4
|
-
* schema
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* schema 为扁平 Type.Object + action 字段级 Type.Union(等价 enum)+ 各字段 Optional +
|
|
5
|
+
* additionalProperties:false。OpenAI function calling 要求 parameters 顶层 type:"object",
|
|
6
|
+
* 顶层 Type.Union 序列化后只有 anyOf 无 type 字段,会被严格 OpenAI 兼容网关 400 拒绝
|
|
7
|
+
* 整个会话——故采用扁平结构(范式对齐 scheduler ScheduleControlParams)。
|
|
8
|
+
*
|
|
9
|
+
* typebox schema 对象即序列化形态:GoalControlParams.type 就是发往 provider 的 JSON 顶层 type。
|
|
10
|
+
* pi 生产校验器为 typebox/compile 的 Compile(schema).Check(args),与此处 Value.Check 同源,
|
|
11
|
+
* 故用 Value.Check 验证。
|
|
12
|
+
*
|
|
13
|
+
* 分支隔离从 schema 层降级为运行时 handler 字段存在性校验(见 goal-control-adapter.test.ts)。
|
|
7
14
|
*/
|
|
8
15
|
import { describe, expect, it } from "vitest";
|
|
9
16
|
import { Value } from "typebox/value";
|
|
10
17
|
|
|
11
18
|
import { GoalControlParams } from "../adapters/goal-control-adapter";
|
|
12
19
|
|
|
13
|
-
describe("GoalControlParams —
|
|
14
|
-
//
|
|
20
|
+
describe("GoalControlParams — 扁平 Object(OpenAI 兼容)", () => {
|
|
21
|
+
// 顶层合规:序列化形态顶层必须有 type:"object",不得有 anyOf(否则被严格网关 400 拒绝)
|
|
22
|
+
it("顶层 type === 'object' 且无 anyOf(OpenAI function calling 合规)", () => {
|
|
23
|
+
expect(GoalControlParams.type).toBe("object");
|
|
24
|
+
expect(GoalControlParams.anyOf).toBeUndefined();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
// valid:各 action 完整 + optional 字段 → 通过
|
|
15
28
|
it.each([
|
|
16
29
|
["create 完整", { action: "create", objective: "x", successCriteria: "y" }],
|
|
17
30
|
["create + slug", { action: "create", slug: "refactor-auth", objective: "x", successCriteria: "y" }],
|
|
@@ -22,16 +35,19 @@ describe("GoalControlParams — discriminated union(TC6)", () => {
|
|
|
22
35
|
expect(Value.Check(GoalControlParams, params)).toBe(true);
|
|
23
36
|
});
|
|
24
37
|
|
|
25
|
-
//
|
|
38
|
+
// 关键语义变更:扁平化后所有声明的字段(含跨 action 的)均为 Optional,
|
|
39
|
+
// schema 不再做分支隔离。{action:"complete", evidence, objective} 放行
|
|
40
|
+
// (objective 是声明的 Optional 字段,schema 层无法区分它属于哪个 action)。
|
|
41
|
+
// 分支隔离降级为运行时 handler 字段存在性校验,见 goal-control-adapter.test.ts。
|
|
42
|
+
it("跨 action 字段(complete + objective)现在通过(分支隔离降级为运行时校验)", () => {
|
|
43
|
+
expect(Value.Check(GoalControlParams, { action: "complete", evidence: "x", objective: "leak" })).toBe(true);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// invalid:缺 action / 未知 action / 额外字段(additionalProperties:false)
|
|
26
47
|
it.each([
|
|
27
|
-
["
|
|
28
|
-
["
|
|
48
|
+
["缺 action", { objective: "x", successCriteria: "y" }],
|
|
49
|
+
["未知 action(不在 union 内)", { action: "unknown" }],
|
|
29
50
|
["create 含额外字段(additionalProperties:false)", { action: "create", objective: "x", successCriteria: "y", foo: 1 }],
|
|
30
|
-
["create 含 complete 分支字段 evidence(分支隔离)", { action: "create", objective: "x", successCriteria: "y", evidence: "leak" }],
|
|
31
|
-
["complete 缺 evidence", { action: "complete" }],
|
|
32
|
-
["report_blocked 缺 reason", { action: "report_blocked" }],
|
|
33
|
-
["未知 action", { action: "unknown" }],
|
|
34
|
-
["缺 action", { objective: "x" }],
|
|
35
51
|
])("invalid: %s → 拒绝", (_name, params) => {
|
|
36
52
|
expect(Value.Check(GoalControlParams, params)).toBe(false);
|
|
37
53
|
});
|
|
@@ -19,10 +19,12 @@
|
|
|
19
19
|
* create 不调 sendUserMessage:toolcall 时 AI 已在 turn 中,返回结果后自行续跑
|
|
20
20
|
* (与 /goal set 的 followUp 触发区分;对齐 Codex create_goal 不自动续跑)。
|
|
21
21
|
*
|
|
22
|
-
* schema
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
22
|
+
* schema:扁平 Type.Object(OpenAI 兼容,C3)——parameters 顶层必须是 type:"object",
|
|
23
|
+
* 顶层 Type.Union 序列化后只有 anyOf 无 type,会被严格 OpenAI 兼容网关 400 拒绝。故采用
|
|
24
|
+
* 扁平 Object + action 字段级 Type.Union(等价 enum)+ 各字段 Optional + additionalProperties:false。
|
|
25
|
+
* 分支隔离从 schema 层降级为运行时 handler 字段存在性校验(见各 handler 开头);
|
|
26
|
+
* pi 生产校验器为 typebox/compile 的 Compile(schema).Check(args),缺失/错误 action、额外字段
|
|
27
|
+
* 在 schema 层拒绝,缺必填字段由 handler 校验兜底。
|
|
26
28
|
*
|
|
27
29
|
* executionMode: "sequential"——状态变更 tool,不可与同批其他 tool 并行执行。
|
|
28
30
|
*
|
|
@@ -46,62 +48,61 @@ import { buildPorts } from "./ports";
|
|
|
46
48
|
// ── Params schema(discriminated union,C3)────────────
|
|
47
49
|
|
|
48
50
|
/**
|
|
49
|
-
* goal_control 参数 schema
|
|
50
|
-
*
|
|
51
|
+
* goal_control 参数 schema。扁平 Type.Object + action 字段级 Type.Union(等价 enum)。
|
|
52
|
+
*
|
|
53
|
+
* OpenAI function calling 规范要求 parameters 顶层必须是 type:"object";顶层 Type.Union
|
|
54
|
+
* 序列化后只有 anyOf 无 type 字段,会被严格的 OpenAI 兼容网关 400 拒绝整个会话。故采用
|
|
55
|
+
* 扁平 Object + action enum,分支隔离从 schema 层降级为运行时 handler 字段存在性校验
|
|
56
|
+
* (见各 handler 开头)。范式对齐 scheduler ScheduleControlParams。
|
|
57
|
+
*
|
|
58
|
+
* additionalProperties:false 阻挡未知字段;缺失/错误 action 仍由 schema 层拒绝。
|
|
51
59
|
*/
|
|
52
|
-
const
|
|
60
|
+
export const GoalControlParams = Type.Object(
|
|
53
61
|
{
|
|
54
|
-
action: Type.
|
|
62
|
+
action: Type.Union(
|
|
63
|
+
[Type.Literal("create"), Type.Literal("complete"), Type.Literal("report_blocked")],
|
|
64
|
+
{ description: "create | complete | report_blocked" },
|
|
65
|
+
),
|
|
55
66
|
slug: Type.Optional(
|
|
56
67
|
Type.String({
|
|
57
68
|
description: "可选。状态栏标题用的短 kebab-case 标识,仅用于显示,不注入 prompt。",
|
|
58
69
|
}),
|
|
59
70
|
),
|
|
60
|
-
objective: Type.
|
|
61
|
-
|
|
71
|
+
objective: Type.Optional(
|
|
72
|
+
Type.String({
|
|
73
|
+
description:
|
|
62
74
|
"必填。用你自己的话重述真实目标——用户实际想要达成什么,而非字面复述请求。模糊时推断最可能的意图并明确陈述。",
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
75
|
+
}),
|
|
76
|
+
),
|
|
77
|
+
successCriteria: Type.Optional(
|
|
78
|
+
Type.String({
|
|
79
|
+
description:
|
|
66
80
|
"必填。可检查的完成条件(如「测试通过」「文件 X 存在且含内容 Y」「命令 Z 输出 W」),不是愿景或「能用就行」。这是 complete 前必须逐条满足的门槛。",
|
|
67
|
-
|
|
81
|
+
}),
|
|
82
|
+
),
|
|
68
83
|
tokenBudget: Type.Optional(
|
|
69
84
|
Type.Number({
|
|
70
|
-
description:
|
|
85
|
+
description:
|
|
86
|
+
"可选。新目标的 token 预算(正数)。默认不设——仅在用户明确要求(如「控制在 1 万 token 内」)或你已获得用户明确同意时才设。切勿自行决定设置预算。",
|
|
71
87
|
}),
|
|
72
88
|
),
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
const CompleteParams = Type.Object(
|
|
78
|
-
{
|
|
79
|
-
action: Type.Literal("complete"),
|
|
80
|
-
evidence: Type.String({
|
|
81
|
-
description:
|
|
89
|
+
evidence: Type.Optional(
|
|
90
|
+
Type.String({
|
|
91
|
+
description:
|
|
82
92
|
"必填。具体完成证据(改动/新建的文件、通过的测试、运行的命令)。不要基于假设、意图或部分进度标记完成。",
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
const ReportBlockedParams = Type.Object(
|
|
89
|
-
{
|
|
90
|
-
action: Type.Literal("report_blocked"),
|
|
91
|
-
reason: Type.String({
|
|
92
|
-
description:
|
|
93
|
+
}),
|
|
94
|
+
),
|
|
95
|
+
reason: Type.Optional(
|
|
96
|
+
Type.String({
|
|
97
|
+
description:
|
|
93
98
|
"必填。具体阻塞条件及已尝试的方案。不要用于不确定、困难、缓慢或未完成的工作——继续做。",
|
|
94
|
-
|
|
99
|
+
}),
|
|
100
|
+
),
|
|
95
101
|
},
|
|
96
102
|
{ additionalProperties: false },
|
|
97
103
|
);
|
|
98
104
|
|
|
99
|
-
export
|
|
100
|
-
|
|
101
|
-
export type CreateActionParams = Static<typeof CreateParams>;
|
|
102
|
-
export type CompleteActionParams = Static<typeof CompleteParams>;
|
|
103
|
-
export type ReportBlockedActionParams = Static<typeof ReportBlockedParams>;
|
|
104
|
-
export type GoalControlActionParams = Static<typeof GoalControlParams>;
|
|
105
|
+
export type GoalControlParamsT = Static<typeof GoalControlParams>;
|
|
105
106
|
|
|
106
107
|
// ── Details(renderResult 数据来源)──────────────────
|
|
107
108
|
|
|
@@ -131,13 +132,16 @@ export interface GoalControlDetails {
|
|
|
131
132
|
* 终态旧 goal 走 createGoal 快速路径覆盖(createGoal 内部 active 守卫,终态可覆盖)。
|
|
132
133
|
*/
|
|
133
134
|
export function handleCreate(
|
|
134
|
-
params:
|
|
135
|
+
params: GoalControlParamsT,
|
|
135
136
|
session: GoalSession,
|
|
136
137
|
ports: ServicePorts,
|
|
137
138
|
): GoalControlDetails {
|
|
139
|
+
if (params.objective === undefined) {
|
|
140
|
+
throw new Error("'objective' required for create. Correct: {\"action\":\"create\",\"objective\":\"...\",\"successCriteria\":\"...\"}");
|
|
141
|
+
}
|
|
138
142
|
const objective = params.objective.trim();
|
|
139
143
|
if (!objective) {
|
|
140
|
-
// schema
|
|
144
|
+
// schema 已放行(扁平化后 optional),此处挡空串(LLM 可能传空串)
|
|
141
145
|
throw new Error(
|
|
142
146
|
"'objective' must not be empty. Describe the concrete objective to pursue. Correct: {\"action\":\"create\",\"slug\":\"<kebab-case>\",\"objective\":\"<concrete objective>\",\"successCriteria\":\"<checkable conditions>\"}",
|
|
143
147
|
);
|
|
@@ -145,6 +149,9 @@ export function handleCreate(
|
|
|
145
149
|
// slug 真 optional(TC11):缺失时 fallback goalId 截断(与 buildGoalGui 口径一致),不强制必填
|
|
146
150
|
const slugInput = params.slug?.trim();
|
|
147
151
|
|
|
152
|
+
if (params.successCriteria === undefined) {
|
|
153
|
+
throw new Error("'successCriteria' required for create. Correct: {\"action\":\"create\",\"objective\":\"...\",\"successCriteria\":\"...\"}");
|
|
154
|
+
}
|
|
148
155
|
const successCriteria = params.successCriteria.trim();
|
|
149
156
|
if (!successCriteria) {
|
|
150
157
|
throw new Error(
|
|
@@ -192,7 +199,7 @@ export function handleCreate(
|
|
|
192
199
|
* 全解耦后不再做 todo 完成前置检查——todo 是否全完成由 AI 自行判断(prompt 软建议)。
|
|
193
200
|
*/
|
|
194
201
|
export function handleComplete(
|
|
195
|
-
params:
|
|
202
|
+
params: GoalControlParamsT,
|
|
196
203
|
session: GoalSession,
|
|
197
204
|
ports: ServicePorts,
|
|
198
205
|
): GoalControlDetails {
|
|
@@ -201,6 +208,9 @@ export function handleComplete(
|
|
|
201
208
|
if (!isActiveStatus(state.status)) {
|
|
202
209
|
throw new Error(`Goal is not active (status: ${state.status}). Only an active goal can be completed.`);
|
|
203
210
|
}
|
|
211
|
+
if (params.evidence === undefined) {
|
|
212
|
+
throw new Error("'evidence' required for complete. Correct: {\"action\":\"complete\",\"evidence\":\"...\"}");
|
|
213
|
+
}
|
|
204
214
|
const evidence = params.evidence.trim();
|
|
205
215
|
if (!evidence) {
|
|
206
216
|
throw new Error(
|
|
@@ -223,7 +233,7 @@ export function handleComplete(
|
|
|
223
233
|
* 否则转 blocked 后 persistState 内部的 tick 因 status≠active 不累加,丢失最后一段运行时间。
|
|
224
234
|
*/
|
|
225
235
|
export function handleReportBlocked(
|
|
226
|
-
params:
|
|
236
|
+
params: GoalControlParamsT,
|
|
227
237
|
session: GoalSession,
|
|
228
238
|
ports: ServicePorts,
|
|
229
239
|
): GoalControlDetails {
|
|
@@ -232,6 +242,9 @@ export function handleReportBlocked(
|
|
|
232
242
|
if (state.status !== "active") {
|
|
233
243
|
throw new Error(`Goal is not active (status: ${state.status}). Only an active goal can report_blocked.`);
|
|
234
244
|
}
|
|
245
|
+
if (params.reason === undefined) {
|
|
246
|
+
throw new Error("'reason' required for report_blocked. Correct: {\"action\":\"report_blocked\",\"reason\":\"...\"}");
|
|
247
|
+
}
|
|
235
248
|
const reason = params.reason.trim();
|
|
236
249
|
if (!reason) {
|
|
237
250
|
throw new Error(
|
|
@@ -268,6 +281,8 @@ export function registerGoalControlTool(pi: ExtensionAPI, session: GoalSession):
|
|
|
268
281
|
label: "Goal Control",
|
|
269
282
|
description: `管理当前会话的目标(goal)。目标用于追踪需要完成验证的复杂工作。
|
|
270
283
|
|
|
284
|
+
预算策略:默认不设 tokenBudget。仅在用户明确要求(如「控制在 1 万 token 内」)或用户的指令明确暗示了一个限制时才设。若你认为应该设预算但用户从未提及,先询问用户获得明确同意,切勿自行决定。
|
|
285
|
+
|
|
271
286
|
动作:
|
|
272
287
|
- create:为复杂的多步骤工作(3+ 步骤、多文件改动、或需要完成验证的工作)主动创建目标。用自己的话重述真实目标,定义可检查的 successCriteria(完成条件)。琐碎的单步任务、普通提问、查找类任务不要创建目标。若已有 active/paused/blocked 目标会失败——请让用户运行 /goal resume 或 /goal clear 后再创建。
|
|
273
288
|
- complete:标记当前 active 目标完成。需要 evidence(具体证据:改动的文件、通过的测试、运行的命令),且必须满足每条 successCriteria 条件。若有预算,在总结里报告最终 token 用量。不要基于假设、意图或部分进度标记完成。
|
|
@@ -287,6 +302,9 @@ export function registerGoalControlTool(pi: ExtensionAPI, session: GoalSession):
|
|
|
287
302
|
// create:主动用于复杂多步骤任务。翻转原「显式启动」策略——让 goal 真正可用。
|
|
288
303
|
// 门槛:3+ 步骤 / 多文件 / 需完成验证,避免对琐碎任务滥建 goal 变噪音。
|
|
289
304
|
"create: proactively start a goal for complex, multi-step work (3+ steps, multi-file, or needs completion verification) — restate the real objective and define checkable successCriteria. Do NOT create for trivial single-step tasks, ordinary lookups, or when a goal is already active. Test: 'is this worth tracking to completion with verification?' — if yes, create a goal.",
|
|
305
|
+
// budget:默认不设预算——仅当用户显式要求(或明确同意)时才设。
|
|
306
|
+
// 对齐 description 的预算策略段 + tokenBudget 参数的 description(三层信号冗余)。
|
|
307
|
+
"budget: never set tokenBudget on your own initiative — the default is no budget. Set a budget only when the user explicitly requested one (e.g. \"keep it under 10k tokens\") or you obtained explicit user consent first. If you think a budget is warranted but the user never mentioned one, ask the user before creating the goal.",
|
|
290
308
|
// 全解耦下 todo 非硬前置——objective 实际达成才算(与 handleComplete「todo 由 AI 自判」一致)
|
|
291
309
|
"complete: proactively call when the active goal's objective is actually achieved, not merely in progress. Evidence must be concrete artifacts (files changed, tests green, commands run) meeting every successCriteria condition. Finishing all todos (incl. verification todos) is the usual readiness signal, but the real bar is the objective being met — you decide.",
|
|
292
310
|
// ≥3 distinct approaches 或同一 blocker 跨连续 turns(T7);达到阈值后报告,不反复报告同一 blocker
|
|
@@ -297,7 +315,7 @@ export function registerGoalControlTool(pi: ExtensionAPI, session: GoalSession):
|
|
|
297
315
|
|
|
298
316
|
async execute(
|
|
299
317
|
_toolCallId: string,
|
|
300
|
-
params:
|
|
318
|
+
params: GoalControlParamsT,
|
|
301
319
|
signal: AbortSignal | undefined,
|
|
302
320
|
_onUpdate: unknown,
|
|
303
321
|
ctx: ExtensionContext,
|
|
@@ -317,13 +335,14 @@ export function registerGoalControlTool(pi: ExtensionAPI, session: GoalSession):
|
|
|
317
335
|
details = handleReportBlocked(params, session, ports);
|
|
318
336
|
}
|
|
319
337
|
|
|
320
|
-
//
|
|
338
|
+
// 扁平化后 params.objective/reason 为 string|undefined(schema 不再按 action 收窄);
|
|
339
|
+
// handler 已对必填字段做存在性校验,此处用 ?. 兜底,未定义时空串
|
|
321
340
|
const text =
|
|
322
341
|
params.action === "create"
|
|
323
|
-
? `Goal created.\nGoal ID: ${details.goalId}\nSlug: ${details.slug ?? ""}\nObjective: ${params.objective
|
|
342
|
+
? `Goal created.\nGoal ID: ${details.goalId}\nSlug: ${details.slug ?? ""}\nObjective: ${params.objective?.trim() ?? ""}`
|
|
324
343
|
: params.action === "complete"
|
|
325
344
|
? `Goal completed.\nGoal ID: ${details.goalId}`
|
|
326
|
-
: `Goal reported blocked.\nGoal ID: ${details.goalId}\nReason: ${params.reason
|
|
345
|
+
: `Goal reported blocked.\nGoal ID: ${details.goalId}\nReason: ${params.reason?.trim() ?? ""}`;
|
|
327
346
|
|
|
328
347
|
// RPC 模式下附加 __gui__(用展开避免 details 来自 frozen 对象时加字段失败)
|
|
329
348
|
if (ctx.mode === "rpc" && session.state) {
|