@devcxl/opencode-cabbage 1.3.0 → 2.0.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/.codex-plugin/plugin.json +1 -1
- package/README.md +35 -23
- package/README.zh.md +22 -15
- package/assets/agents/dev-lifecycle.md +6 -0
- package/assets/prompts/bootstrap.md +4 -0
- package/assets/skills/flow-change/SKILL.md +109 -0
- package/assets/skills/flow-design/SKILL.md +15 -0
- package/assets/skills/flow-docs/SKILL.md +112 -0
- package/assets/skills/flow-incident/SKILL.md +98 -0
- package/assets/skills/flow-research/SKILL.md +9 -0
- package/assets/skills/flow-setup/SKILL.md +13 -0
- package/codex-skills/agent-dev-lifecycle/SKILL.md +2 -0
- package/codex-skills/flow-change/SKILL.md +109 -0
- package/codex-skills/flow-design/SKILL.md +18 -3
- package/codex-skills/flow-docs/SKILL.md +112 -0
- package/codex-skills/flow-incident/SKILL.md +98 -0
- package/codex-skills/flow-research/SKILL.md +12 -3
- package/codex-skills/flow-setup/SKILL.md +13 -0
- package/dist/index.d.ts +2 -5
- package/dist/index.js +1 -4
- package/dist/index.js.map +1 -1
- package/dist/plugin/agents.d.ts +20 -0
- package/dist/plugin/agents.js +79 -0
- package/dist/plugin/agents.js.map +1 -1
- package/dist/plugin/cabbage-cli.d.ts +28 -0
- package/dist/plugin/cabbage-cli.js +92 -0
- package/dist/plugin/cabbage-cli.js.map +1 -0
- package/dist/plugin/cabbage-tool.d.ts +103 -0
- package/dist/plugin/cabbage-tool.js +169 -0
- package/dist/plugin/cabbage-tool.js.map +1 -0
- package/dist/plugin/commands.d.ts +10 -0
- package/dist/plugin/commands.js +1 -1
- package/dist/plugin/commands.js.map +1 -1
- package/dist/plugin/continuation.d.ts +22 -0
- package/dist/plugin/continuation.js +233 -0
- package/dist/plugin/continuation.js.map +1 -0
- package/dist/plugin/goal-store.d.ts +42 -49
- package/dist/plugin/goal-store.js +35 -32
- package/dist/plugin/goal-store.js.map +1 -1
- package/dist/plugin/goal.d.ts +92 -28
- package/dist/plugin/goal.js +201 -158
- package/dist/plugin/goal.js.map +1 -1
- package/dist/plugin/install-dir.d.ts +13 -0
- package/dist/plugin/install-dir.js +46 -0
- package/dist/plugin/install-dir.js.map +1 -0
- package/dist/plugin/server.d.ts +4 -13
- package/dist/plugin/server.js +278 -423
- package/dist/plugin/server.js.map +1 -1
- package/dist/plugin/session-abort.d.ts +5 -0
- package/dist/plugin/session-abort.js +26 -0
- package/dist/plugin/session-abort.js.map +1 -0
- package/dist/plugin/skills.d.ts +15 -0
- package/dist/plugin/skills.js +47 -50
- package/dist/plugin/skills.js.map +1 -1
- package/dist/plugin.d.ts +2 -2
- package/dist/plugin.js +6 -2
- package/dist/plugin.js.map +1 -1
- package/dist/util/paths.d.ts +0 -6
- package/dist/util/paths.js +0 -15
- package/dist/util/paths.js.map +1 -1
- package/package.json +4 -7
- package/vendor/cabbage/MANIFEST.json +8 -0
- package/vendor/cabbage/cabbage_cli/__init__.py +1 -0
- package/vendor/cabbage/cabbage_cli/__main__.py +2 -0
- package/vendor/cabbage/cabbage_cli/assets/docs-site/.vitepress/config.ts +78 -0
- package/vendor/cabbage/cabbage_cli/assets/docs-site/README.md +31 -0
- package/vendor/cabbage/cabbage_cli/assets/docs-site/package.json +16 -0
- package/vendor/cabbage/cabbage_cli/assets/docs-site/pnpm-lock.yaml +2504 -0
- package/vendor/cabbage/cabbage_cli/assets/integrations/cabbage.yml +127 -0
- package/vendor/cabbage/cabbage_cli/assets/templates/adr.md +47 -0
- package/vendor/cabbage/cabbage_cli/assets/templates/api-design.md +45 -0
- package/vendor/cabbage/cabbage_cli/assets/templates/change-record.md +21 -0
- package/vendor/cabbage/cabbage_cli/assets/templates/database-design.md +37 -0
- package/vendor/cabbage/cabbage_cli/assets/templates/generic.md +20 -0
- package/vendor/cabbage/cabbage_cli/assets/templates/impact.md +41 -0
- package/vendor/cabbage/cabbage_cli/assets/templates/incident.md +48 -0
- package/vendor/cabbage/cabbage_cli/assets/templates/postmortem.md +43 -0
- package/vendor/cabbage/cabbage_cli/assets/templates/prd.md +71 -0
- package/vendor/cabbage/cabbage_cli/assets/templates/release-plan.md +45 -0
- package/vendor/cabbage/cabbage_cli/assets/templates/rfc.md +44 -0
- package/vendor/cabbage/cabbage_cli/assets/templates/security-review.md +35 -0
- package/vendor/cabbage/cabbage_cli/assets/templates/tasks.md +62 -0
- package/vendor/cabbage/cabbage_cli/assets/templates/tech-spec.md +87 -0
- package/vendor/cabbage/cabbage_cli/assets/templates/test-plan.md +44 -0
- package/vendor/cabbage/cabbage_cli/assets/workflows/change.yaml +11 -0
- package/vendor/cabbage/cabbage_cli/assets/workflows/incident.yaml +4 -0
- package/vendor/cabbage/cabbage_cli/cli.py +283 -0
- package/vendor/cabbage/cabbage_cli/core.py +606 -0
- package/vendor/cabbage/cabbage_cli/scaffold.py +313 -0
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import { MAX_CONTINUATIONS, budgetReached, continuationPrompt, formatGoal, isBlockedReport, isDoneReport, mutateGoal, normalizeProfileModel, snapshotTokenTotal, } from "./goal.js";
|
|
2
|
+
import { consumeAborted } from "./session-abort.js";
|
|
3
|
+
import { readIndex, updateFlowSession, removeFlowSession } from "../kernel/session-index.js";
|
|
4
|
+
import { getContextBlock, formatContextBlock } from "../kernel/context.js";
|
|
5
|
+
const continuationInFlight = new Set();
|
|
6
|
+
const COMPACTION_THRESHOLD = 20;
|
|
7
|
+
/** autoResume 跳过窗口:索引 30s 内更新过续接的 flow 不恢复(可能刚续接过,立即恢复会双发) */
|
|
8
|
+
const AUTO_RESUME_SKIP_WINDOW_MS = 30_000;
|
|
9
|
+
/**
|
|
10
|
+
* 子会话活动跟踪(V2 无 session.list/children 查询 API,改用事件溯源):
|
|
11
|
+
* - session.created(带 parentID)建立父子关系
|
|
12
|
+
* - execution.started / retry.scheduled → busy;execution.succeeded/failed/interrupted → idle
|
|
13
|
+
*
|
|
14
|
+
* 局限:重启后内存清空,重启瞬间仍在运行且未再发事件的子会话不被识别(窗口小,可接受;
|
|
15
|
+
* 子代理结果注入父会话会重新触发事件流)。V1 依赖实时查询,此为 V2 的降级替代。
|
|
16
|
+
*/
|
|
17
|
+
export class SessionActivityTracker {
|
|
18
|
+
parentOf = new Map();
|
|
19
|
+
busy = new Set();
|
|
20
|
+
onCreated(sessionID, parentID) {
|
|
21
|
+
if (parentID)
|
|
22
|
+
this.parentOf.set(sessionID, parentID);
|
|
23
|
+
}
|
|
24
|
+
onBusy(sessionID) {
|
|
25
|
+
this.busy.add(sessionID);
|
|
26
|
+
}
|
|
27
|
+
onIdle(sessionID) {
|
|
28
|
+
this.busy.delete(sessionID);
|
|
29
|
+
}
|
|
30
|
+
hasWorkingChildren(sessionID) {
|
|
31
|
+
for (const [child, parent] of this.parentOf) {
|
|
32
|
+
if (parent === sessionID && this.busy.has(child))
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/** 续接前注入会话快照的 agent/模型,防回落默认值(switch 为幂等调用) */
|
|
39
|
+
async function restoreSessionProfile(ctx, sessionID, profile) {
|
|
40
|
+
if (profile?.agent) {
|
|
41
|
+
await ctx.session.switchAgent({ sessionID, agent: profile.agent }).catch((error) => {
|
|
42
|
+
console.warn("[cabbage] switchAgent failed:", error?.message ?? error);
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
const model = normalizeProfileModel(profile?.model);
|
|
46
|
+
if (model) {
|
|
47
|
+
await ctx.session.switchModel({ sessionID, model }).catch((error) => {
|
|
48
|
+
console.warn("[cabbage] switchModel failed:", error?.message ?? error);
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
export async function queueContinuation(ctx, store, sessionID, projectDir, tracker) {
|
|
53
|
+
if (continuationInFlight.has(sessionID))
|
|
54
|
+
return;
|
|
55
|
+
continuationInFlight.add(sessionID);
|
|
56
|
+
try {
|
|
57
|
+
const decision = await mutateGoal(store, sessionID, async (goal) => {
|
|
58
|
+
// 读改写与事件处理同锁串行,杜绝续接计数/状态被并发覆盖
|
|
59
|
+
if (!goal || goal.status !== "active")
|
|
60
|
+
return { goal, value: null };
|
|
61
|
+
await updateFlowSession(projectDir, goal.parentIssueNumber, { status: goal.status, continuationCount: goal.continuationCount }).catch(() => undefined);
|
|
62
|
+
if (consumeAborted(sessionID)) {
|
|
63
|
+
goal.status = "paused";
|
|
64
|
+
goal.statusReason = "aborted by user";
|
|
65
|
+
await updateFlowSession(projectDir, goal.parentIssueNumber, { status: "paused" }).catch(() => undefined);
|
|
66
|
+
return { goal, value: null };
|
|
67
|
+
}
|
|
68
|
+
if (goal.continuationCount >= MAX_CONTINUATIONS) {
|
|
69
|
+
goal.status = "paused";
|
|
70
|
+
await updateFlowSession(projectDir, goal.parentIssueNumber, { status: "paused" }).catch(() => undefined);
|
|
71
|
+
return { goal, value: null };
|
|
72
|
+
}
|
|
73
|
+
// 子会话活动闸:后台 subagent 仍 busy 时跳过续接(父会话轮次结束不代表任务静默)
|
|
74
|
+
if (tracker.hasWorkingChildren(sessionID)) {
|
|
75
|
+
return { goal, value: null };
|
|
76
|
+
}
|
|
77
|
+
// 尾部静默检查:轮次结束后仍可能有新 user 消息在途或 synthetic 尚未消费。
|
|
78
|
+
// 最后一条是 user/synthetic、或最后 assistant 未完成(无 completed 时间)→ 跳过,
|
|
79
|
+
// 等这些消息各自触发的新轮次结束事件重新驱动续接。
|
|
80
|
+
const messages = await store.context(sessionID);
|
|
81
|
+
if (!messages || messages.length === 0)
|
|
82
|
+
return { goal, value: null };
|
|
83
|
+
const lastMessage = messages[messages.length - 1];
|
|
84
|
+
if (lastMessage?.type === "user" || lastMessage?.type === "synthetic")
|
|
85
|
+
return { goal, value: null };
|
|
86
|
+
let lastAssistant = null;
|
|
87
|
+
for (let i = messages.length - 1; i >= 0; i -= 1) {
|
|
88
|
+
if (messages[i]?.type === "assistant") {
|
|
89
|
+
lastAssistant = messages[i];
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
if (!lastAssistant)
|
|
94
|
+
return { goal, value: null };
|
|
95
|
+
const completed = lastAssistant.time?.completed;
|
|
96
|
+
if (!(typeof completed === "number" && completed > 0))
|
|
97
|
+
return { goal, value: null };
|
|
98
|
+
// [BLOCKED:] 阻塞报告 / [DONE:] 完成报告:agent 明确信号 → 暂停 goal 请用户/验证介入
|
|
99
|
+
// (防空转烧 token;完成报告后应由 dev-lifecycle 派 goal-verify 做最终独立验证)。
|
|
100
|
+
// resume 后首次 tick 豁免(statusReason='resumed' 被消费清除),
|
|
101
|
+
// 否则 resume 会立即被上一轮旧报告再次暂停成死路。
|
|
102
|
+
const assistantText = (lastAssistant.content ?? [])
|
|
103
|
+
.filter((part) => part?.type === "text" && typeof part.text === "string")
|
|
104
|
+
.map((part) => part.text)
|
|
105
|
+
.join("\n");
|
|
106
|
+
const agentSignaled = isBlockedReport(assistantText) || isDoneReport(assistantText);
|
|
107
|
+
if (agentSignaled) {
|
|
108
|
+
if (goal.statusReason === "resumed") {
|
|
109
|
+
goal.statusReason = "";
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
goal.status = "paused";
|
|
113
|
+
goal.statusReason = isBlockedReport(assistantText) ? "blocked report" : "done report";
|
|
114
|
+
await updateFlowSession(projectDir, goal.parentIssueNumber, { status: "paused" }).catch(() => undefined);
|
|
115
|
+
return { goal, value: null };
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
else if (goal.statusReason === "resumed") {
|
|
119
|
+
// 豁免消费:resume 后的首次 tick 无论是否命中信号都清除 kickoff 标记。
|
|
120
|
+
// 若不消费,首 tick 正常推进后 statusReason 残留,后续真实信号会被误豁免一次。
|
|
121
|
+
goal.statusReason = "";
|
|
122
|
+
}
|
|
123
|
+
// Token 记账(会话累计快照):used = 当前会话累计 − create 基线,单调不减。
|
|
124
|
+
// cache.read 已含此前轮次付费 token,compaction 不做分段(预算为护栏非账单)。
|
|
125
|
+
const tokensBaseline = goal.tokensBaseline ?? 0;
|
|
126
|
+
const tokensUsed = Math.max(goal.tokensUsed ?? 0, Math.max(0, snapshotTokenTotal(await store.sessionTokens(sessionID)) - tokensBaseline));
|
|
127
|
+
goal.tokensBaseline = tokensBaseline;
|
|
128
|
+
goal.tokensUsed = tokensUsed;
|
|
129
|
+
// token 预算护栏:达限即暂停(用户需提高预算后 resume,护栏优先)
|
|
130
|
+
if (budgetReached(goal)) {
|
|
131
|
+
goal.status = "paused";
|
|
132
|
+
goal.statusReason = "token budget reached";
|
|
133
|
+
await updateFlowSession(projectDir, goal.parentIssueNumber, { status: "paused" }).catch(() => undefined);
|
|
134
|
+
return { goal, value: null };
|
|
135
|
+
}
|
|
136
|
+
// 先写计数后发 prompt:崩溃窗口只会"少发一次续接"(等下次轮次结束重试),
|
|
137
|
+
// 不会"prompt 已发但计数未落盘"导致重启 autoResume 双发。
|
|
138
|
+
goal.continuationCount++;
|
|
139
|
+
await updateFlowSession(projectDir, goal.parentIssueNumber, {
|
|
140
|
+
continuationCount: goal.continuationCount,
|
|
141
|
+
lastContinuedAt: Date.now(),
|
|
142
|
+
}).catch(() => undefined);
|
|
143
|
+
return { goal, value: { proceed: true, goal } };
|
|
144
|
+
});
|
|
145
|
+
if (!decision?.proceed || !decision.goal)
|
|
146
|
+
return;
|
|
147
|
+
// abort 二次检查:轮次结束事件与 abort 事件并发时,避免对已中断的会话发出续接
|
|
148
|
+
if (consumeAborted(sessionID)) {
|
|
149
|
+
await mutateGoal(store, sessionID, async (goal) => {
|
|
150
|
+
if (!goal)
|
|
151
|
+
return { goal, value: null };
|
|
152
|
+
goal.status = "paused";
|
|
153
|
+
goal.continuationCount = Math.max(0, goal.continuationCount - 1);
|
|
154
|
+
await updateFlowSession(projectDir, goal.parentIssueNumber, { status: "paused" }).catch(() => undefined);
|
|
155
|
+
return { goal, value: null };
|
|
156
|
+
}).catch(() => undefined);
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
// 每 20 次续接主动 compact 会话历史(原生压缩;失败不阻塞续接)
|
|
160
|
+
if (decision.goal.continuationCount > 0 && decision.goal.continuationCount % COMPACTION_THRESHOLD === 0) {
|
|
161
|
+
try {
|
|
162
|
+
await ctx.session.compact({ sessionID });
|
|
163
|
+
}
|
|
164
|
+
catch (err) {
|
|
165
|
+
console.warn("[cabbage] compaction failed:", err);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
const contextBlock = await getContextBlock(projectDir);
|
|
169
|
+
const contextText = contextBlock ? `${formatContextBlock(contextBlock)}\n\n` : "";
|
|
170
|
+
try {
|
|
171
|
+
await restoreSessionProfile(ctx, sessionID, decision.goal.sessionProfile);
|
|
172
|
+
await ctx.session.synthetic({
|
|
173
|
+
sessionID,
|
|
174
|
+
text: contextText + continuationPrompt(decision.goal.parentIssueNumber),
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
catch (err) {
|
|
178
|
+
// 发送失败 → 回滚计数(尽力;回滚失败只会少一次续接,方向安全)
|
|
179
|
+
console.error("[cabbage] continuation prompt failed, rolling back counter:", err);
|
|
180
|
+
await mutateGoal(store, sessionID, async (goal) => {
|
|
181
|
+
if (!goal)
|
|
182
|
+
return { goal, value: null };
|
|
183
|
+
goal.continuationCount = Math.max(0, goal.continuationCount - 1);
|
|
184
|
+
await updateFlowSession(projectDir, goal.parentIssueNumber, { continuationCount: goal.continuationCount }).catch(() => undefined);
|
|
185
|
+
return { goal, value: null };
|
|
186
|
+
}).catch(() => undefined);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
catch (err) {
|
|
190
|
+
console.error("[cabbage] continuation failed:", err);
|
|
191
|
+
}
|
|
192
|
+
finally {
|
|
193
|
+
continuationInFlight.delete(sessionID);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
export async function autoResume(ctx, store, projectDir) {
|
|
197
|
+
const index = await readIndex(projectDir);
|
|
198
|
+
for (const [parentIssueNumber, entry] of Object.entries(index.flows)) {
|
|
199
|
+
if (entry.status !== "active")
|
|
200
|
+
continue;
|
|
201
|
+
// 重启双发防护:仅跳过"最近实际续接过"的 flow(lastContinuedAt 在发送续接时写入)。
|
|
202
|
+
// 不用 updatedAt——它被 queueContinuation 开头的索引心跳无条件刷新,反映"最近轮次结束",
|
|
203
|
+
// 崩溃前频繁结束的 flow 会被误跳过且无再武装(静默卡住)。
|
|
204
|
+
// 无 lastContinuedAt 的旧 entry 不跳过(兼容升级前索引)。跳过时告警保证可观测。
|
|
205
|
+
if (entry.lastContinuedAt && Date.now() - entry.lastContinuedAt < AUTO_RESUME_SKIP_WINDOW_MS) {
|
|
206
|
+
console.warn(`[cabbage] auto-resume: skip #${parentIssueNumber} (continued ${Math.round((Date.now() - entry.lastContinuedAt) / 1000)}s ago; next turn end re-arms)`);
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
const { goal } = await store.read(entry.sessionID);
|
|
210
|
+
// 索引一致性自愈:entry 指向的 goal 已是别的 Flow(edit 换 Flow 后旧 entry 残留/rebind 失败)→
|
|
211
|
+
// 删除陈旧 entry,防止每次重启对同一会话重复续接
|
|
212
|
+
if (goal && goal.parentIssueNumber !== Number(parentIssueNumber)) {
|
|
213
|
+
console.warn(`[cabbage] auto-resume: index #${parentIssueNumber} points to goal of Flow #${goal.parentIssueNumber}; removing stale entry`);
|
|
214
|
+
await removeFlowSession(projectDir, Number(parentIssueNumber));
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
if (!goal || goal.status !== "active") {
|
|
218
|
+
await updateFlowSession(projectDir, Number(parentIssueNumber), { status: "paused" });
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
try {
|
|
222
|
+
await restoreSessionProfile(ctx, entry.sessionID, goal.sessionProfile);
|
|
223
|
+
await ctx.session.synthetic({
|
|
224
|
+
sessionID: entry.sessionID,
|
|
225
|
+
text: `[auto-resume] Plugin restarted. Resuming previous goal:\n\n${formatGoal(goal)}\n\nContinue working.`,
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
catch (err) {
|
|
229
|
+
console.error("[cabbage] auto-resume failed:", err);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
//# sourceMappingURL=continuation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"continuation.js","sourceRoot":"","sources":["../../src/plugin/continuation.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,33 +1,52 @@
|
|
|
1
1
|
import type { GoalData } from "./goal.js";
|
|
2
2
|
/**
|
|
3
|
-
* goal 存储抽象(V2
|
|
3
|
+
* goal 存储抽象(OpenCode V2)。
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* V2 实现 = 会话 metadata(ctx.session.get/update);
|
|
6
|
+
* 消息面用 session.context 投影;记账快照用会话累计 tokens。
|
|
7
|
+
* read 的 fail-closed 语义(异常 → null)由实现保证,调用方依赖此兜底。
|
|
8
8
|
*/
|
|
9
|
-
/** goal
|
|
9
|
+
/** goal 宿主会话的最小面(V2 SessionInfo 子集) */
|
|
10
10
|
export interface GoalSession {
|
|
11
11
|
parentID: string | null;
|
|
12
12
|
metadata: Record<string, unknown>;
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
info?: {
|
|
13
|
+
agent?: string | null;
|
|
14
|
+
model?: {
|
|
15
|
+
providerID?: string;
|
|
17
16
|
id?: string;
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
17
|
+
variant?: string;
|
|
18
|
+
} | null;
|
|
19
|
+
tokens?: unknown;
|
|
20
|
+
}
|
|
21
|
+
/** V2 消息投影的最小形状(session.context 返回值的子集) */
|
|
22
|
+
export interface GoalContextMessage {
|
|
23
|
+
type?: string;
|
|
24
|
+
time?: {
|
|
25
|
+
completed?: number;
|
|
26
|
+
created?: number;
|
|
25
27
|
} | null;
|
|
26
|
-
|
|
28
|
+
content?: Array<{
|
|
27
29
|
type?: string;
|
|
28
30
|
text?: string;
|
|
29
31
|
}>;
|
|
30
32
|
}
|
|
33
|
+
/**
|
|
34
|
+
* GoalStore V2 实现所需的 session 域最小面(ctx.session 的子集)。
|
|
35
|
+
* 参数/返回值放宽为 any:该接口是插件到存储的唯一适配边界,
|
|
36
|
+
* 内部做运行时形状校验后以强类型 GoalStore 暴露(避免把平台品牌类型渗入内核)。
|
|
37
|
+
*/
|
|
38
|
+
export interface GoalStoreSessionApi {
|
|
39
|
+
get(args: {
|
|
40
|
+
sessionID: string;
|
|
41
|
+
}): Promise<any>;
|
|
42
|
+
update(args: {
|
|
43
|
+
sessionID: string;
|
|
44
|
+
metadata: any;
|
|
45
|
+
}): Promise<any>;
|
|
46
|
+
context(args: {
|
|
47
|
+
sessionID: string;
|
|
48
|
+
}): Promise<any>;
|
|
49
|
+
}
|
|
31
50
|
/** goal 状态存储。read 的 fail-closed 语义(异常 → null)由实现保证,调用方依赖此兜底。 */
|
|
32
51
|
export interface GoalStore {
|
|
33
52
|
read(sessionID: string): Promise<{
|
|
@@ -36,36 +55,10 @@ export interface GoalStore {
|
|
|
36
55
|
}>;
|
|
37
56
|
/** 写 goal;null 表示删除。existingSession 复用 read 结果避免二次读取。 */
|
|
38
57
|
write(sessionID: string, goal: GoalData | null, existingSession?: GoalSession | null): Promise<void>;
|
|
39
|
-
/**
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
export interface GoalStoreClient {
|
|
44
|
-
session: {
|
|
45
|
-
get(args: {
|
|
46
|
-
sessionID: string;
|
|
47
|
-
}): Promise<{
|
|
48
|
-
data?: {
|
|
49
|
-
parentID?: string | null;
|
|
50
|
-
metadata?: Record<string, unknown>;
|
|
51
|
-
} | null;
|
|
52
|
-
}>;
|
|
53
|
-
update(args: {
|
|
54
|
-
sessionID: string;
|
|
55
|
-
metadata: Record<string, unknown>;
|
|
56
|
-
}): Promise<unknown>;
|
|
57
|
-
messages(args: {
|
|
58
|
-
sessionID: string;
|
|
59
|
-
limit?: number;
|
|
60
|
-
}): Promise<{
|
|
61
|
-
data?: GoalMessage[];
|
|
62
|
-
}>;
|
|
63
|
-
};
|
|
58
|
+
/** 会话消息投影(续接尾部检查与信号检测用)。异常/非数组时返回 null。 */
|
|
59
|
+
context(sessionID: string): Promise<GoalContextMessage[] | null>;
|
|
60
|
+
/** 会话累计 token 快照(预算护栏记账用)。异常时返回 null。 */
|
|
61
|
+
sessionTokens(sessionID: string): Promise<unknown | null>;
|
|
64
62
|
}
|
|
65
|
-
/**
|
|
66
|
-
export declare function
|
|
67
|
-
/**
|
|
68
|
-
* 从插件上下文构建 SDK client(V1 宿主机制)。
|
|
69
|
-
* V2 下 ctx 本身即 server client,本函数与 createMetadataGoalStore 一起被替换。
|
|
70
|
-
*/
|
|
71
|
-
export declare function createGoalClient(serverUrl: URL, v1Client: any): import("@opencode-ai/sdk/v2").OpencodeClient;
|
|
63
|
+
/** V2 实现:goal 持久化在 session metadata 的 goal 键;消息/tokens 走 session 域读取。 */
|
|
64
|
+
export declare function createSessionGoalStore(session: GoalStoreSessionApi): GoalStore;
|
|
@@ -1,17 +1,21 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
function toGoalSession(session) {
|
|
2
|
+
return {
|
|
3
|
+
parentID: session?.parentID ?? null,
|
|
4
|
+
metadata: session?.metadata ?? {},
|
|
5
|
+
agent: session?.agent ?? null,
|
|
6
|
+
model: session?.model ?? null,
|
|
7
|
+
tokens: session?.tokens,
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
/** V2 实现:goal 持久化在 session metadata 的 goal 键;消息/tokens 走 session 域读取。 */
|
|
11
|
+
export function createSessionGoalStore(session) {
|
|
4
12
|
return {
|
|
5
13
|
async read(sessionID) {
|
|
6
14
|
try {
|
|
7
|
-
const result = await
|
|
8
|
-
|
|
9
|
-
if (!session)
|
|
15
|
+
const result = await session.get({ sessionID });
|
|
16
|
+
if (!result)
|
|
10
17
|
return { goal: null, session: null };
|
|
11
|
-
const goalSession =
|
|
12
|
-
parentID: session.parentID ?? null,
|
|
13
|
-
metadata: session.metadata ?? {},
|
|
14
|
-
};
|
|
18
|
+
const goalSession = toGoalSession(result);
|
|
15
19
|
if (!goalSession.metadata.goal)
|
|
16
20
|
return { goal: null, session: goalSession };
|
|
17
21
|
return { goal: goalSession.metadata.goal, session: goalSession };
|
|
@@ -21,34 +25,33 @@ export function createMetadataGoalStore(client) {
|
|
|
21
25
|
}
|
|
22
26
|
},
|
|
23
27
|
async write(sessionID, goal, existingSession) {
|
|
24
|
-
const
|
|
25
|
-
if (!
|
|
28
|
+
const current = existingSession ?? (await this.read(sessionID)).session;
|
|
29
|
+
if (!current)
|
|
26
30
|
throw new Error("Failed to get session");
|
|
27
|
-
const existing =
|
|
31
|
+
const existing = current.metadata ?? {};
|
|
28
32
|
const metadata = goal === null
|
|
29
33
|
? Object.fromEntries(Object.entries(existing).filter(([k]) => k !== "goal"))
|
|
30
34
|
: { ...existing, goal };
|
|
31
|
-
await
|
|
35
|
+
await session.update({ sessionID, metadata });
|
|
32
36
|
},
|
|
33
|
-
async
|
|
34
|
-
|
|
35
|
-
|
|
37
|
+
async context(sessionID) {
|
|
38
|
+
try {
|
|
39
|
+
const result = await session.context({ sessionID });
|
|
40
|
+
return Array.isArray(result) ? result : null;
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
async sessionTokens(sessionID) {
|
|
47
|
+
try {
|
|
48
|
+
const result = await session.get({ sessionID });
|
|
49
|
+
return result?.tokens ?? null;
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
36
54
|
},
|
|
37
55
|
};
|
|
38
56
|
}
|
|
39
|
-
/**
|
|
40
|
-
* 从插件上下文构建 SDK client(V1 宿主机制)。
|
|
41
|
-
* V2 下 ctx 本身即 server client,本函数与 createMetadataGoalStore 一起被替换。
|
|
42
|
-
*/
|
|
43
|
-
export function createGoalClient(serverUrl, v1Client) {
|
|
44
|
-
const v1Config = v1Client?.getConfig?.() ?? {};
|
|
45
|
-
return createOpencodeClient({
|
|
46
|
-
baseUrl: serverUrl.origin,
|
|
47
|
-
headers: v1Config.headers,
|
|
48
|
-
fetch: v1Config.fetch,
|
|
49
|
-
// SDK v2 默认 ThrowOnError=false(4xx/5xx 静默 resolve 为 {error}),
|
|
50
|
-
// 必须显式开启,否则 readGoal/writeGoal 的 try/catch 与错误传播全部失效
|
|
51
|
-
throwOnError: true,
|
|
52
|
-
});
|
|
53
|
-
}
|
|
54
57
|
//# sourceMappingURL=goal-store.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"goal-store.js","sourceRoot":"","sources":["../../src/plugin/goal-store.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"goal-store.js","sourceRoot":"","sources":["../../src/plugin/goal-store.ts"],"names":[],"mappings":"AAgDA,SAAS,aAAa,CAAC,OAMH;IAClB,OAAO;QACL,QAAQ,EAAE,OAAO,EAAE,QAAQ,IAAI,IAAI;QACnC,QAAQ,EAAE,OAAO,EAAE,QAAQ,IAAI,EAAE;QACjC,KAAK,EAAE,OAAO,EAAE,KAAK,IAAI,IAAI;QAC7B,KAAK,EAAE,OAAO,EAAE,KAAK,IAAI,IAAI;QAC7B,MAAM,EAAE,OAAO,EAAE,MAAM;KACxB,CAAA;AACH,CAAC;AAED,yEAAyE;AACzE,MAAM,UAAU,sBAAsB,CAAC,OAA4B;IACjE,OAAO;QACL,KAAK,CAAC,IAAI,CAAC,SAAS;YAClB,IAAI,CAAC;gBACH,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,EAAE,SAAS,EAAE,CAAC,CAAA;gBAC/C,IAAI,CAAC,MAAM;oBAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,CAAA;gBACjD,MAAM,WAAW,GAAG,aAAa,CAAC,MAAM,CAAC,CAAA;gBACzC,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,IAAI;oBAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,WAAW,EAAE,CAAA;gBAC3E,OAAO,EAAE,IAAI,EAAE,WAAW,CAAC,QAAQ,CAAC,IAAgB,EAAE,OAAO,EAAE,WAAW,EAAE,CAAA;YAC9E,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,CAAA;YACtC,CAAC;QACH,CAAC;QAED,KAAK,CAAC,KAAK,CAAC,SAAS,EAAE,IAAI,EAAE,eAAe;YAC1C,MAAM,OAAO,GAAG,eAAe,IAAI,CAAC,MAAM,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CAAA;YACvE,IAAI,CAAC,OAAO;gBAAE,MAAM,IAAI,KAAK,CAAC,uBAAuB,CAAC,CAAA;YAEtD,MAAM,QAAQ,GAA4B,OAAO,CAAC,QAAQ,IAAI,EAAE,CAAA;YAChE,MAAM,QAAQ,GAAG,IAAI,KAAK,IAAI;gBAC5B,CAAC,CAAC,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,MAAM,CAAC,CAAC;gBAC5E,CAAC,CAAC,EAAE,GAAG,QAAQ,EAAE,IAAI,EAAE,CAAA;YAEzB,MAAM,OAAO,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC,CAAA;QAC/C,CAAC;QAED,KAAK,CAAC,OAAO,CAAC,SAAS;YACrB,IAAI,CAAC;gBACH,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,OAAO,CAAC,EAAE,SAAS,EAAE,CAAC,CAAA;gBACnD,OAAO,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAA;YAC9C,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,IAAI,CAAA;YACb,CAAC;QACH,CAAC;QAED,KAAK,CAAC,aAAa,CAAC,SAAS;YAC3B,IAAI,CAAC;gBACH,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,EAAE,SAAS,EAAE,CAAC,CAAA;gBAC/C,OAAO,MAAM,EAAE,MAAM,IAAI,IAAI,CAAA;YAC/B,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,IAAI,CAAA;YACb,CAAC;QACH,CAAC;KACF,CAAA;AACH,CAAC"}
|
package/dist/plugin/goal.d.ts
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
|
-
import type { ToolContext, ToolResult } from "@opencode-ai/plugin";
|
|
2
1
|
import type { GoalSession, GoalStore } from "./goal-store.js";
|
|
3
2
|
export type GoalStatus = "active" | "paused" | "complete";
|
|
4
|
-
/**
|
|
3
|
+
/** 用户最后使用的会话配置快照:续接注入时显式传回,防止回落/切换默认 agent 与模型 */
|
|
5
4
|
export interface GoalSessionProfile {
|
|
6
5
|
agent: string;
|
|
6
|
+
/** V1 存量语义:{providerID, modelID};V2 新写入为 {providerID, id, variant?},读取时两种都接受 */
|
|
7
7
|
model?: {
|
|
8
8
|
providerID: string;
|
|
9
|
-
modelID
|
|
9
|
+
modelID?: string;
|
|
10
|
+
id?: string;
|
|
11
|
+
variant?: string;
|
|
10
12
|
};
|
|
11
13
|
}
|
|
12
14
|
/** 最小化 goal(§10.2):目标与验收条件从 Flow Record(Parent Issue body)读取 */
|
|
@@ -14,15 +16,15 @@ export interface GoalData {
|
|
|
14
16
|
parentIssueNumber: number;
|
|
15
17
|
status: GoalStatus;
|
|
16
18
|
continuationCount: number;
|
|
17
|
-
/** 用户最后使用的 agent/模型(create
|
|
19
|
+
/** 用户最后使用的 agent/模型(create 或用户切换时快照) */
|
|
18
20
|
sessionProfile?: GoalSessionProfile;
|
|
19
21
|
/** 可选 token 预算(护栏):tokensUsed 达限后自动 pause,防止长跑成本失控 */
|
|
20
22
|
tokenBudget?: number;
|
|
21
|
-
/** 已用 token
|
|
23
|
+
/** 已用 token(会话累计快照 − baseline,单调不减;护栏非账单) */
|
|
22
24
|
tokensUsed?: number;
|
|
23
|
-
/** 记账基线:create
|
|
25
|
+
/** 记账基线:create 时点的会话累计 token 快照 */
|
|
24
26
|
tokensBaseline?: number;
|
|
25
|
-
/** goal 创建时刻(ms
|
|
27
|
+
/** goal 创建时刻(ms):记账基线与诊断用 */
|
|
26
28
|
createdAt?: number;
|
|
27
29
|
/** 状态原因:'resumed' 是 UI/resume 的 kickoff 信号(首次 tick 豁免 blocked 检测后消费清除) */
|
|
28
30
|
statusReason?: string;
|
|
@@ -30,11 +32,22 @@ export interface GoalData {
|
|
|
30
32
|
export declare function createGoal(parentIssueNumber: number, sessionProfile?: GoalSessionProfile, tokenBudget?: number): GoalData;
|
|
31
33
|
export declare function canTransitionTo(goal: GoalData, target: GoalStatus): boolean;
|
|
32
34
|
export declare function formatGoal(goal: GoalData): string;
|
|
35
|
+
/** 把存储的 profile.model(V1/V2 两种形状)归一为 V2 的 ModelRef;无法识别时返回 null */
|
|
36
|
+
export declare function normalizeProfileModel(model: {
|
|
37
|
+
providerID?: string;
|
|
38
|
+
modelID?: string;
|
|
39
|
+
id?: string;
|
|
40
|
+
variant?: string;
|
|
41
|
+
} | undefined): {
|
|
42
|
+
providerID: string;
|
|
43
|
+
id: string;
|
|
44
|
+
variant?: string;
|
|
45
|
+
} | null;
|
|
33
46
|
export declare const MAX_CONTINUATIONS = 50;
|
|
34
47
|
/**
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
48
|
+
* token 快照 = input + reasoning + cache.read + output。
|
|
49
|
+
* OpenCode 每轮的 cache.read 已含此前所有轮次付费 token,
|
|
50
|
+
* 最新快照即整段成本,无需跨消息求和。reasoning 也计费,护栏方向偏严。
|
|
38
51
|
*/
|
|
39
52
|
export declare function snapshotTokenTotal(tokens: unknown): number;
|
|
40
53
|
/** token 预算护栏:达到或超过预算即应暂停(null 表示未设预算) */
|
|
@@ -61,24 +74,75 @@ export declare function mutateGoal<T>(store: GoalStore, sessionID: string, fn: (
|
|
|
61
74
|
goal: GoalData | null;
|
|
62
75
|
value: T;
|
|
63
76
|
}>): Promise<T>;
|
|
64
|
-
|
|
77
|
+
/** goal 工具执行时可见的 context 最小面(V2 ToolContext 子集) */
|
|
78
|
+
export interface GoalToolContext {
|
|
79
|
+
sessionID: string;
|
|
80
|
+
agent?: string;
|
|
81
|
+
}
|
|
82
|
+
export interface GoalToolInput {
|
|
83
|
+
op: "create" | "get" | "complete" | "resume" | "cancel" | "pause" | "edit";
|
|
84
|
+
parent_issue_number?: number;
|
|
85
|
+
token_budget?: number;
|
|
86
|
+
}
|
|
87
|
+
export interface GoalToolResult {
|
|
88
|
+
content: string;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* goal 工具的允许名单(与权限规则层一致)。
|
|
92
|
+
* 规则层通过 agent permissions 隐藏/放行工具;此处的检查是工具级兜底,
|
|
93
|
+
* 覆盖规则层未覆盖的 agent(例如后续新注册的 agent)。
|
|
94
|
+
*/
|
|
95
|
+
export declare const GOAL_ALLOWED_AGENTS: Set<string>;
|
|
96
|
+
export declare const GOAL_TOOL_INPUT_SCHEMA: {
|
|
97
|
+
readonly type: "object";
|
|
98
|
+
readonly properties: {
|
|
99
|
+
readonly op: {
|
|
100
|
+
readonly type: "string";
|
|
101
|
+
readonly enum: readonly ["create", "get", "complete", "resume", "cancel", "pause", "edit"];
|
|
102
|
+
readonly description: "Goal operation";
|
|
103
|
+
};
|
|
104
|
+
readonly parent_issue_number: {
|
|
105
|
+
readonly type: "number";
|
|
106
|
+
readonly description: "Parent GitHub Issue number of the Flow Record (required for create; editable via edit)";
|
|
107
|
+
};
|
|
108
|
+
readonly token_budget: {
|
|
109
|
+
readonly type: "number";
|
|
110
|
+
readonly description: "Optional token budget guardrail; goal auto-pauses when tokensUsed reaches it. Pass 0 via edit to remove the budget";
|
|
111
|
+
};
|
|
112
|
+
};
|
|
113
|
+
readonly required: readonly ["op"];
|
|
114
|
+
readonly additionalProperties: false;
|
|
115
|
+
};
|
|
116
|
+
export declare const GOAL_TOOL_DESCRIPTION = "Manage the active goal-mode objective (minimal: parentIssueNumber/status/continuationCount).\n\nUse a single op field:\n- create: starts a goal bound to a Flow Record. Requires parent_issue_number.\n- get: returns the current goal.\n- resume: re-activates a paused goal.\n- cancel: discards the current goal.\n- pause: pauses the active goal.\n- complete: marks the goal as completed. Follow the returned instructions.";
|
|
117
|
+
/**
|
|
118
|
+
* 构建 goal 工具描述符:{name, description, input(JSON Schema), options, execute}。
|
|
119
|
+
* execute 返回 {content}(V2 Tool.Result 形状);授权在工具内自检(见 GOAL_ALLOWED_AGENTS)。
|
|
120
|
+
*/
|
|
121
|
+
export declare function buildGoalTool(store: GoalStore, projectDir: string): {
|
|
122
|
+
name: string;
|
|
65
123
|
description: string;
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
124
|
+
input: {
|
|
125
|
+
readonly type: "object";
|
|
126
|
+
readonly properties: {
|
|
127
|
+
readonly op: {
|
|
128
|
+
readonly type: "string";
|
|
129
|
+
readonly enum: readonly ["create", "get", "complete", "resume", "cancel", "pause", "edit"];
|
|
130
|
+
readonly description: "Goal operation";
|
|
131
|
+
};
|
|
132
|
+
readonly parent_issue_number: {
|
|
133
|
+
readonly type: "number";
|
|
134
|
+
readonly description: "Parent GitHub Issue number of the Flow Record (required for create; editable via edit)";
|
|
135
|
+
};
|
|
136
|
+
readonly token_budget: {
|
|
137
|
+
readonly type: "number";
|
|
138
|
+
readonly description: "Optional token budget guardrail; goal auto-pauses when tokensUsed reaches it. Pass 0 via edit to remove the budget";
|
|
139
|
+
};
|
|
140
|
+
};
|
|
141
|
+
readonly required: readonly ["op"];
|
|
142
|
+
readonly additionalProperties: false;
|
|
143
|
+
};
|
|
144
|
+
options: {
|
|
145
|
+
permission: string;
|
|
78
146
|
};
|
|
79
|
-
execute(args:
|
|
80
|
-
op: "complete" | "create" | "get" | "resume" | "cancel" | "pause" | "edit";
|
|
81
|
-
parent_issue_number?: number | undefined;
|
|
82
|
-
token_budget?: number | undefined;
|
|
83
|
-
}, context: ToolContext): Promise<ToolResult>;
|
|
147
|
+
execute(args: GoalToolInput, context: GoalToolContext): Promise<GoalToolResult>;
|
|
84
148
|
};
|