principles-disciple 1.7.6 → 1.7.8
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/dist/commands/context.js +5 -15
- package/dist/commands/evolution-status.js +2 -9
- package/dist/commands/export.js +61 -8
- package/dist/commands/nocturnal-review.d.ts +24 -0
- package/dist/commands/nocturnal-review.js +265 -0
- package/dist/commands/nocturnal-rollout.d.ts +27 -0
- package/dist/commands/nocturnal-rollout.js +671 -0
- package/dist/commands/nocturnal-train.d.ts +25 -0
- package/dist/commands/nocturnal-train.js +919 -0
- package/dist/commands/pain.js +8 -21
- package/dist/constants/tools.d.ts +2 -2
- package/dist/constants/tools.js +1 -1
- package/dist/core/adaptive-thresholds.d.ts +186 -0
- package/dist/core/adaptive-thresholds.js +300 -0
- package/dist/core/config.d.ts +2 -38
- package/dist/core/config.js +6 -61
- package/dist/core/event-log.d.ts +1 -2
- package/dist/core/event-log.js +0 -3
- package/dist/core/evolution-engine.js +1 -21
- package/dist/core/evolution-reducer.d.ts +7 -1
- package/dist/core/evolution-reducer.js +56 -4
- package/dist/core/evolution-types.d.ts +61 -9
- package/dist/core/evolution-types.js +31 -9
- package/dist/core/external-training-contract.d.ts +276 -0
- package/dist/core/external-training-contract.js +269 -0
- package/dist/core/local-worker-routing.d.ts +175 -0
- package/dist/core/local-worker-routing.js +525 -0
- package/dist/core/model-deployment-registry.d.ts +218 -0
- package/dist/core/model-deployment-registry.js +503 -0
- package/dist/core/model-training-registry.d.ts +295 -0
- package/dist/core/model-training-registry.js +475 -0
- package/dist/core/nocturnal-arbiter.d.ts +159 -0
- package/dist/core/nocturnal-arbiter.js +534 -0
- package/dist/core/nocturnal-candidate-scoring.d.ts +137 -0
- package/dist/core/nocturnal-candidate-scoring.js +266 -0
- package/dist/core/nocturnal-compliance.d.ts +175 -0
- package/dist/core/nocturnal-compliance.js +824 -0
- package/dist/core/nocturnal-dataset.d.ts +224 -0
- package/dist/core/nocturnal-dataset.js +443 -0
- package/dist/core/nocturnal-executability.d.ts +85 -0
- package/dist/core/nocturnal-executability.js +331 -0
- package/dist/core/nocturnal-export.d.ts +124 -0
- package/dist/core/nocturnal-export.js +275 -0
- package/dist/core/nocturnal-paths.d.ts +124 -0
- package/dist/core/nocturnal-paths.js +214 -0
- package/dist/core/nocturnal-trajectory-extractor.d.ts +242 -0
- package/dist/core/nocturnal-trajectory-extractor.js +307 -0
- package/dist/core/nocturnal-trinity.d.ts +311 -0
- package/dist/core/nocturnal-trinity.js +880 -0
- package/dist/core/paths.d.ts +6 -0
- package/dist/core/paths.js +6 -0
- package/dist/core/principle-training-state.d.ts +121 -0
- package/dist/core/principle-training-state.js +321 -0
- package/dist/core/promotion-gate.d.ts +238 -0
- package/dist/core/promotion-gate.js +529 -0
- package/dist/core/session-tracker.d.ts +10 -0
- package/dist/core/session-tracker.js +14 -0
- package/dist/core/shadow-observation-registry.d.ts +217 -0
- package/dist/core/shadow-observation-registry.js +308 -0
- package/dist/core/training-program.d.ts +233 -0
- package/dist/core/training-program.js +433 -0
- package/dist/core/trajectory.d.ts +95 -1
- package/dist/core/trajectory.js +220 -6
- package/dist/core/workspace-context.d.ts +0 -6
- package/dist/core/workspace-context.js +0 -12
- package/dist/hooks/bash-risk.d.ts +6 -6
- package/dist/hooks/bash-risk.js +8 -8
- package/dist/hooks/gate-block-helper.js +1 -1
- package/dist/hooks/gate.d.ts +1 -1
- package/dist/hooks/gate.js +2 -2
- package/dist/hooks/gfi-gate.d.ts +3 -3
- package/dist/hooks/gfi-gate.js +15 -14
- package/dist/hooks/pain.js +6 -9
- package/dist/hooks/progressive-trust-gate.d.ts +21 -49
- package/dist/hooks/progressive-trust-gate.js +51 -204
- package/dist/hooks/prompt.d.ts +11 -11
- package/dist/hooks/prompt.js +158 -72
- package/dist/hooks/subagent.js +43 -6
- package/dist/i18n/commands.js +8 -8
- package/dist/index.js +129 -28
- package/dist/service/evolution-worker.d.ts +42 -4
- package/dist/service/evolution-worker.js +321 -13
- package/dist/service/nocturnal-runtime.d.ts +183 -0
- package/dist/service/nocturnal-runtime.js +352 -0
- package/dist/service/nocturnal-service.d.ts +163 -0
- package/dist/service/nocturnal-service.js +787 -0
- package/dist/service/nocturnal-target-selector.d.ts +145 -0
- package/dist/service/nocturnal-target-selector.js +315 -0
- package/dist/service/phase3-input-filter.d.ts +2 -23
- package/dist/service/phase3-input-filter.js +3 -27
- package/dist/service/runtime-summary-service.d.ts +0 -10
- package/dist/service/runtime-summary-service.js +1 -54
- package/dist/tools/deep-reflect.js +2 -1
- package/dist/types/event-types.d.ts +2 -10
- package/dist/types/runtime-summary.d.ts +1 -8
- package/dist/types.d.ts +0 -3
- package/dist/types.js +0 -2
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/templates/langs/en/skills/pd-mentor/SKILL.md +5 -5
- package/templates/langs/zh/skills/pd-mentor/SKILL.md +5 -5
- package/templates/pain_settings.json +0 -6
- package/dist/commands/trust.d.ts +0 -4
- package/dist/commands/trust.js +0 -78
- package/dist/core/trust-engine.d.ts +0 -96
- package/dist/core/trust-engine.js +0 -286
|
@@ -0,0 +1,671 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Nocturnal Rollout Command Handler
|
|
3
|
+
* =================================
|
|
4
|
+
*
|
|
5
|
+
* Plugin command handler for nocturnal rollout and promotion operations.
|
|
6
|
+
* Provides commands for:
|
|
7
|
+
* - evaluate-promotion: Evaluate if checkpoint passes promotion gate
|
|
8
|
+
* - advance-promotion: Advance checkpoint promotion state
|
|
9
|
+
* - bind: Bind checkpoint to worker profile
|
|
10
|
+
* - enable-routing: Enable routing for a profile
|
|
11
|
+
* - disable-routing: Disable routing for a profile
|
|
12
|
+
* - rollback: Rollback deployment to previous checkpoint
|
|
13
|
+
* - status: Show deployment status for profiles
|
|
14
|
+
* - show-promotion: Show promotion record for a checkpoint
|
|
15
|
+
*
|
|
16
|
+
* Usage:
|
|
17
|
+
* /nocturnal-rollout evaluate-promotion <checkpointId> [--profile=<profile>]
|
|
18
|
+
* /nocturnal-rollout advance-promotion <checkpointId> [--profile=<profile>] [--review]
|
|
19
|
+
* /nocturnal-rollout bind <checkpointId> --profile=<profile>
|
|
20
|
+
* /nocturnal-rollout enable-routing <profile>
|
|
21
|
+
* /nocturnal-rollout disable-routing <profile>
|
|
22
|
+
* /nocturnal-rollout rollback <profile>
|
|
23
|
+
* /nocturnal-rollout status [--profile=<profile>]
|
|
24
|
+
* /nocturnal-rollout show-promotion <checkpointId>
|
|
25
|
+
*/
|
|
26
|
+
import { evaluatePromotionGate, advancePromotion, getPromotionRecord, listPromotionsByState, DEFAULT_BASELINE_METRICS, DEFAULT_MIN_DELTA, DEFAULT_ALLOWED_MARGIN, } from '../core/promotion-gate.js';
|
|
27
|
+
import { bindCheckpointToWorkerProfile, enableRoutingForProfile, disableRoutingForProfile, rollbackDeployment, getDeployment, getDeploymentLineage, isRoutingEnabledForProfile, } from '../core/model-deployment-registry.js';
|
|
28
|
+
import { classifyTask, } from '../core/local-worker-routing.js';
|
|
29
|
+
import { completeShadowObservation, completeShadowObservationByTask, } from '../core/shadow-observation-registry.js';
|
|
30
|
+
import { getCheckpoint, } from '../core/model-training-registry.js';
|
|
31
|
+
function isZh(ctx) {
|
|
32
|
+
return String(ctx.config?.language || 'en').startsWith('zh');
|
|
33
|
+
}
|
|
34
|
+
function parseProfile(arg) {
|
|
35
|
+
if (!arg)
|
|
36
|
+
return 'local-reader';
|
|
37
|
+
if (arg === 'local-reader' || arg === 'local-editor') {
|
|
38
|
+
return arg;
|
|
39
|
+
}
|
|
40
|
+
return 'local-reader';
|
|
41
|
+
}
|
|
42
|
+
function formatPromotionState(state, zh) {
|
|
43
|
+
const map = {
|
|
44
|
+
rejected: { en: 'Rejected', zh: '已拒绝' },
|
|
45
|
+
candidate_only: { en: 'Candidate Only', zh: '仅候选' },
|
|
46
|
+
shadow_ready: { en: 'Shadow Ready', zh: '待 Shadow' },
|
|
47
|
+
promotable: { en: 'Promotable', zh: '可晋升' },
|
|
48
|
+
};
|
|
49
|
+
return map[state][zh ? 'zh' : 'en'];
|
|
50
|
+
}
|
|
51
|
+
function formatConstraintCheck(check, zh) {
|
|
52
|
+
const constraintNames = {
|
|
53
|
+
arbiterRejectRate: zh ? 'Arbiter 拒绝率' : 'Arbiter Reject Rate',
|
|
54
|
+
executabilityRejectRate: zh ? '可执行性拒绝率' : 'Executability Reject Rate',
|
|
55
|
+
reviewedSubsetQuality: zh ? '质量分数' : 'Quality Score',
|
|
56
|
+
};
|
|
57
|
+
const name = constraintNames[check.constraint] || check.constraint;
|
|
58
|
+
const icon = check.passed ? (zh ? '✅' : 'PASS') : (zh ? '❌' : 'FAIL');
|
|
59
|
+
return `${icon} ${name}: ${check.actual.toFixed(4)} (baseline: ${check.baseline.toFixed(4)}, threshold: ${check.threshold.toFixed(4)})`;
|
|
60
|
+
}
|
|
61
|
+
export function handleNocturnalRolloutCommand(ctx) {
|
|
62
|
+
const workspaceDir = ctx.config?.workspaceDir || process.cwd();
|
|
63
|
+
const zh = isZh(ctx);
|
|
64
|
+
const args = (ctx.args || '').trim();
|
|
65
|
+
const parts = args.split(/\s+/).filter(Boolean);
|
|
66
|
+
const [subcommand = 'help'] = parts;
|
|
67
|
+
// Parse common arguments
|
|
68
|
+
const profileArg = parts.find((p) => p.startsWith('--profile='))?.split('=')[1];
|
|
69
|
+
const checkpointIdArg = parts.find((p) => p.startsWith('--checkpoint-id='))?.split('=')[1];
|
|
70
|
+
try {
|
|
71
|
+
// ── Help ────────────────────────────────────────────────────────────────
|
|
72
|
+
if (subcommand === 'help' || subcommand === '--help') {
|
|
73
|
+
return {
|
|
74
|
+
text: zh
|
|
75
|
+
? ` nocturnal-rollout 命令帮助
|
|
76
|
+
|
|
77
|
+
用法:
|
|
78
|
+
/nocturnal-rollout evaluate-promotion <checkpointId> [--profile=<profile>]
|
|
79
|
+
/nocturnal-rollout advance-promotion <checkpointId> [--profile=<profile>] [--review]
|
|
80
|
+
/nocturnal-rollout bind <checkpointId> --profile=<profile>
|
|
81
|
+
/nocturnal-rollout enable-routing <profile>
|
|
82
|
+
/nocturnal-rollout disable-routing <profile>
|
|
83
|
+
/nocturnal-rollout rollback <profile>
|
|
84
|
+
/nocturnal-rollout status [--profile=<profile>]
|
|
85
|
+
/nocturnal-rollout show-promotion <checkpointId>
|
|
86
|
+
|
|
87
|
+
说明:
|
|
88
|
+
evaluate-promotion - 评估检查点是否通过晋升门
|
|
89
|
+
advance-promotion - 推进检查点晋升状态 (需要 --review 标志表示人工审核通过)
|
|
90
|
+
bind - 将检查点绑定到工作机配置文件
|
|
91
|
+
enable-routing - 启用配置文件路由
|
|
92
|
+
disable-routing - 禁用配置文件路由
|
|
93
|
+
rollback - 回滚到上一个检查点
|
|
94
|
+
status - 显示所有配置文件部署状态
|
|
95
|
+
show-promotion - 显示检查点晋升记录
|
|
96
|
+
|
|
97
|
+
配置文件:
|
|
98
|
+
local-reader - 本地阅读器 (Phase 7 首发的唯一配置)
|
|
99
|
+
local-editor - 本地编辑器 (需要显式启用)
|
|
100
|
+
|
|
101
|
+
阶段状态:
|
|
102
|
+
rejected - 检查点不得路由
|
|
103
|
+
candidate_only - 检查点有效但尚未准备好 shadow
|
|
104
|
+
shadow_ready - 检查点可进入受控 shadow 部署
|
|
105
|
+
promotable - 检查点可替换当前活动检查点
|
|
106
|
+
|
|
107
|
+
注意事项:
|
|
108
|
+
- 晋升必须经过: 离线 lineage → eval → promotion gate → shadow rollout
|
|
109
|
+
- local-reader 是 Phase 7 唯一允许的 rollout 目标
|
|
110
|
+
- 路由必须在绑定后显式启用`
|
|
111
|
+
: ` nocturnal-rollout command help
|
|
112
|
+
|
|
113
|
+
Usage:
|
|
114
|
+
/nocturnal-rollout evaluate-promotion <checkpointId> [--profile=<profile>]
|
|
115
|
+
/nocturnal-rollout advance-promotion <checkpointId> [--profile=<profile>] [--review]
|
|
116
|
+
/nocturnal-rollout bind <checkpointId> --profile=<profile>
|
|
117
|
+
/nocturnal-rollout enable-routing <profile>
|
|
118
|
+
/nocturnal-rollout disable-routing <profile>
|
|
119
|
+
/nocturnal-rollout rollback <profile>
|
|
120
|
+
/nocturnal-rollout status [--profile=<profile>]
|
|
121
|
+
/nocturnal-rollout show-promotion <checkpointId>
|
|
122
|
+
|
|
123
|
+
Description:
|
|
124
|
+
evaluate-promotion - Evaluate if checkpoint passes promotion gate
|
|
125
|
+
advance-promotion - Advance checkpoint promotion state (requires --review flag for orchestrator review)
|
|
126
|
+
bind - Bind checkpoint to worker profile
|
|
127
|
+
enable-routing - Enable routing for a profile
|
|
128
|
+
disable-routing - Disable routing for a profile
|
|
129
|
+
rollback - Rollback to previous checkpoint
|
|
130
|
+
status - Show deployment status for all profiles
|
|
131
|
+
show-promotion - Show promotion record for a checkpoint
|
|
132
|
+
|
|
133
|
+
Profiles:
|
|
134
|
+
local-reader - Local reader (only allowed profile for Phase 7)
|
|
135
|
+
local-editor - Local editor (requires explicit enablement)
|
|
136
|
+
|
|
137
|
+
Promotion States:
|
|
138
|
+
rejected - Checkpoint must not be routed
|
|
139
|
+
candidate_only - Checkpoint valid but not ready for shadow
|
|
140
|
+
shadow_ready - Checkpoint may enter controlled shadow deployment
|
|
141
|
+
promotable - Checkpoint may replace current active checkpoint
|
|
142
|
+
|
|
143
|
+
Important:
|
|
144
|
+
- Promotion requires: offline lineage → eval → promotion gate → shadow rollout
|
|
145
|
+
- local-reader is the only allowed rollout target for Phase 7
|
|
146
|
+
- Routing must be explicitly enabled after binding`,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
// ── Evaluate Promotion ────────────────────────────────────────────────
|
|
150
|
+
if (subcommand === 'evaluate-promotion') {
|
|
151
|
+
const checkpointId = parts[1] || checkpointIdArg;
|
|
152
|
+
if (!checkpointId) {
|
|
153
|
+
return { text: zh ? '错误: 需要 checkpointId' : 'Error: checkpointId required' };
|
|
154
|
+
}
|
|
155
|
+
const profile = parseProfile(profileArg);
|
|
156
|
+
const checkpoint = getCheckpoint(workspaceDir, checkpointId);
|
|
157
|
+
if (!checkpoint) {
|
|
158
|
+
return { text: zh ? `错误: Checkpoint 未找到: ${checkpointId}` : `Error: Checkpoint not found: ${checkpointId}` };
|
|
159
|
+
}
|
|
160
|
+
const result = evaluatePromotionGate(workspaceDir, {
|
|
161
|
+
checkpointId,
|
|
162
|
+
targetProfile: profile,
|
|
163
|
+
baselineMetrics: DEFAULT_BASELINE_METRICS,
|
|
164
|
+
minDelta: DEFAULT_MIN_DELTA,
|
|
165
|
+
allowedMargin: DEFAULT_ALLOWED_MARGIN,
|
|
166
|
+
});
|
|
167
|
+
let text = zh
|
|
168
|
+
? `=== 晋升门评估 ===
|
|
169
|
+
Checkpoint: ${checkpointId.substring(0, 8)}...
|
|
170
|
+
Profile: ${profile}
|
|
171
|
+
结果: ${result.passes ? (zh ? '通过' : 'PASS') : (zh ? '未通过' : 'FAIL')}
|
|
172
|
+
建议状态: ${result.suggestedState ? formatPromotionState(result.suggestedState, zh) : 'N/A'}
|
|
173
|
+
|
|
174
|
+
--- Delta 检查 ---
|
|
175
|
+
${result.deltaCheck.passed ? (zh ? '✅' : 'PASS') : (zh ? '❌' : 'FAIL')} Delta: ${result.deltaCheck.actual >= 0 ? '+' : ''}${result.deltaCheck.actual.toFixed(4)} (阈值: ${result.deltaCheck.threshold.toFixed(4)})
|
|
176
|
+
|
|
177
|
+
--- 约束检查 ---`
|
|
178
|
+
: `=== Promotion Gate Evaluation ===
|
|
179
|
+
Checkpoint: ${checkpointId.substring(0, 8)}...
|
|
180
|
+
Profile: ${profile}
|
|
181
|
+
Result: ${result.passes ? 'PASS' : 'FAIL'}
|
|
182
|
+
Suggested State: ${result.suggestedState ? formatPromotionState(result.suggestedState, zh) : 'N/A'}
|
|
183
|
+
|
|
184
|
+
--- Delta Check ---
|
|
185
|
+
${result.deltaCheck.passed ? 'PASS' : 'FAIL'} Delta: ${result.deltaCheck.actual >= 0 ? '+' : ''}${result.deltaCheck.actual.toFixed(4)} (threshold: ${result.deltaCheck.threshold.toFixed(4)})
|
|
186
|
+
|
|
187
|
+
--- Constraint Checks ---`;
|
|
188
|
+
for (const check of result.constraintChecks) {
|
|
189
|
+
text += `\n${formatConstraintCheck(check, zh)}`;
|
|
190
|
+
}
|
|
191
|
+
if (result.blockers.length > 0) {
|
|
192
|
+
text += `\n\n--- Blockers ---`;
|
|
193
|
+
for (const blocker of result.blockers) {
|
|
194
|
+
text += `\n - ${blocker}`;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
text += `\n\n--- 下一步 ---
|
|
198
|
+
${result.passes
|
|
199
|
+
? (zh
|
|
200
|
+
? `1. 推进晋升: /nocturnal-rollout advance-promotion ${checkpointId} --profile=${profile} --review
|
|
201
|
+
2. 绑定部署: /nocturnal-rollout bind ${checkpointId} --profile=${profile}`
|
|
202
|
+
: `1. Advance promotion: /nocturnal-rollout advance-promotion ${checkpointId} --profile=${profile} --review
|
|
203
|
+
2. Bind deployment: /nocturnal-rollout bind ${checkpointId} --profile=${profile}`)
|
|
204
|
+
: (zh
|
|
205
|
+
? `1. 查看 blockers 并修复问题
|
|
206
|
+
2. 重新运行评估前确保满足所有约束`
|
|
207
|
+
: `1. Review blockers and fix issues
|
|
208
|
+
2. Ensure all constraints are met before re-evaluation`)}`;
|
|
209
|
+
return { text };
|
|
210
|
+
}
|
|
211
|
+
// ── Advance Promotion ─────────────────────────────────────────────────
|
|
212
|
+
if (subcommand === 'advance-promotion') {
|
|
213
|
+
const checkpointId = parts[1] || checkpointIdArg;
|
|
214
|
+
if (!checkpointId) {
|
|
215
|
+
return { text: zh ? '错误: 需要 checkpointId' : 'Error: checkpointId required' };
|
|
216
|
+
}
|
|
217
|
+
const profile = parseProfile(profileArg);
|
|
218
|
+
const hasReview = args.includes('--review');
|
|
219
|
+
const noteArg = parts.find((p) => p.startsWith('--note='))?.split('=')[1];
|
|
220
|
+
try {
|
|
221
|
+
const promotion = advancePromotion(workspaceDir, {
|
|
222
|
+
checkpointId,
|
|
223
|
+
targetProfile: profile,
|
|
224
|
+
baselineMetrics: DEFAULT_BASELINE_METRICS,
|
|
225
|
+
orchestratorReviewPassed: hasReview,
|
|
226
|
+
reviewNote: noteArg,
|
|
227
|
+
minDelta: DEFAULT_MIN_DELTA,
|
|
228
|
+
allowedMargin: DEFAULT_ALLOWED_MARGIN,
|
|
229
|
+
});
|
|
230
|
+
return {
|
|
231
|
+
text: zh
|
|
232
|
+
? `✅ 晋升状态已更新
|
|
233
|
+
Checkpoint: ${checkpointId.substring(0, 8)}...
|
|
234
|
+
新状态: ${formatPromotionState(promotion.state, zh)}
|
|
235
|
+
Review: ${promotion.orchestratorReviewPassed ? (zh ? '通过' : 'Passed') : (zh ? '未审核' : 'Not reviewed')}
|
|
236
|
+
Delta: ${promotion.reducedPromptDelta >= 0 ? '+' : ''}${promotion.reducedPromptDelta.toFixed(4)}
|
|
237
|
+
|
|
238
|
+
${promotion.state === 'shadow_ready'
|
|
239
|
+
? (zh ? '⚠️ 进入 shadow 部署。请运行 /nocturnal-rollout bind 完成绑定。' : '⚠️ Ready for shadow deployment. Run /nocturnal-rollout bind to complete binding.')
|
|
240
|
+
: promotion.state === 'promotable'
|
|
241
|
+
? (zh ? '✅ 检查点可晋升到生产环境。' : '✅ Checkpoint is promotable to production.')
|
|
242
|
+
: promotion.state === 'rejected'
|
|
243
|
+
? (zh ? '❌ 检查点被拒绝。' : '❌ Checkpoint was rejected.')
|
|
244
|
+
: (zh ? '等待进一步审核。' : 'Waiting for further review.')}
|
|
245
|
+
${promotion.shadowStartedAt ? `Shadow 开始: ${new Date(promotion.shadowStartedAt).toLocaleString()}` : ''}`
|
|
246
|
+
: `✅ Promotion state updated
|
|
247
|
+
Checkpoint: ${checkpointId.substring(0, 8)}...
|
|
248
|
+
New State: ${formatPromotionState(promotion.state, zh)}
|
|
249
|
+
Review: ${promotion.orchestratorReviewPassed ? 'Passed' : 'Not reviewed'}
|
|
250
|
+
Delta: ${promotion.reducedPromptDelta >= 0 ? '+' : ''}${promotion.reducedPromptDelta.toFixed(4)}
|
|
251
|
+
|
|
252
|
+
${promotion.state === 'shadow_ready'
|
|
253
|
+
? '⚠️ Ready for shadow deployment. Run /nocturnal-rollout bind to complete binding.'
|
|
254
|
+
: promotion.state === 'promotable'
|
|
255
|
+
? '✅ Checkpoint is promotable to production.'
|
|
256
|
+
: promotion.state === 'rejected'
|
|
257
|
+
? '❌ Checkpoint was rejected.'
|
|
258
|
+
: 'Waiting for further review.'}
|
|
259
|
+
${promotion.shadowStartedAt ? `Shadow started: ${new Date(promotion.shadowStartedAt).toLocaleString()}` : ''}`,
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
catch (err) {
|
|
263
|
+
return {
|
|
264
|
+
text: zh
|
|
265
|
+
? `❌ 晋升失败: ${err.message}`
|
|
266
|
+
: `❌ Advance promotion failed: ${err.message}`,
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
// ── Bind ──────────────────────────────────────────────────────────────
|
|
271
|
+
if (subcommand === 'bind') {
|
|
272
|
+
const checkpointId = parts[1] || checkpointIdArg;
|
|
273
|
+
if (!checkpointId) {
|
|
274
|
+
return { text: zh ? '错误: 需要 checkpointId' : 'Error: checkpointId required' };
|
|
275
|
+
}
|
|
276
|
+
const profile = parseProfile(profileArg);
|
|
277
|
+
const noteArg = parts.find((p) => p.startsWith('--note='))?.split('=')[1];
|
|
278
|
+
try {
|
|
279
|
+
const deployment = bindCheckpointToWorkerProfile(workspaceDir, profile, checkpointId, noteArg);
|
|
280
|
+
return {
|
|
281
|
+
text: zh
|
|
282
|
+
? `✅ 部署已绑定
|
|
283
|
+
Profile: ${profile}
|
|
284
|
+
Checkpoint: ${checkpointId.substring(0, 8)}...
|
|
285
|
+
Artifact: ${deployment.targetModelFamily}
|
|
286
|
+
路由启用: ${deployment.routingEnabled ? (zh ? '是' : 'Yes') : (zh ? '否' : 'No')}
|
|
287
|
+
|
|
288
|
+
下一步:
|
|
289
|
+
1. 启用路由: /nocturnal-rollout enable-routing ${profile}
|
|
290
|
+
2. 或保持禁用进行测试`
|
|
291
|
+
: `✅ Deployment bound
|
|
292
|
+
Profile: ${profile}
|
|
293
|
+
Checkpoint: ${checkpointId.substring(0, 8)}...
|
|
294
|
+
Artifact: ${deployment.targetModelFamily}
|
|
295
|
+
Routing Enabled: ${deployment.routingEnabled ? 'Yes' : 'No'}
|
|
296
|
+
|
|
297
|
+
Next steps:
|
|
298
|
+
1. Enable routing: /nocturnal-rollout enable-routing ${profile}
|
|
299
|
+
2. Or keep disabled for testing`,
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
catch (err) {
|
|
303
|
+
return {
|
|
304
|
+
text: zh
|
|
305
|
+
? `❌ 绑定失败: ${err.message}`
|
|
306
|
+
: `❌ Bind failed: ${err.message}`,
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
// ── Enable Routing ────────────────────────────────────────────────────
|
|
311
|
+
if (subcommand === 'enable-routing') {
|
|
312
|
+
const profile = parseProfile(profileArg || parts[1]);
|
|
313
|
+
try {
|
|
314
|
+
const deployment = enableRoutingForProfile(workspaceDir, profile);
|
|
315
|
+
return {
|
|
316
|
+
text: zh
|
|
317
|
+
? `✅ 路由已启用
|
|
318
|
+
Profile: ${profile}
|
|
319
|
+
Checkpoint: ${deployment.activeCheckpointId?.substring(0, 8) || 'none'}...
|
|
320
|
+
路由状态: ${deployment.routingEnabled ? (zh ? '启用' : 'Enabled') : (zh ? '禁用' : 'Disabled')}`
|
|
321
|
+
: `✅ Routing enabled
|
|
322
|
+
Profile: ${profile}
|
|
323
|
+
Checkpoint: ${deployment.activeCheckpointId?.substring(0, 8) || 'none'}...
|
|
324
|
+
Routing: ${deployment.routingEnabled ? 'Enabled' : 'Disabled'}`,
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
catch (err) {
|
|
328
|
+
return {
|
|
329
|
+
text: zh
|
|
330
|
+
? `❌ 启用路由失败: ${err.message}`
|
|
331
|
+
: `❌ Enable routing failed: ${err.message}`,
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
// ── Disable Routing ────────────────────────────────────────────────────
|
|
336
|
+
if (subcommand === 'disable-routing') {
|
|
337
|
+
const profile = parseProfile(profileArg || parts[1]);
|
|
338
|
+
try {
|
|
339
|
+
const deployment = disableRoutingForProfile(workspaceDir, profile);
|
|
340
|
+
return {
|
|
341
|
+
text: zh
|
|
342
|
+
? `✅ 路由已禁用
|
|
343
|
+
Profile: ${profile}
|
|
344
|
+
Checkpoint: ${deployment.activeCheckpointId?.substring(0, 8) || 'none'}...
|
|
345
|
+
路由状态: ${deployment.routingEnabled ? (zh ? '启用' : 'Enabled') : (zh ? '禁用' : 'Disabled')}
|
|
346
|
+
|
|
347
|
+
注意: 禁用路由后,该配置文件的流量将返回主代理。`
|
|
348
|
+
: `✅ Routing disabled
|
|
349
|
+
Profile: ${profile}
|
|
350
|
+
Checkpoint: ${deployment.activeCheckpointId?.substring(0, 8) || 'none'}...
|
|
351
|
+
Routing: ${deployment.routingEnabled ? 'Enabled' : 'Disabled'}
|
|
352
|
+
|
|
353
|
+
Note: After disabling routing, traffic for this profile will return to the main agent.`,
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
catch (err) {
|
|
357
|
+
return {
|
|
358
|
+
text: zh
|
|
359
|
+
? `❌ 禁用路由失败: ${err.message}`
|
|
360
|
+
: `❌ Disable routing failed: ${err.message}`,
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
// ── Rollback ──────────────────────────────────────────────────────────
|
|
365
|
+
if (subcommand === 'rollback') {
|
|
366
|
+
const profile = parseProfile(profileArg || parts[1]);
|
|
367
|
+
const noteArg = parts.find((p) => p.startsWith('--note='))?.split('=')[1];
|
|
368
|
+
try {
|
|
369
|
+
const deployment = rollbackDeployment(workspaceDir, profile, noteArg);
|
|
370
|
+
return {
|
|
371
|
+
text: zh
|
|
372
|
+
? `✅ 已回滚
|
|
373
|
+
Profile: ${profile}
|
|
374
|
+
新 Checkpoint: ${deployment.activeCheckpointId?.substring(0, 8) || 'none'}...
|
|
375
|
+
上一个 Checkpoint: ${deployment.previousCheckpointId?.substring(0, 8) || 'none'}...
|
|
376
|
+
路由状态: ${deployment.routingEnabled ? (zh ? '启用' : 'Enabled') : (zh ? '禁用' : 'Disabled')}`
|
|
377
|
+
: `✅ Rolled back
|
|
378
|
+
Profile: ${profile}
|
|
379
|
+
New Checkpoint: ${deployment.activeCheckpointId?.substring(0, 8) || 'none'}...
|
|
380
|
+
Previous Checkpoint: ${deployment.previousCheckpointId?.substring(0, 8) || 'none'}...
|
|
381
|
+
Routing: ${deployment.routingEnabled ? 'Enabled' : 'Disabled'}`,
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
catch (err) {
|
|
385
|
+
return {
|
|
386
|
+
text: zh
|
|
387
|
+
? `❌ 回滚失败: ${err.message}`
|
|
388
|
+
: `❌ Rollback failed: ${err.message}`,
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
// ── Status ─────────────────────────────────────────────────────────────
|
|
393
|
+
if (subcommand === 'status') {
|
|
394
|
+
const profile = profileArg ? parseProfile(profileArg) : undefined;
|
|
395
|
+
if (profile) {
|
|
396
|
+
const deployment = getDeployment(workspaceDir, profile);
|
|
397
|
+
if (!deployment) {
|
|
398
|
+
return {
|
|
399
|
+
text: zh
|
|
400
|
+
? `Profile ${profile}: 无部署记录`
|
|
401
|
+
: `Profile ${profile}: No deployment record`,
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
const checkpoint = deployment.activeCheckpointId
|
|
405
|
+
? getCheckpoint(workspaceDir, deployment.activeCheckpointId)
|
|
406
|
+
: null;
|
|
407
|
+
const lineage = deployment.activeCheckpointId
|
|
408
|
+
? getDeploymentLineage(workspaceDir, profile)
|
|
409
|
+
: null;
|
|
410
|
+
return {
|
|
411
|
+
text: zh
|
|
412
|
+
? `=== ${profile} 部署状态 ===
|
|
413
|
+
Checkpoint: ${deployment.activeCheckpointId?.substring(0, 8) || 'none'}...
|
|
414
|
+
Deployable: ${checkpoint?.deployable ? (zh ? '是' : 'Yes') : (zh ? '否' : 'No')}
|
|
415
|
+
路由启用: ${deployment.routingEnabled ? (zh ? '是' : 'Yes') : (zh ? '否' : 'No')}
|
|
416
|
+
上次更新: ${new Date(deployment.updatedAt).toLocaleString()}
|
|
417
|
+
${deployment.previousCheckpointId ? `回滚目标: ${deployment.previousCheckpointId.substring(0, 8)}...` : ''}
|
|
418
|
+
${deployment.note ? `备注: ${deployment.note}` : ''}`
|
|
419
|
+
: `=== ${profile} Deployment Status ===
|
|
420
|
+
Checkpoint: ${deployment.activeCheckpointId?.substring(0, 8) || 'none'}...
|
|
421
|
+
Deployable: ${checkpoint?.deployable ? 'Yes' : 'No'}
|
|
422
|
+
Routing Enabled: ${deployment.routingEnabled ? 'Yes' : 'No'}
|
|
423
|
+
Last Updated: ${new Date(deployment.updatedAt).toLocaleString()}
|
|
424
|
+
${deployment.previousCheckpointId ? `Rollback target: ${deployment.previousCheckpointId.substring(0, 8)}...` : ''}
|
|
425
|
+
${deployment.note ? `Note: ${deployment.note}` : ''}`,
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
// Show all profiles
|
|
429
|
+
const profiles = ['local-reader', 'local-editor'];
|
|
430
|
+
let text = zh ? '=== 部署状态 ===\n' : '=== Deployment Status ===\n';
|
|
431
|
+
for (const p of profiles) {
|
|
432
|
+
const d = getDeployment(workspaceDir, p);
|
|
433
|
+
const routing = isRoutingEnabledForProfile(workspaceDir, p);
|
|
434
|
+
text += `\n${p}:
|
|
435
|
+
${d
|
|
436
|
+
? `Checkpoint: ${d.activeCheckpointId?.substring(0, 8) || 'none'}... | Routing: ${routing ? (zh ? '启用' : 'Enabled') : (zh ? '禁用' : 'Disabled')}`
|
|
437
|
+
: (zh ? ' 无部署记录' : ' No deployment')}`;
|
|
438
|
+
}
|
|
439
|
+
return { text };
|
|
440
|
+
}
|
|
441
|
+
// ── Show Promotion ────────────────────────────────────────────────────
|
|
442
|
+
if (subcommand === 'show-promotion') {
|
|
443
|
+
const checkpointId = parts[1] || checkpointIdArg;
|
|
444
|
+
if (!checkpointId) {
|
|
445
|
+
return { text: zh ? '错误: 需要 checkpointId' : 'Error: checkpointId required' };
|
|
446
|
+
}
|
|
447
|
+
const promotion = getPromotionRecord(workspaceDir, checkpointId);
|
|
448
|
+
if (!promotion) {
|
|
449
|
+
return {
|
|
450
|
+
text: zh
|
|
451
|
+
? `未找到晋升记录: ${checkpointId}`
|
|
452
|
+
: `No promotion record found: ${checkpointId}`,
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
return {
|
|
456
|
+
text: zh
|
|
457
|
+
? `=== 晋升记录 ===
|
|
458
|
+
Checkpoint: ${promotion.checkpointId.substring(0, 8)}...
|
|
459
|
+
状态: ${formatPromotionState(promotion.state, zh)}
|
|
460
|
+
Profile: ${promotion.targetProfile}
|
|
461
|
+
Family: ${promotion.targetModelFamily}
|
|
462
|
+
Delta: ${promotion.reducedPromptDelta >= 0 ? '+' : ''}${promotion.reducedPromptDelta.toFixed(4)}
|
|
463
|
+
|
|
464
|
+
约束指标:
|
|
465
|
+
Arbiter 拒绝率: ${promotion.constraintMetrics.arbiterRejectRate.toFixed(4)}
|
|
466
|
+
可执行性拒绝率: ${promotion.constraintMetrics.executabilityRejectRate.toFixed(4)}
|
|
467
|
+
质量分数: ${promotion.constraintMetrics.reviewedSubsetQuality.toFixed(4)}
|
|
468
|
+
|
|
469
|
+
基线:
|
|
470
|
+
Arbiter 拒绝率: ${promotion.baselineMetrics.arbiterRejectRate.toFixed(4)}
|
|
471
|
+
可执行性拒绝率: ${promotion.baselineMetrics.executabilityRejectRate.toFixed(4)}
|
|
472
|
+
质量分数: ${promotion.baselineMetrics.reviewedSubsetQuality.toFixed(4)}
|
|
473
|
+
|
|
474
|
+
审核通过: ${promotion.orchestratorReviewPassed ? (zh ? '是' : 'Yes') : (zh ? '否' : 'No')}
|
|
475
|
+
${promotion.reviewNote ? `审核备注: ${promotion.reviewNote}` : ''}
|
|
476
|
+
创建: ${new Date(promotion.createdAt).toLocaleString()}
|
|
477
|
+
状态变更: ${new Date(promotion.stateChangedAt).toLocaleString()}
|
|
478
|
+
${promotion.shadowStartedAt ? `Shadow 开始: ${new Date(promotion.shadowStartedAt).toLocaleString()}` : ''}
|
|
479
|
+
${promotion.promotableAt ? `可晋升时间: ${new Date(promotion.promotableAt).toLocaleString()}` : ''}
|
|
480
|
+
${promotion.previousPromotionId ? `上一个晋升: ${promotion.previousPromotionId.substring(0, 8)}...` : ''}`
|
|
481
|
+
: `=== Promotion Record ===
|
|
482
|
+
Checkpoint: ${promotion.checkpointId.substring(0, 8)}...
|
|
483
|
+
State: ${formatPromotionState(promotion.state, zh)}
|
|
484
|
+
Profile: ${promotion.targetProfile}
|
|
485
|
+
Family: ${promotion.targetModelFamily}
|
|
486
|
+
Delta: ${promotion.reducedPromptDelta >= 0 ? '+' : ''}${promotion.reducedPromptDelta.toFixed(4)}
|
|
487
|
+
|
|
488
|
+
Constraint Metrics:
|
|
489
|
+
Arbiter Reject Rate: ${promotion.constraintMetrics.arbiterRejectRate.toFixed(4)}
|
|
490
|
+
Executability Reject Rate: ${promotion.constraintMetrics.executabilityRejectRate.toFixed(4)}
|
|
491
|
+
Quality Score: ${promotion.constraintMetrics.reviewedSubsetQuality.toFixed(4)}
|
|
492
|
+
|
|
493
|
+
Baseline:
|
|
494
|
+
Arbiter Reject Rate: ${promotion.baselineMetrics.arbiterRejectRate.toFixed(4)}
|
|
495
|
+
Executability Reject Rate: ${promotion.baselineMetrics.executabilityRejectRate.toFixed(4)}
|
|
496
|
+
Quality Score: ${promotion.baselineMetrics.reviewedSubsetQuality.toFixed(4)}
|
|
497
|
+
|
|
498
|
+
Review Passed: ${promotion.orchestratorReviewPassed ? 'Yes' : 'No'}
|
|
499
|
+
${promotion.reviewNote ? `Review Note: ${promotion.reviewNote}` : ''}
|
|
500
|
+
Created: ${new Date(promotion.createdAt).toLocaleString()}
|
|
501
|
+
State Changed: ${new Date(promotion.stateChangedAt).toLocaleString()}
|
|
502
|
+
${promotion.shadowStartedAt ? `Shadow Started: ${new Date(promotion.shadowStartedAt).toLocaleString()}` : ''}
|
|
503
|
+
${promotion.promotableAt ? `Promotable At: ${new Date(promotion.promotableAt).toLocaleString()}` : ''}
|
|
504
|
+
${promotion.previousPromotionId ? `Previous Promotion: ${promotion.previousPromotionId.substring(0, 8)}...` : ''}`,
|
|
505
|
+
};
|
|
506
|
+
}
|
|
507
|
+
// ── List by State ─────────────────────────────────────────────────────
|
|
508
|
+
if (subcommand === 'list-by-state') {
|
|
509
|
+
const stateArg = parts[1];
|
|
510
|
+
if (!stateArg) {
|
|
511
|
+
return { text: zh ? '错误: 需要状态参数' : 'Error: state argument required' };
|
|
512
|
+
}
|
|
513
|
+
const validStates = ['rejected', 'candidate_only', 'shadow_ready', 'promotable'];
|
|
514
|
+
if (!validStates.includes(stateArg)) {
|
|
515
|
+
return {
|
|
516
|
+
text: zh
|
|
517
|
+
? `无效状态: ${stateArg}。有效值: ${validStates.join(', ')}`
|
|
518
|
+
: `Invalid state: ${stateArg}. Valid values: ${validStates.join(', ')}`,
|
|
519
|
+
};
|
|
520
|
+
}
|
|
521
|
+
const promotions = listPromotionsByState(workspaceDir, stateArg);
|
|
522
|
+
if (promotions.length === 0) {
|
|
523
|
+
return {
|
|
524
|
+
text: zh
|
|
525
|
+
? `没有 ${stateArg} 状态的检查点`
|
|
526
|
+
: `No checkpoints with state ${stateArg}`,
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
const lines = promotions.map((p) => `${p.checkpointId.substring(0, 8)}... | ${p.targetProfile} | ${p.targetModelFamily} | Delta: ${p.reducedPromptDelta >= 0 ? '+' : ''}${p.reducedPromptDelta.toFixed(4)}`);
|
|
530
|
+
return {
|
|
531
|
+
text: zh
|
|
532
|
+
? `${stateArg} 检查点 (${promotions.length}):
|
|
533
|
+
${lines.join('\n')}`
|
|
534
|
+
: `${stateArg} checkpoints (${promotions.length}):
|
|
535
|
+
${lines.join('\n')}`,
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
// ── Route ───────────────────────────────────────────────────────────────
|
|
539
|
+
// Execute a routing decision for a task. Records shadow observation if routing to shadow.
|
|
540
|
+
if (subcommand === 'route') {
|
|
541
|
+
// Parse routing input arguments
|
|
542
|
+
const intentArg = parts.find((p) => p.startsWith('--intent='))?.slice('--intent='.length) ?? '';
|
|
543
|
+
const descriptionArg = parts.find((p) => p.startsWith('--description='))?.slice('--description='.length) ?? '';
|
|
544
|
+
const toolsArg = parts.find((p) => p.startsWith('--tools='))?.slice('--tools='.length) ?? '';
|
|
545
|
+
const filesArg = parts.find((p) => p.startsWith('--files='))?.slice('--files='.length) ?? '';
|
|
546
|
+
const outputArg = parts.find((p) => p.startsWith('--output='))?.slice('--output='.length) ?? '';
|
|
547
|
+
const riskArg = parts.find((p) => p.startsWith('--risk='))?.slice('--risk='.length) ?? '';
|
|
548
|
+
const complexityArg = parts.find((p) => p.startsWith('--complexity='))?.slice('--complexity='.length) ?? '';
|
|
549
|
+
const routingInput = {
|
|
550
|
+
taskIntent: intentArg || undefined,
|
|
551
|
+
taskDescription: descriptionArg || undefined,
|
|
552
|
+
requestedTools: toolsArg ? toolsArg.split(',').map((t) => t.trim()) : undefined,
|
|
553
|
+
requestedFiles: filesArg ? filesArg.split(',').map((f) => f.trim()) : undefined,
|
|
554
|
+
expectedOutputShape: outputArg || undefined,
|
|
555
|
+
riskSignals: riskArg ? riskArg.split(',').map((r) => r.trim()) : undefined,
|
|
556
|
+
complexityHints: complexityArg ? complexityArg.split(',').map((c) => c.trim()) : undefined,
|
|
557
|
+
targetProfile: parseProfile(profileArg),
|
|
558
|
+
};
|
|
559
|
+
const decision = classifyTask(routingInput, workspaceDir);
|
|
560
|
+
const shadowNote = decision.shadowObservationId
|
|
561
|
+
? `\n\n${zh ? '📝 Shadow 观察已记录' : '📝 Shadow observation recorded'}: ${decision.shadowObservationId}`
|
|
562
|
+
: '';
|
|
563
|
+
return {
|
|
564
|
+
text: zh
|
|
565
|
+
? `=== 路由决策 ===
|
|
566
|
+
决策: ${decision.decision === 'route_local' ? '路由到本地' : '保持在主代理'}
|
|
567
|
+
目标配置: ${decision.targetProfile ?? 'N/A'}
|
|
568
|
+
分类: ${decision.classification}
|
|
569
|
+
原因: ${decision.reason}
|
|
570
|
+
${decision.blockers.length > 0 ? `阻塞因素:\n${decision.blockers.map((b) => ' - ' + b).join('\n')}` : ''}
|
|
571
|
+
${decision.activeCheckpointId ? `活动 Checkpoint: ${decision.activeCheckpointId.substring(0, 8)}...` : ''}
|
|
572
|
+
${decision.activeCheckpointState ? `Checkpoint 状态: ${decision.activeCheckpointState}` : ''}
|
|
573
|
+
${decision.decision === 'route_local' && decision.activeCheckpointState === 'shadow_ready' ? (zh ? '⚠️ 正在路由到 shadow 检查点' : '⚠️ Routing to shadow checkpoint') : ''}
|
|
574
|
+
${shadowNote}
|
|
575
|
+
|
|
576
|
+
下一步:
|
|
577
|
+
${decision.decision === 'route_local'
|
|
578
|
+
? (zh ? ' 任务将路由到本地配置文件。任务完成后使用 /nocturnal-rollout complete-shadow 完成 shadow 观察。' : ' Task will be routed to local profile. After task completes, use /nocturnal-rollout complete-shadow to complete the shadow observation.')
|
|
579
|
+
: (zh ? ' 任务必须保留在主代理。' : ' Task must stay on main agent.')}`
|
|
580
|
+
: `=== Routing Decision ===
|
|
581
|
+
Decision: ${decision.decision === 'route_local' ? 'ROUTE_LOCAL' : 'STAY_MAIN'}
|
|
582
|
+
Target Profile: ${decision.targetProfile ?? 'N/A'}
|
|
583
|
+
Classification: ${decision.classification}
|
|
584
|
+
Reason: ${decision.reason}
|
|
585
|
+
${decision.blockers.length > 0 ? `Blockers:\n${decision.blockers.map((b) => ' - ' + b).join('\n')}` : ''}
|
|
586
|
+
${decision.activeCheckpointId ? `Active Checkpoint: ${decision.activeCheckpointId.substring(0, 8)}...` : ''}
|
|
587
|
+
${decision.activeCheckpointState ? `Checkpoint State: ${decision.activeCheckpointState}` : ''}
|
|
588
|
+
${decision.decision === 'route_local' && decision.activeCheckpointState === 'shadow_ready' ? '⚠️ Routing to shadow checkpoint' : ''}
|
|
589
|
+
${shadowNote}
|
|
590
|
+
|
|
591
|
+
Next steps:
|
|
592
|
+
${decision.decision === 'route_local'
|
|
593
|
+
? ' Task will be routed to local profile. After task completes, use /nocturnal-rollout complete-shadow to complete the shadow observation.'
|
|
594
|
+
: ' Task must stay on main agent.'}`,
|
|
595
|
+
};
|
|
596
|
+
}
|
|
597
|
+
// ── Complete Shadow Observation ─────────────────────────────────────────
|
|
598
|
+
if (subcommand === 'complete-shadow') {
|
|
599
|
+
const obsIdArg = parts.find((p) => p.startsWith('--observation-id='))?.split('=')[1];
|
|
600
|
+
const fingerprintArg = parts.find((p) => p.startsWith('--fingerprint='))?.split('=')[1];
|
|
601
|
+
const outcomeArg = parts.find((p) => p.startsWith('--outcome='))?.split('=')[1] ?? 'accepted';
|
|
602
|
+
const timedOutArg = parts.includes('--timed-out');
|
|
603
|
+
const threwArg = parts.includes('--threw');
|
|
604
|
+
const invalidArg = parts.includes('--invalid');
|
|
605
|
+
const rejectedArg = parts.includes('--rejected');
|
|
606
|
+
const validOutcomes = ['accepted', 'rejected', 'escalated'];
|
|
607
|
+
const outcome = validOutcomes.includes(outcomeArg)
|
|
608
|
+
? outcomeArg
|
|
609
|
+
: 'accepted';
|
|
610
|
+
const failureSignals = {
|
|
611
|
+
timedOut: timedOutArg,
|
|
612
|
+
threwException: threwArg,
|
|
613
|
+
invalidOutput: invalidArg,
|
|
614
|
+
profileRejected: rejectedArg,
|
|
615
|
+
extra: {},
|
|
616
|
+
};
|
|
617
|
+
let observation = null;
|
|
618
|
+
if (obsIdArg) {
|
|
619
|
+
observation = completeShadowObservation(workspaceDir, {
|
|
620
|
+
observationId: obsIdArg,
|
|
621
|
+
outcome,
|
|
622
|
+
failureSignals,
|
|
623
|
+
});
|
|
624
|
+
}
|
|
625
|
+
else if (fingerprintArg) {
|
|
626
|
+
// Look up by task fingerprint (recorded during route)
|
|
627
|
+
observation = completeShadowObservationByTask(workspaceDir, fingerprintArg, outcome, failureSignals);
|
|
628
|
+
}
|
|
629
|
+
else {
|
|
630
|
+
return {
|
|
631
|
+
text: zh
|
|
632
|
+
? '错误: 需要 --observation-id 或 --fingerprint 参数'
|
|
633
|
+
: 'Error: --observation-id or --fingerprint is required',
|
|
634
|
+
};
|
|
635
|
+
}
|
|
636
|
+
if (!observation) {
|
|
637
|
+
return {
|
|
638
|
+
text: zh
|
|
639
|
+
? `未找到 shadow 观察。检查 ID 或指纹是否正确。`
|
|
640
|
+
: `Shadow observation not found. Check the ID or fingerprint.`,
|
|
641
|
+
};
|
|
642
|
+
}
|
|
643
|
+
return {
|
|
644
|
+
text: zh
|
|
645
|
+
? `✅ Shadow 观察已完成
|
|
646
|
+
观察 ID: ${observation.observationId.substring(0, 8)}...
|
|
647
|
+
结果: ${observation.outcome}
|
|
648
|
+
Checkpoint: ${observation.checkpointId.substring(0, 8)}...
|
|
649
|
+
完成时间: ${observation.completedAt}`
|
|
650
|
+
: `✅ Shadow observation completed
|
|
651
|
+
Observation ID: ${observation.observationId.substring(0, 8)}...
|
|
652
|
+
Outcome: ${observation.outcome}
|
|
653
|
+
Checkpoint: ${observation.checkpointId.substring(0, 8)}...
|
|
654
|
+
Completed At: ${observation.completedAt}`,
|
|
655
|
+
};
|
|
656
|
+
}
|
|
657
|
+
// Unknown subcommand
|
|
658
|
+
return {
|
|
659
|
+
text: zh
|
|
660
|
+
? `未知子命令: ${subcommand}。运行 /nocturnal-rollout help 查看帮助。`
|
|
661
|
+
: `Unknown subcommand: ${subcommand}. Run /nocturnal-rollout help for usage.`,
|
|
662
|
+
};
|
|
663
|
+
}
|
|
664
|
+
catch (err) {
|
|
665
|
+
return {
|
|
666
|
+
text: zh
|
|
667
|
+
? `❌ 命令失败: ${err.message}`
|
|
668
|
+
: `❌ Command failed: ${err.message}`,
|
|
669
|
+
};
|
|
670
|
+
}
|
|
671
|
+
}
|