@yeaft/webchat-agent 1.0.526 → 1.0.528
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/local-runtime/version.json +1 -1
- package/local-runtime/web/app.bundle.js +119 -97
- package/local-runtime/web/app.bundle.js.gz +0 -0
- package/local-runtime/web/index.html +2 -2
- package/local-runtime/web/style.bundle.css +1 -1
- package/local-runtime/web/style.bundle.css.gz +0 -0
- package/package.json +1 -1
- package/repo-workflow-cli.js +34 -26
- package/repo-workflow.js +0 -779
- package/yeaft/engine.js +13 -1
- package/yeaft/routing/router.js +0 -135
- package/yeaft/sub-agent/execution-control.js +16 -9
- package/yeaft/sub-agent/liveness.js +20 -12
- package/yeaft/sub-agent/runner.js +12 -15
- package/yeaft/sub-agent/status.js +5 -5
- package/yeaft/tools/activation.js +1 -4
- package/yeaft/tools/agent.js +21 -40
- package/yeaft/tools/apply-patch.js +442 -134
- package/yeaft/tools/bash.js +11 -9
- package/yeaft/tools/exit-worktree.js +1 -1
- package/yeaft/tools/file-read.js +36 -4
- package/yeaft/tools/git-read.js +29 -9
- package/yeaft/tools/index.js +0 -2
- package/yeaft/tools/list-agents.js +60 -58
- package/yeaft/tools/list-tasks.js +31 -3
- package/yeaft/tools/process-runner.js +29 -1
- package/yeaft/tools/registry.js +18 -0
- package/yeaft/tools/route-forward.js +1 -19
- package/yeaft/tools/update-agent.js +2 -1
- package/yeaft/tools/wait-agent.js +17 -17
- package/yeaft/tools/web-fetch.js +6 -1
- package/yeaft/web-bridge.js +2 -54
- package/skills/review-merge-tag/SKILL.md +0 -78
- package/yeaft/tools/repo-workflow.js +0 -80
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
* from every text_delta).
|
|
25
25
|
*
|
|
26
26
|
* Every envelope includes `outputFile` (durable per-agent JSONL log) and
|
|
27
|
-
* `liveness` (toolUseCount,
|
|
27
|
+
* `liveness` (toolUseCount, usageTokens, outputChars, msSinceLastEvent, recentTools,
|
|
28
28
|
* lastEventType) so the model can distinguish "stuck" from "still
|
|
29
29
|
* working" and can Read the log directly when it needs the full
|
|
30
30
|
* timeline.
|
|
@@ -57,10 +57,10 @@ function nextStepsFor(status, opts = {}) {
|
|
|
57
57
|
}
|
|
58
58
|
if (opts.timedOut && opts.stale) {
|
|
59
59
|
return (
|
|
60
|
-
'
|
|
61
|
-
'
|
|
62
|
-
'
|
|
63
|
-
'
|
|
60
|
+
'No observable event arrived within the diagnostic threshold. This does ' +
|
|
61
|
+
'not prove the provider or tool is dead. Do NOT keep calling WaitAgent ' +
|
|
62
|
+
'in a blind loop; inspect outputFile, then use CloseAgent only if you ' +
|
|
63
|
+
'actually intend to cancel, or report that the background task is quiet.'
|
|
64
64
|
);
|
|
65
65
|
}
|
|
66
66
|
if (opts.timedOut && opts.mustCollectReply) {
|
|
@@ -197,7 +197,7 @@ export default defineTool({
|
|
|
197
197
|
en: `Wait for a sub-agent's next state change (turn end, terminal, or wait-timeout) and retrieve a status envelope.
|
|
198
198
|
|
|
199
199
|
Returns JSON with explicit \`status\`, latest \`result\` text, \`liveness\`
|
|
200
|
-
counters (toolUseCount,
|
|
200
|
+
counters (actual toolUseCount, provider usageTokens, outputChars, msSinceLastEvent, recentTools), the
|
|
201
201
|
durable \`outputFile\` path you can Read at any time, and a status-specific
|
|
202
202
|
\`next_steps\` directive telling you what to call next.
|
|
203
203
|
|
|
@@ -214,16 +214,16 @@ Status semantics:
|
|
|
214
214
|
|
|
215
215
|
After WaitAgent returns, act on the status. A non-stale timeout after PromptAgent
|
|
216
216
|
has 'mustCollectReply=true': call WaitAgent again with a larger bounded timeout in
|
|
217
|
-
the same parent turn until idle/terminal. A stale
|
|
218
|
-
|
|
219
|
-
|
|
217
|
+
the same parent turn until idle/terminal. A stale diagnostic breaks that blind
|
|
218
|
+
wait loop but does not cancel or prove failure: inspect outputFile before deciding.
|
|
219
|
+
For ordinary SpawnAgent background work, use ListAgents or later completion
|
|
220
|
+
notifications instead of repeatedly re-waiting.
|
|
220
221
|
|
|
221
222
|
The default wait is a bounded 5000ms poll; callers may request up to 300000ms
|
|
222
|
-
(5 minutes).
|
|
223
|
-
checked after each timeout, and stale/stalled is the explicit stop condition.`,
|
|
223
|
+
(5 minutes). This caps one WaitAgent call only, not the sub-agent lifetime.`,
|
|
224
224
|
zh: `等待子 Agent 的下一次状态变更(turn 结束、终止或等待超时)并获取状态信封。
|
|
225
225
|
|
|
226
|
-
返回 JSON,含明确的 status、最新的 result 文本、liveness
|
|
226
|
+
返回 JSON,含明确的 status、最新的 result 文本、liveness 计数器(实际 toolUseCount、provider usageTokens、outputChars、
|
|
227
227
|
msSinceLastEvent、recentTools)、可随时 Read 的持久化 outputFile 路径,以及状态相关的 next_steps
|
|
228
228
|
指令告诉你下一步该调用什么。
|
|
229
229
|
|
|
@@ -237,11 +237,11 @@ msSinceLastEvent、recentTools)、可随时 Read 的持久化 outputFile 路
|
|
|
237
237
|
|
|
238
238
|
PromptAgent 后若非 stale/stalled 的有界等待超时,必须在同一父级 turn 使用更大的有界 timeout
|
|
239
239
|
再次调用 WaitAgent,直到 idle/terminal;不要改用 ListAgents/notification 丢下未收集的回复。
|
|
240
|
-
|
|
241
|
-
|
|
240
|
+
若信封显示 stale/stalled,应停止盲目等待并检查 outputFile;它只是诊断,不会取消任务,也不能证明
|
|
241
|
+
provider 或工具已经卡死。仅在确实要取消时使用 CloseAgent。
|
|
242
242
|
|
|
243
243
|
普通 SpawnAgent 异步流程仍用 ListAgents 做非阻塞状态检查,并依赖后续 completion
|
|
244
|
-
notification
|
|
244
|
+
notification;默认 5000ms、最大 300000ms 仅限制单次 WaitAgent 调用,不限制子 Agent 生命周期。`
|
|
245
245
|
},
|
|
246
246
|
parameters: {
|
|
247
247
|
type: 'object',
|
|
@@ -258,8 +258,8 @@ notification;不要对普通后台任务盲目循环等待。每次等待都
|
|
|
258
258
|
minimum: 0,
|
|
259
259
|
maximum: 300000,
|
|
260
260
|
description: {
|
|
261
|
-
en: 'Maximum
|
|
262
|
-
zh: '
|
|
261
|
+
en: 'Maximum duration of this wait call, not the sub-agent lifetime (default: 5000, max: 300000)',
|
|
262
|
+
zh: '本次等待调用的最长时间,不是子 Agent 生命周期(默认 5000,最大 300000)',
|
|
263
263
|
},
|
|
264
264
|
},
|
|
265
265
|
},
|
package/yeaft/tools/web-fetch.js
CHANGED
|
@@ -9,7 +9,12 @@ import { defineTool } from './types.js';
|
|
|
9
9
|
|
|
10
10
|
/** Strip HTML tags and normalize whitespace for readability. */
|
|
11
11
|
function htmlToText(html) {
|
|
12
|
-
|
|
12
|
+
// Remove common site chrome before applying the caller's length budget.
|
|
13
|
+
// Fall back for pages whose only content is navigation; raw/API is untouched.
|
|
14
|
+
const cleaned = html.replace(/<(script|style)\b[^>]*>[\s\S]*?<\/\1>/gi, '');
|
|
15
|
+
const withoutChrome = cleaned.replace(/<(nav|footer)\b[^>]*>[\s\S]*?<\/\1>/gi, '');
|
|
16
|
+
const source = withoutChrome.replace(/<[^>]+>/g, '').trim() ? withoutChrome : cleaned;
|
|
17
|
+
return source
|
|
13
18
|
// Remove script/style blocks
|
|
14
19
|
.replace(/<script[\s\S]*?<\/script>/gi, '')
|
|
15
20
|
.replace(/<style[\s\S]*?<\/style>/gi, '')
|
package/yeaft/web-bridge.js
CHANGED
|
@@ -77,12 +77,7 @@ import { hydrateYeaftStatusFromSession } from './status-cache.js';
|
|
|
77
77
|
import { handleVpSubscribe } from './vp/vp-bridge.js';
|
|
78
78
|
import { createVp, updateVp, deleteVp, readVp, VpCrudError } from './vp/vp-crud.js';
|
|
79
79
|
import { scanVpLibrary } from './vp/vp-store.js';
|
|
80
|
-
import {
|
|
81
|
-
bindRepoApprovalCapability,
|
|
82
|
-
createRouter,
|
|
83
|
-
isRepoApprovalCapability,
|
|
84
|
-
revokeRepoApprovalCapability,
|
|
85
|
-
} from './routing/router.js';
|
|
80
|
+
import { createRouter } from './routing/router.js';
|
|
86
81
|
import {
|
|
87
82
|
SessionCrudError,
|
|
88
83
|
createSessionFromSpec,
|
|
@@ -473,22 +468,6 @@ const turnAbortCtrls = new Map();
|
|
|
473
468
|
* @type {Map<string, { sessionId: string, vpId: string, threadId: string, key: string }>}
|
|
474
469
|
*/
|
|
475
470
|
const turnAbortMeta = new Map();
|
|
476
|
-
/** @type {Map<string, { capability: object, sessionId: string, vpId: string }>} Exact-turn repo approvals, never persisted or projected. */
|
|
477
|
-
const turnRepoApprovals = new Map();
|
|
478
|
-
|
|
479
|
-
function revokeTurnRepoApproval(turnId) {
|
|
480
|
-
const grant = turnRepoApprovals.get(turnId);
|
|
481
|
-
turnRepoApprovals.delete(turnId);
|
|
482
|
-
return grant ? revokeRepoApprovalCapability(grant.capability) : false;
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
function revokeRepoApprovalsForRecipient(sessionId, vpId) {
|
|
486
|
-
for (const [turnId, grant] of turnRepoApprovals.entries()) {
|
|
487
|
-
if (grant.sessionId !== sessionId || grant.vpId !== vpId) continue;
|
|
488
|
-
revokeTurnRepoApproval(turnId);
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
|
|
492
471
|
/**
|
|
493
472
|
* Per-VP status broker — the agent-side authority for VP timeline
|
|
494
473
|
* status. Lazy-initialized on first use because `sendSessionEvent` is
|
|
@@ -1175,7 +1154,6 @@ function invalidateGroupContext(sessionId, { abortRuntime = false } = {}) {
|
|
|
1175
1154
|
}
|
|
1176
1155
|
for (const [turnId, meta] of Array.from(turnAbortMeta.entries())) {
|
|
1177
1156
|
if (meta?.sessionId !== sessionId) continue;
|
|
1178
|
-
revokeTurnRepoApproval(turnId);
|
|
1179
1157
|
turnAbortCtrls.delete(turnId);
|
|
1180
1158
|
turnAbortMeta.delete(turnId);
|
|
1181
1159
|
}
|
|
@@ -2159,21 +2137,12 @@ async function routeEnvelopeToVpThread(sessionId, vpId, envelope) {
|
|
|
2159
2137
|
let related = false;
|
|
2160
2138
|
|
|
2161
2139
|
const meta = envelope?.msg?.meta || {};
|
|
2162
|
-
const repoApproval = envelope?._repoApproval;
|
|
2163
|
-
const hasRepoApproval = isRepoApprovalCapability(repoApproval);
|
|
2164
|
-
if (envelope?.msg?.role === 'user' || envelope?.msg?.from === 'user') {
|
|
2165
|
-
revokeRepoApprovalsForRecipient(sessionId, vpId);
|
|
2166
|
-
}
|
|
2167
2140
|
const isTaskResult = meta.injectedBy === 'task_result';
|
|
2168
2141
|
const sourceThreadId = typeof meta.sourceThreadId === 'string' && meta.sourceThreadId.trim()
|
|
2169
2142
|
? meta.sourceThreadId.trim()
|
|
2170
2143
|
: null;
|
|
2171
2144
|
|
|
2172
|
-
if (
|
|
2173
|
-
// Approval is an execution capability, not text. It must never collapse
|
|
2174
|
-
// into a related thread's pendingQueries/rescue path.
|
|
2175
|
-
thread = getOrCreateVpThread({ sessionId, vpId, title: fallbackTitle(text) });
|
|
2176
|
-
} else if (isTaskResult && sourceThreadId) {
|
|
2145
|
+
if (isTaskResult && sourceThreadId) {
|
|
2177
2146
|
thread = getOrCreateVpThread({ sessionId, vpId, threadId: sourceThreadId, title: fallbackTitle(text) });
|
|
2178
2147
|
} else if (runningThreads.length === 0) {
|
|
2179
2148
|
thread = getOrCreateVpThread({ sessionId, vpId, title: fallbackTitle(text) });
|
|
@@ -2216,12 +2185,6 @@ async function routeEnvelopeToVpThread(sessionId, vpId, envelope) {
|
|
|
2216
2185
|
if (!thread) return null;
|
|
2217
2186
|
rememberThreadMessage(thread, envelope?.msg);
|
|
2218
2187
|
const turnId = `${randomUUID().slice(0, 8)}:${vpId}`;
|
|
2219
|
-
if (hasRepoApproval) {
|
|
2220
|
-
if (!bindRepoApprovalCapability(repoApproval, { sessionId, recipientVpId: vpId, turnId })) {
|
|
2221
|
-
return null;
|
|
2222
|
-
}
|
|
2223
|
-
turnRepoApprovals.set(turnId, { capability: repoApproval, sessionId, vpId });
|
|
2224
|
-
}
|
|
2225
2188
|
const perfTraceId = envelope?._perfTraceId || envelope?.perfTraceId || null;
|
|
2226
2189
|
if (perfTraceId) {
|
|
2227
2190
|
recordAgentPerfTrace(ctx.CONFIG, {
|
|
@@ -2397,7 +2360,6 @@ function ensureDriverRunning(sessionId, vpId, threadId = 'main') {
|
|
|
2397
2360
|
} catch (err) {
|
|
2398
2361
|
console.warn('[Yeaft] driveVp: runVpTurn failed', vpId, err?.message || err);
|
|
2399
2362
|
} finally {
|
|
2400
|
-
revokeTurnRepoApproval(turnId);
|
|
2401
2363
|
turnAbortCtrls.delete(turnId);
|
|
2402
2364
|
turnAbortMeta.delete(turnId);
|
|
2403
2365
|
if (vpAborts.get(key) === vpAbort) vpAborts.delete(key);
|
|
@@ -2532,7 +2494,6 @@ export async function __testResetVpState() {
|
|
|
2532
2494
|
if (Array.isArray(inbox)) inbox.length = 0;
|
|
2533
2495
|
}
|
|
2534
2496
|
vpThreads.clear();
|
|
2535
|
-
for (const turnId of turnRepoApprovals.keys()) revokeTurnRepoApproval(turnId);
|
|
2536
2497
|
turnAbortCtrls.clear();
|
|
2537
2498
|
turnAbortMeta.clear();
|
|
2538
2499
|
await __testDrainVpDrivers();
|
|
@@ -5442,7 +5403,6 @@ async function runVpTurnWithEscalation(args) {
|
|
|
5442
5403
|
graceMs: ESCALATE_AFTER_ABORT_MS,
|
|
5443
5404
|
onEscalate: () => {
|
|
5444
5405
|
if (escalationState.escalated) return;
|
|
5445
|
-
revokeTurnRepoApproval(turnId);
|
|
5446
5406
|
escalationState.escalated = true;
|
|
5447
5407
|
escalationState.terminalEmitted = true;
|
|
5448
5408
|
console.error(
|
|
@@ -5661,7 +5621,6 @@ async function runVpTurn({ prompt, promptParts = null, sessionId, vpId, threadId
|
|
|
5661
5621
|
queryTimer = setTimeout(() => {
|
|
5662
5622
|
if (!vpAbort.signal.aborted) {
|
|
5663
5623
|
console.error(`[Yeaft] query timeout after ${queryTimeoutMs / 1000}s of silence — aborting VP ${vpId}`);
|
|
5664
|
-
revokeTurnRepoApproval(turnId);
|
|
5665
5624
|
try { vpAbort.abort(); } catch { /* best-effort */ }
|
|
5666
5625
|
}
|
|
5667
5626
|
}, queryTimeoutMs);
|
|
@@ -6346,10 +6305,6 @@ function emitQueuedTurnAbort(meta, turnId) {
|
|
|
6346
6305
|
}
|
|
6347
6306
|
|
|
6348
6307
|
function abortAllVpRuntime(aborted, sessionId = null) {
|
|
6349
|
-
for (const [turnId, meta] of Array.from(turnAbortMeta.entries())) {
|
|
6350
|
-
if (sessionId && meta?.sessionId !== sessionId) continue;
|
|
6351
|
-
revokeTurnRepoApproval(turnId);
|
|
6352
|
-
}
|
|
6353
6308
|
for (const [key, ctrl] of vpAborts) {
|
|
6354
6309
|
if (sessionId && !key.startsWith(`${sessionId}::`)) continue;
|
|
6355
6310
|
try {
|
|
@@ -6392,7 +6347,6 @@ export function handleYeaftAbortThread(_msg = {}) {
|
|
|
6392
6347
|
const meta = turnAbortMeta.get(turnId);
|
|
6393
6348
|
if ((meta?.threadId || 'main') !== targetThreadId) continue;
|
|
6394
6349
|
try { if (!ctrl.signal.aborted) { ctrl.abort(); aborted.push(turnId); } } catch { /* best-effort */ }
|
|
6395
|
-
revokeTurnRepoApproval(turnId);
|
|
6396
6350
|
turnAbortCtrls.delete(turnId);
|
|
6397
6351
|
turnAbortMeta.delete(turnId);
|
|
6398
6352
|
}
|
|
@@ -6411,7 +6365,6 @@ export function handleYeaftAbortThread(_msg = {}) {
|
|
|
6411
6365
|
for (const [turnId, ctrl] of turnAbortCtrls) {
|
|
6412
6366
|
try { if (!ctrl.signal.aborted) { ctrl.abort(); aborted.push(turnId); } } catch { /* best-effort */ }
|
|
6413
6367
|
}
|
|
6414
|
-
for (const turnId of turnAbortMeta.keys()) revokeTurnRepoApproval(turnId);
|
|
6415
6368
|
turnAbortCtrls.clear();
|
|
6416
6369
|
turnAbortMeta.clear();
|
|
6417
6370
|
abortAllVpRuntime(aborted);
|
|
@@ -6438,12 +6391,10 @@ export function handleYeaftAbortAll(msg = {}) {
|
|
|
6438
6391
|
const meta = turnAbortMeta.get(turnId);
|
|
6439
6392
|
if (sessionId && meta?.sessionId !== sessionId) continue;
|
|
6440
6393
|
try { if (!ctrl.signal.aborted) { ctrl.abort(); aborted.push(turnId); } } catch { /* best-effort */ }
|
|
6441
|
-
revokeTurnRepoApproval(turnId);
|
|
6442
6394
|
turnAbortCtrls.delete(turnId);
|
|
6443
6395
|
turnAbortMeta.delete(turnId);
|
|
6444
6396
|
}
|
|
6445
6397
|
if (!sessionId) {
|
|
6446
|
-
for (const turnId of turnAbortMeta.keys()) revokeTurnRepoApproval(turnId);
|
|
6447
6398
|
turnAbortCtrls.clear();
|
|
6448
6399
|
turnAbortMeta.clear();
|
|
6449
6400
|
}
|
|
@@ -6490,7 +6441,6 @@ export function handleYeaftAbortTurn(msg = {}) {
|
|
|
6490
6441
|
success = true;
|
|
6491
6442
|
abortedTurnIds.push(turnId);
|
|
6492
6443
|
}
|
|
6493
|
-
revokeTurnRepoApproval(turnId);
|
|
6494
6444
|
turnAbortCtrls.delete(turnId);
|
|
6495
6445
|
turnAbortMeta.delete(turnId);
|
|
6496
6446
|
}
|
|
@@ -7827,7 +7777,6 @@ export async function resetYeaftSession() {
|
|
|
7827
7777
|
try { if (!ctrl.signal.aborted) ctrl.abort(); } catch { /* best-effort */ }
|
|
7828
7778
|
}
|
|
7829
7779
|
vpAborts.clear();
|
|
7830
|
-
for (const turnId of turnAbortMeta.keys()) revokeTurnRepoApproval(turnId);
|
|
7831
7780
|
turnAbortCtrls.clear();
|
|
7832
7781
|
turnAbortMeta.clear();
|
|
7833
7782
|
vpInboxes.clear();
|
|
@@ -8306,7 +8255,6 @@ export const __testHooks = {
|
|
|
8306
8255
|
return loadAndBroadcastYeaftSkillSlashCommands();
|
|
8307
8256
|
},
|
|
8308
8257
|
resetAbortState() {
|
|
8309
|
-
for (const turnId of turnRepoApprovals.keys()) revokeTurnRepoApproval(turnId);
|
|
8310
8258
|
turnAbortCtrls.clear();
|
|
8311
8259
|
turnAbortMeta.clear();
|
|
8312
8260
|
vpAborts.clear();
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: review-merge-tag
|
|
3
|
-
description: 用 RepoWorkflow 在任意 GitHub 仓库执行开发 worktree、精确 PR review snapshot、base-ref CAS landing、数字 tag 与 workflow 验证;避免重复手工 git/gh 检查。
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Repository Workflow
|
|
7
|
-
|
|
8
|
-
不要逐条调用 `git fetch`、`git worktree list`、`gh pr view`、`git ls-remote` 和 `gh run watch` 来发现同一批事实。Session 内使用 `RepoWorkflow` 工具。非 Yeaft 环境可用 `yeaft-repo` CLI 执行 `prepare` 和 `review-prep`,这两个阶段返回与工具相同的紧凑 JSON;CLI 不能执行 `land`。
|
|
9
|
-
|
|
10
|
-
## 三个边界
|
|
11
|
-
|
|
12
|
-
### 1. 开发准备
|
|
13
|
-
|
|
14
|
-
```json
|
|
15
|
-
{
|
|
16
|
-
"phase": "prepare",
|
|
17
|
-
"name": "fix-short-description"
|
|
18
|
-
}
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
它会在当前仓库中确定远端默认分支、拉取最新精确基线、检查 worktree/branch/path 冲突,并创建或安全复用 `yeaft-wt/<name>`。后续开发只使用返回的 `worktree.path`。
|
|
22
|
-
|
|
23
|
-
### 2. 独立 review 准备
|
|
24
|
-
|
|
25
|
-
Reviewer 调用:
|
|
26
|
-
|
|
27
|
-
```json
|
|
28
|
-
{
|
|
29
|
-
"phase": "review-prep",
|
|
30
|
-
"pr": 123
|
|
31
|
-
}
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
它会一次冻结 GitHub PR 的 base branch/base SHA/head/merge snapshot、检查 draft/conflict/checks,并创建 detached exact-snapshot review worktree。Reviewer 必须审查返回的 `reviewWorktree.path`,并把明确结论、`pullRequest.baseBranch`、`pullRequest.baseSha`、`pullRequest.headSha` 和 `pullRequest.snapshotSha` 交回开发者。
|
|
35
|
-
|
|
36
|
-
工具只冻结事实,不替代代码审查。
|
|
37
|
-
|
|
38
|
-
### 3. Review 通过后的落地
|
|
39
|
-
|
|
40
|
-
只有收到独立 reviewer 的明确批准后才能调用。Reviewer 必须用 `RouteForward` 把绑定仓库、PR、exact base branch/base SHA/head/merge snapshot 的结构化批准交回 landing VP;宿主会随该 handoff 铸造不可序列化、一次性的 approval capability。随后 landing VP 才能在处理该 handoff 的同一 turn 调用:
|
|
41
|
-
|
|
42
|
-
```json
|
|
43
|
-
{
|
|
44
|
-
"phase": "land",
|
|
45
|
-
"pr": 123,
|
|
46
|
-
"baseBranch": "main",
|
|
47
|
-
"baseSha": "<exact 40-char base SHA>",
|
|
48
|
-
"reviewedHead": "<exact 40-char head SHA>",
|
|
49
|
-
"reviewedSnapshot": "<exact 40-char merge snapshot SHA>",
|
|
50
|
-
"tagPrefix": "v1.0.",
|
|
51
|
-
"workflow": "Dev Release"
|
|
52
|
-
}
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
`approvedBy` 等普通字符串没有授权语义。`land` 只接受宿主 capability,并重新冻结 PR;任何 repo、PR、recipient、base branch、base SHA、head 或 snapshot 不匹配都会停止。Reviewed snapshot 必须以批准的 base SHA/head 为两个父提交;landing 的目标 ref 只从批准的 base branch 推导,并在任何写入前拒绝 PR retarget。之后它通过单次 receive-pack 更新,用 `--force-with-lease=<base-ref>:<approved-base>` 将该 exact snapshot 安装到批准的远端 base。若 base 在最后冻结与 receive 之间前进,lease 会拒绝更新,并且不会创建 tag。若要求 tag,它只在 base CAS 成功后从远端 tags 数值计算下一版,用 create-only lease 推送并复核 base 与 tag。若要求 workflow,它等待匹配 tag 与 reviewed snapshot SHA 的 run 完成。
|
|
56
|
-
|
|
57
|
-
Worktree cleanup 不属于 landing。`land` 会在任何仓库或远端副作用前拒绝 cleanup 请求,包括 active `repoRoot`;需要 cleanup 时必须在 landing 完成后走独立操作,cleanup 失败不能改变 landing 结果。
|
|
58
|
-
|
|
59
|
-
## 通用性与边界
|
|
60
|
-
|
|
61
|
-
- 适用于 Agent 可访问的任意本地 GitHub 仓库,不包含项目路径或固定 `main` 假设。
|
|
62
|
-
- 需要 `git` 和已认证的 `gh` CLI;不依赖 Python,也不通过 shell 拼接命令。
|
|
63
|
-
- 当前不支持 GitLab/Bitbucket;不要把 GitHub 成功误报为 forge-agnostic。
|
|
64
|
-
- 自动化只消除重复事实核对,不消除独立 review、测试选择、代码判断或用户授权。
|
|
65
|
-
- `land` 后若 workflow 失败,merge/tag 可能已经完成;读取错误 JSON 的 `details.remoteEffects`,不要盲目重试或重打 tag。
|
|
66
|
-
|
|
67
|
-
## CLI
|
|
68
|
-
|
|
69
|
-
Yeaft 外部或人工终端只可运行准备阶段:
|
|
70
|
-
|
|
71
|
-
```bash
|
|
72
|
-
yeaft-repo prepare --name fix-short-description
|
|
73
|
-
yeaft-repo review-prep --pr 123
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
独立 CLI 不能执行 `land`。Landing 必须在 Session 中通过 `RepoWorkflow` 工具执行,并携带当前 handoff 由宿主签发、绑定 exact review 的 approval capability;普通参数或 `approvedBy` 字符串不能替代该 capability。
|
|
77
|
-
|
|
78
|
-
每条可用命令 stdout 只输出一个成功 JSON;失败时 stderr 输出一个带稳定 `code` 和副作用状态的 JSON。
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
import { defineTool } from './types.js';
|
|
2
|
-
import {
|
|
3
|
-
formatRepoWorkflowError,
|
|
4
|
-
landRepoWorkflow,
|
|
5
|
-
prepareRepoReview,
|
|
6
|
-
prepareRepoWorkflow,
|
|
7
|
-
} from '../../repo-workflow.js';
|
|
8
|
-
|
|
9
|
-
const repoWorkflow = defineTool({
|
|
10
|
-
name: 'RepoWorkflow',
|
|
11
|
-
description: {
|
|
12
|
-
en: `Run one deterministic GitHub repository workflow phase and return compact structured evidence.
|
|
13
|
-
|
|
14
|
-
Use this instead of repeatedly issuing git/gh shell commands:
|
|
15
|
-
- prepare: fetch the remote default branch and create/reuse an exact-base development worktree.
|
|
16
|
-
- review-prep: freeze a PR's exact base/head/GitHub merge snapshot and create/reuse a clean detached review worktree.
|
|
17
|
-
- land: re-freeze an independently approved exact base branch/base SHA/head/snapshot and atomically install that exact snapshot on the approved remote base with an expected-old lease, then optionally create the next numeric tag and wait for a workflow. Worktree cleanup is deliberately separate.
|
|
18
|
-
|
|
19
|
-
This tool does not review code and never infers approval. The land phase requires the reviewer identity and exact SHAs returned by review-prep. Supports any local GitHub repository available to the Agent; git and authenticated gh CLIs are required.`,
|
|
20
|
-
zh: `执行一个确定性的 GitHub 仓库工作流阶段,并返回紧凑的结构化证据。
|
|
21
|
-
|
|
22
|
-
应使用此工具代替反复执行 git/gh shell 命令:
|
|
23
|
-
- prepare:拉取远端默认分支,并创建或复用精确基线的开发 worktree。
|
|
24
|
-
- review-prep:冻结 PR 的精确 base/head/GitHub merge snapshot,并创建或复用干净的 detached review worktree。
|
|
25
|
-
- land:重新冻结已经独立批准的 exact base branch/base SHA/head/snapshot,以 expected-old lease 在已批准的远端 base 上原子安装该 exact snapshot,随后可选创建下一个数字 tag 并等待 workflow。Worktree cleanup 刻意与 landing 分离。
|
|
26
|
-
|
|
27
|
-
此工具不做代码审查,也不会推断 review 已通过。land 必须提供 reviewer 身份以及 review-prep 返回的精确 SHA。适用于 Agent 可访问的任意本地 GitHub 仓库;需要 git 和已认证的 gh CLI。`,
|
|
28
|
-
},
|
|
29
|
-
parameters: {
|
|
30
|
-
type: 'object',
|
|
31
|
-
properties: {
|
|
32
|
-
phase: { type: 'string', enum: ['prepare', 'review-prep', 'land'] },
|
|
33
|
-
cwd: { type: 'string', description: 'Repository or worktree path; defaults to the current engine cwd' },
|
|
34
|
-
remote: { type: 'string', description: 'Git remote name (default: origin)' },
|
|
35
|
-
baseBranch: { type: 'string', description: 'Explicit base branch for prepare, or exact approved destination branch for land' },
|
|
36
|
-
baseSha: { type: 'string', description: 'Exact independently approved base SHA for land' },
|
|
37
|
-
name: { type: 'string', description: 'Development or review worktree name' },
|
|
38
|
-
worktreePath: { type: 'string', description: 'Explicit worktree path' },
|
|
39
|
-
pr: { type: 'integer', description: 'GitHub pull request number' },
|
|
40
|
-
reviewedHead: { type: 'string', description: 'Exact independently approved PR head SHA' },
|
|
41
|
-
reviewedSnapshot: { type: 'string', description: 'Exact independently approved GitHub merge snapshot SHA' },
|
|
42
|
-
tagPrefix: { type: 'string', description: 'Optional numeric tag prefix, for example v1.0.' },
|
|
43
|
-
tagStart: { type: 'integer', minimum: 0 },
|
|
44
|
-
workflow: { type: 'string', description: 'Optional GitHub Actions workflow name to wait for' },
|
|
45
|
-
waitTimeoutMs: { type: 'integer', minimum: 1 },
|
|
46
|
-
pollIntervalMs: { type: 'integer', minimum: 1 },
|
|
47
|
-
},
|
|
48
|
-
required: ['phase'],
|
|
49
|
-
},
|
|
50
|
-
timeoutMs: 0,
|
|
51
|
-
isConcurrencySafe: () => false,
|
|
52
|
-
isReadOnly: () => false,
|
|
53
|
-
isDestructive: input => input?.phase === 'land',
|
|
54
|
-
async execute(input, ctx) {
|
|
55
|
-
const options = { ...input, cwd: input.cwd || ctx?.cwd || process.cwd() };
|
|
56
|
-
try {
|
|
57
|
-
const dependencies = input.phase === 'land'
|
|
58
|
-
? {
|
|
59
|
-
signal: ctx?.signal,
|
|
60
|
-
approvalCapability: ctx?.inboundEnvelope?._repoApproval,
|
|
61
|
-
approvalContext: {
|
|
62
|
-
sessionId: ctx?.sessionId,
|
|
63
|
-
recipientVpId: ctx?.senderVpId,
|
|
64
|
-
turnId: ctx?.turnId,
|
|
65
|
-
},
|
|
66
|
-
}
|
|
67
|
-
: { signal: ctx?.signal };
|
|
68
|
-
const result = input.phase === 'prepare'
|
|
69
|
-
? await prepareRepoWorkflow(options, dependencies)
|
|
70
|
-
: input.phase === 'review-prep'
|
|
71
|
-
? await prepareRepoReview(options, dependencies)
|
|
72
|
-
: await landRepoWorkflow(options, dependencies);
|
|
73
|
-
return JSON.stringify(result);
|
|
74
|
-
} catch (error) {
|
|
75
|
-
return JSON.stringify(formatRepoWorkflowError(error));
|
|
76
|
-
}
|
|
77
|
-
},
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
export default repoWorkflow;
|