@kasenri/dsh-orbit 0.5.6 → 0.5.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/lib/supervisor.js CHANGED
@@ -1,70 +1,88 @@
1
1
  import { join } from 'node:path';
2
2
  import { randomUUID } from 'node:crypto';
3
- import { buildEvidenceBundle, formatEvidenceBundle } from "./evidence.js";
3
+ import { buildEvidenceBundle, buildStepResult, formatStepResults, formatEvidenceBundle } from "./evidence.js";
4
4
  import { assertCommanderDecision, assertStrategyDecision, assertTimeoutDecision, assertWatchdogDecision, assertGuardWatchdogDecision, COMMANDER_FINAL_EVALUATE_SCHEMA, COMMANDER_PLAN_SCHEMA, COMMANDER_STEP_EVALUATE_SCHEMA, COMMANDER_STRATEGY_SCHEMA, WATCHDOG_GUARD_SCHEMA, WATCHDOG_RUNTIME_SCHEMA, WATCHDOG_STRATEGY_SCHEMA, WATCHDOG_TIMEOUT_SCHEMA, } from "./decisions.js";
5
- import { applyCommanderNeedsUser, applyCorrectionStep, applyExecutorCapabilityUnavailable, applyExecutorInterrupted, applyExecutorResume, applyExecutorSuccess, applyFinalAppend, applyFinalSuccess, applyPlan, applyStepPass, baseStepIdOf, beginStep, clearExecutorChild, clearGuardRecovery, correctionBlockCode, correctionDepthOf, createInitialState, enterBudgetExhausted, enterNeedsUser, markStrategyChallengeUsed, normalizePlan, openWatchdogAttempt, recordGuardRecovery, recordPlanFailure, recordWatchdogDecision, restoreEvaluationState, resumeFromNeedsUser, stopRun, } from "./kernel.js";
5
+ import { applyCommanderNeedsUser, applyCorrectionStep, applyExecutorCapabilityUnavailable, applyExecutorInterrupted, applyExecutorResume, applyExecutorSuccess, applyFinalAppend, applyFinalSuccess, applyPlan, applyStepPass, baseStepIdOf, beginStep, clearExecutorChild, clearGuardRecovery, correctionBlockCode, correctionDepthOf, createInitialState, enterBudgetExhausted, enterNeedsUser, markStrategyChallengeUsed, normalizePlan, openWatchdogAttempt, recordGuardRecovery, recordPlanFailure, recordWatchdogDecision, restoreEvaluationState, resumeFromNeedsUser, stopRun, upsertStepResult, normalizeCapabilities, } from "./kernel.js";
6
6
  import { truncateSafe } from "./sanitize.js";
7
7
  import { OrbitStateStore } from "./state-store.js";
8
+ import { executorToolsFor, EXECUTOR_READ_ONLY_TOOLS, READ_ONLY_ROLE_TOOLS } from "./capabilities.js";
8
9
  import { COMMANDER_EXTENSION_MS, COMMANDER_HARD_CEILING_MS, COMMANDER_SOFT_DEADLINE_MS, DEFAULT_CAPABILITIES, EXECUTOR_TIMEOUT_MS, GUARD_ESCALATION_THRESHOLD, GUARD_FIRST_INSTRUCTION, GUARD_NEEDS_USER_INSTRUCTION, GUARD_RECOVERY_CAP, GUARD_REPEAT_INSTRUCTION, GUARD_RETRY_INSTRUCTION, MAX_EXECUTOR_INTERRUPT_RETRIES, MAX_WATCHDOG_CALLS_PER_STEP, WATCHDOG_TIMEOUT_MS, } from "./types.js";
9
- const COMMANDER_PLAN_PROMPT = (goal, constraints, userReply) => `You are the Orbit Commander. Produce the smallest set of 2-5 logical engineering steps for this goal.
10
- Rules: ordinary engineering steps must omit capabilities. Add capability "browser" only when the step must drive a real web page, and "web-api-recon" when it must analyze captured network/API traffic. Keep it minimal.
11
- Submit your final plan through the structured result protocol.
12
- Goal: ${goal}
13
- Hard constraints: ${constraints.join('; ') || 'none'}${userReply}`;
14
- const COMMANDER_STEP_PROMPT = (goal, step, evidence, state) => `You are the Orbit Commander doing STEP_EVALUATE. Verify the real project state; do not trust the Executor claim alone. Treat verified execution evidence as authoritative.
15
- Allowed decisions ONLY: PASS_CURRENT_STEP | CORRECT_CURRENT_STEP | NEEDS_USER.
16
- - CORRECT_CURRENT_STEP requires a concrete next step goal (optional capabilities).
17
- Submit your final judgment through the structured result protocol.
18
- Original goal: ${goal}
19
- Current step ${step.id}: ${step.goal}
20
- Iteration counters: loop ${state.loop.used}/${state.loop.max}${userReplyLine(state)}
21
- Executor claim:
10
+ import { resolveEffectiveRoutes } from "./routes.js";
11
+ const COMMANDER_PLAN_PROMPT = (goal, constraints, userReply) => `你是 Orbit 指挥官(Commander),当前阶段:PLAN。
12
+ 请为下述目标制定最小化的 1-5 个逻辑工程步骤。
13
+ 规则:基础读取不声明 capabilities;修改文件使用 "filesystem",执行命令使用 "shell",访问网页/API 使用 "web",驱动真实浏览器使用 "browser"。只选择当前步骤真正需要的能力,可组合,保持最小化。
14
+ 请通过结构化结果协议提交最终计划。
15
+ 你的自然语言输出、推理说明和总结默认全部使用简体中文;decision 枚举、capability id、代码、命令、路径、provider/model ID 等机器标识保持原样。
16
+ 目标:${goal}
17
+ 硬性约束:${constraints.join(';') || '无'}${userReply}`;
18
+ const COMMANDER_STEP_PROMPT = (goal, step, evidence, state) => `你是 Orbit 指挥官(Commander),当前阶段:STEP_EVALUATE。
19
+ 请核实真实项目状态,不要只相信执行员的说法;以可验证的执行证据为准。
20
+ 允许的 decision 仅限:PASS_CURRENT_STEP | CORRECT_CURRENT_STEP | NEEDS_USER。
21
+ - CORRECT_CURRENT_STEP 必须给出具体的下一步目标(可选 capabilities)。
22
+ 请通过结构化结果协议提交最终判断。
23
+ 你的自然语言输出、推理说明和总结默认全部使用简体中文;decision 枚举、代码、命令、路径、provider/model ID 等机器标识保持原样。
24
+ 原始目标:${goal}
25
+ 当前步骤 ${step.id}:${step.goal}
26
+ 迭代计数:loop ${state.loop.used}/${state.loop.max}${userReplyLine(state)}
27
+ 执行员证据:
22
28
  ${evidence}`;
23
- const COMMANDER_FINAL_PROMPT = (goal, plan, evidence, state) => `You are the Orbit Commander doing FINAL_EVALUATE. All planned steps are done. Decide whether the original goal is truly satisfied against the real project state.
24
- Allowed decisions ONLY: SUCCESS | APPEND | NEEDS_USER.
25
- - APPEND requires next steps or a next step goal; appends are bounded by the remaining loop budget.
26
- Submit your final judgment through the structured result protocol.
27
- Original goal: ${goal}
28
- Plan summary: ${plan.summary}
29
- Steps: ${plan.steps.map((step) => `${step.id}:${step.goal}[${step.status}]`).join('; ')}
30
- Loop: ${state.loop.used}/${state.loop.max}${userReplyLine(state)}
31
- Executor claim:
29
+ const COMMANDER_FINAL_PROMPT = (goal, plan, evidence, state) => `你是 Orbit 指挥官(Commander),当前阶段:FINAL_EVALUATE。
30
+ 所有计划步骤均已执行完毕。请依据真实项目状态判断原始目标是否真正达成。
31
+ 允许的 decision 仅限:SUCCESS | APPEND | NEEDS_USER。
32
+ - APPEND 必须给出新增步骤或下一步目标;追加受剩余 loop 预算限制。
33
+ 请通过结构化结果协议提交最终判断。
34
+ 你的自然语言输出、推理说明和总结默认全部使用简体中文;decision 枚举、代码、命令、路径、provider/model ID 等机器标识保持原样。
35
+ 原始目标:${goal}
36
+ 计划摘要:${plan.summary}
37
+ 步骤:${plan.steps.map((step) => `${step.id}:${step.goal}[${step.status}]`).join('; ')}
38
+ Loop:${state.loop.used}/${state.loop.max}${userReplyLine(state)}
39
+ 执行员证据:
32
40
  ${evidence}`;
33
41
  /**
34
42
  * The durable user reply line for role prompts. The reply is the user's answer
35
43
  * to a NEEDS_USER question and never replaces the original goal.
36
44
  */
37
45
  function userReplyLine(state) {
38
- return state.pending_user_reply ? `\nUser reply (the user's answer to the previous question): ${state.pending_user_reply}` : '';
46
+ return state.pending_user_reply ? `\n用户回复(对上一个问题的回答):${state.pending_user_reply}` : '';
39
47
  }
40
- const COMMANDER_STRATEGY_PROMPT = (goal, base, challenge, state) => `You are the Orbit Commander reconsidering strategy after a repeated correction on ${base} (STRATEGY_RECONSIDER).
41
- Allowed decisions ONLY: KEEP_APPROACH | REPLACE_CURRENT_STEP | NEEDS_USER.
42
- - REPLACE_CURRENT_STEP requires a replacement goal.
43
- Submit your final judgment through the structured result protocol.
44
- Goal: ${goal}
45
- Loop: ${state.loop.used}/${state.loop.max}
46
- Watchdog challenge: ${challenge}`;
47
- const WATCHDOG_RUNTIME_PROMPT = (step, reason, telemetry) => `You are the Orbit Smart Watchdog doing RUNTIME_DIAGNOSE. Diagnose only the current runtime anomaly. Do not review code quality.
48
- Allowed decisions ONLY: RESUME_CHILD | RESTART_STEP | NEEDS_USER | RUNTIME_BUG.
49
- Submit your final judgment through the structured result protocol.
50
- Failed step ${step.id}: ${step.goal}
51
- Runtime anomaly: ${reason}
52
- Telemetry: ${JSON.stringify(telemetry ?? {})}`;
53
- const WATCHDOG_STRATEGY_PROMPT = (step, reason, state) => `You are the Orbit Smart Watchdog doing STRATEGY_CHALLENGE. Ask: is the current approach tunnel vision? Is this blocker truly required? Is there a simpler route?
54
- Submit one focused challenge question through the structured result protocol.
55
- Step ${step.id}: ${step.goal}
56
- Repeated correction: ${reason}
57
- Loop: ${state.loop.used}/${state.loop.max}`;
58
- const WATCHDOG_GUARD_PROMPT = (code, count, stepId) => `You are the Orbit Smart Watchdog doing GUARD_ESCALATION. A safety guard blocked a tool ${count} times.
59
- Allowed decisions ONLY: RETRY_DIFFERENTLY | NEEDS_USER.
60
- Submit your final judgment through the structured result protocol.
61
- Guard code: ${code}
62
- Step: ${stepId}`;
63
- const WATCHDOG_TIMEOUT_PROMPT = (mode, elapsed, extensions, telemetry) => `You are the Orbit Smart Watchdog doing COMMANDER_TIMEOUT_REVIEW. The Commander has run ${elapsed}ms with ${extensions} extension(s).
64
- Allowed decisions ONLY: EXTEND | INTERRUPT | NEEDS_USER.
65
- Submit your final judgment through the structured result protocol.
66
- Mode: ${mode}
67
- Telemetry: ${JSON.stringify(telemetry ?? {})}`;
48
+ const COMMANDER_STRATEGY_PROMPT = (goal, base, challenge, state) => `你是 Orbit 指挥官(Commander),当前阶段:STRATEGY_RECONSIDER。
49
+ 步骤 ${base} 已连续修正多次,请重新审视当前策略。
50
+ 允许的 decision 仅限:KEEP_APPROACH | REPLACE_CURRENT_STEP | NEEDS_USER。
51
+ - REPLACE_CURRENT_STEP 必须给出替代目标。
52
+ 请通过结构化结果协议提交最终判断。
53
+ 你的自然语言输出、推理说明和总结默认全部使用简体中文;decision 枚举、代码、命令、路径、provider/model ID 等机器标识保持原样。
54
+ 目标:${goal}
55
+ Loop:${state.loop.used}/${state.loop.max}
56
+ 监控模型质疑:${challenge}`;
57
+ const WATCHDOG_RUNTIME_PROMPT = (step, reason, telemetry) => `你是 Orbit 监控模型(Smart Watchdog),当前阶段:RUNTIME_DIAGNOSE。
58
+ 只诊断当前运行时异常,不评审代码质量。
59
+ 允许的 decision 仅限:RESUME_CHILD | RESTART_STEP | NEEDS_USER | RUNTIME_BUG。
60
+ 请通过结构化结果协议提交最终判断。
61
+ 你的自然语言输出、推理说明和总结默认全部使用简体中文;decision 枚举、代码、命令、路径、provider/model ID 等机器标识保持原样。
62
+ 失败步骤 ${step.id}:${step.goal}
63
+ 运行时异常:${reason}
64
+ 遥测:${JSON.stringify(telemetry ?? {})}`;
65
+ const WATCHDOG_STRATEGY_PROMPT = (step, reason, state) => `你是 Orbit 监控模型(Smart Watchdog),当前阶段:STRATEGY_CHALLENGE。
66
+ 请质疑:当前思路是否陷入隧道视野?这个阻塞是否真的必要?是否存在更简单的路径?
67
+ 请通过结构化结果协议提交一个聚焦的质疑问题。
68
+ 你的自然语言输出默认使用简体中文;decision 枚举、代码、命令、路径、provider/model ID 等机器标识保持原样。
69
+ 步骤 ${step.id}:${step.goal}
70
+ 重复修正:${reason}
71
+ Loop:${state.loop.used}/${state.loop.max}`;
72
+ const WATCHDOG_GUARD_PROMPT = (code, count, stepId) => `你是 Orbit 监控模型(Smart Watchdog),当前阶段:GUARD_ESCALATION。
73
+ 安全护栏已连续 ${count} 次拦截某个工具调用。
74
+ 允许的 decision 仅限:RETRY_DIFFERENTLY | NEEDS_USER。
75
+ 请通过结构化结果协议提交最终判断。
76
+ 你的自然语言输出默认使用简体中文;decision 枚举、代码、命令、路径、provider/model ID 等机器标识保持原样。
77
+ 护栏代码:${code}
78
+ 步骤:${stepId}`;
79
+ const WATCHDOG_TIMEOUT_PROMPT = (mode, elapsed, extensions, telemetry) => `你是 Orbit 监控模型(Smart Watchdog),当前阶段:COMMANDER_TIMEOUT_REVIEW。
80
+ 指挥官已运行 ${elapsed}ms,期间延长 ${extensions} 次。
81
+ 允许的 decision 仅限:EXTEND | INTERRUPT | NEEDS_USER。
82
+ 请通过结构化结果协议提交最终判断。
83
+ 你的自然语言输出默认使用简体中文;decision 枚举、代码、命令、路径、provider/model ID 等机器标识保持原样。
84
+ 模式:${mode}
85
+ 遥测:${JSON.stringify(telemetry ?? {})}`;
68
86
  export class OrbitSupervisor {
69
87
  store;
70
88
  host;
@@ -80,40 +98,108 @@ export class OrbitSupervisor {
80
98
  return this.host.now();
81
99
  }
82
100
  createState(input) {
101
+ const ownerSessionId = this.config.resolveOwnerSessionId?.();
83
102
  return createInitialState({
84
103
  runId: typeof input.run_id === 'string' && input.run_id.length > 0 ? input.run_id : randomUUID(),
85
104
  now: this.now(),
86
105
  goal: (input.goal ?? '').trim(),
87
106
  ...(input.preset !== undefined ? { preset: input.preset } : {}),
88
- routes: this.config.resolveRoutes?.() ?? this.config.defaultRoutes,
107
+ routes: this.resolveNewRoutes(),
89
108
  ...(input.approved_loop_count !== undefined ? { approvedLoopCount: input.approved_loop_count } : {}),
90
109
  ...(input.max_loops !== undefined ? { maxLoops: input.max_loops } : {}),
91
110
  ...(input.user_hard_constraints ? { userHardConstraints: input.user_hard_constraints } : {}),
92
111
  githubAllowed: input.github_allowed === true,
112
+ ...(ownerSessionId === undefined ? {} : { ownerSessionId }),
93
113
  });
94
114
  }
115
+ resolveNewRoutes() {
116
+ if (this.config.resolveRoutes)
117
+ return this.config.resolveRoutes();
118
+ return resolveEffectiveRoutes({ configRoutes: this.config.defaultRoutes });
119
+ }
120
+ async preflightRoutes(routes, signal) {
121
+ const issues = await this.host.validateRoutes(routes, signal);
122
+ return issues.length === 0 ? undefined : `ORBIT_ROLE_MODEL_UNAVAILABLE: ${issues.join(';')}。请重新选择可用模型。`;
123
+ }
95
124
  async bootstrap(input, signal) {
125
+ if (signal?.aborted)
126
+ return { ok: false, action: 'run', message: 'ORBIT_ABORTED' };
127
+ const competitors = await this.host.otherMutationDrivers(join(this.store.stateDir, '..'));
128
+ if (competitors.length > 0)
129
+ return {
130
+ ok: false, action: 'run', message: `ORBIT_MUTATION_DRIVER_CONFLICT: ${competitors.join(', ')} 已持有此 workspace 的修改权。`,
131
+ };
96
132
  const requestedGoal = (input.goal ?? '').trim();
97
133
  let state = this.store.readState();
98
134
  const raw = this.store.readRawState();
99
135
  const legacy = raw !== null && raw['schema_version'] !== 2;
100
136
  if (legacy && requestedGoal) {
101
- state = this.store.writeState(this.createState(input));
137
+ let created;
138
+ try {
139
+ created = this.createState(input);
140
+ }
141
+ catch (error) {
142
+ return { ok: false, action: 'run', message: error instanceof Error ? error.message : String(error) };
143
+ }
144
+ const invalid = await this.preflightRoutes(created.routes, signal);
145
+ if (invalid)
146
+ return { ok: false, action: 'run', message: invalid };
147
+ state = this.store.writeState(created);
102
148
  }
103
149
  else if (!state) {
104
150
  if (!requestedGoal)
105
- return { ok: false, action: 'run', message: 'ORBIT_GOAL_REQUIRED: provide a goal to start a run.' };
106
- state = this.store.writeState(this.createState(input));
151
+ return { ok: false, action: 'run', message: 'ORBIT_GOAL_REQUIRED: 请提供要执行的目标。' };
152
+ let created;
153
+ try {
154
+ created = this.createState(input);
155
+ }
156
+ catch (error) {
157
+ return { ok: false, action: 'run', message: error instanceof Error ? error.message : String(error) };
158
+ }
159
+ const invalid = await this.preflightRoutes(created.routes, signal);
160
+ if (invalid)
161
+ return { ok: false, action: 'run', message: invalid };
162
+ state = this.store.writeState(created);
107
163
  }
108
164
  else if (input.run_id && input.run_id !== state.run_id && !legacy) {
109
165
  return { ok: false, action: 'run', message: `ORBIT_RUN_NOT_FOUND: ${input.run_id}` };
110
166
  }
111
167
  if (['SUCCESS', 'STOPPED', 'BUDGET_EXHAUSTED'].includes(state.phase) && requestedGoal) {
112
- state = this.store.writeState(this.createState(input));
168
+ let created;
169
+ try {
170
+ created = this.createState(input);
171
+ }
172
+ catch (error) {
173
+ return { ok: false, action: 'run', message: error instanceof Error ? error.message : String(error) };
174
+ }
175
+ const invalid = await this.preflightRoutes(created.routes, signal);
176
+ if (invalid)
177
+ return { ok: false, action: 'run', message: invalid };
178
+ state = this.store.writeState(created);
113
179
  }
114
180
  if (state.phase === 'NEEDS_USER' && requestedGoal) {
115
181
  // A reply to the Commander's question is not a new goal: keep the run,
116
- // its original goal and its frozen routes, and carry the reply durably.
182
+ // its original goal and its frozen routes, and carry the reply durably —
183
+ // but only from the Session that owns the run. Another Session's message
184
+ // must never be consumed as this run's reply.
185
+ const owner = state.owner_session_id;
186
+ const incoming = this.config.resolveOwnerSessionId?.();
187
+ if (owner === undefined) {
188
+ return {
189
+ ok: false, action: 'run', run_id: state.run_id, phase: state.phase,
190
+ message: `ORBIT_NEEDS_USER_OWNER_UNKNOWN: 这是升级前遗留 Run ${state.run_id},无法安全判断所属 Session。请显式 resume,或 stop 后重新开始。`,
191
+ };
192
+ }
193
+ if (incoming === undefined || incoming !== owner) {
194
+ return {
195
+ ok: false,
196
+ action: 'run',
197
+ run_id: state.run_id,
198
+ phase: state.phase,
199
+ message: `ORBIT_NEEDS_USER_OTHER_SESSION: Run ${state.run_id}(goal: ${truncateSafe(state.goal, 120)})仍在等待所属 Session 的用户回复;` +
200
+ '当前消息未被当作回复。请在原 Session 回复,或显式 stop/resume。',
201
+ };
202
+ }
117
203
  resumeFromNeedsUser(state, requestedGoal);
118
204
  this.store.writeState(state);
119
205
  }
@@ -122,12 +208,12 @@ export class OrbitSupervisor {
122
208
  ok: false,
123
209
  action: 'run',
124
210
  run_id: state.run_id,
125
- message: 'ORBIT_ACTIVE_RUN_EXISTS: current Lite run owns this project; resume it or stop it before starting a different goal.',
211
+ message: 'ORBIT_ACTIVE_RUN_EXISTS: 当前项目已有活动中的 Orbit Run,请先继续或停止该 Run。',
126
212
  };
127
213
  }
128
214
  return this.run(state, signal);
129
215
  }
130
- async run(state, signal) {
216
+ async run(state, signal, preflight = true) {
131
217
  if (signal?.aborted)
132
218
  return this.result(state, false, 'ORBIT_ABORTED');
133
219
  const projectDir = join(this.store.stateDir, '..');
@@ -139,7 +225,7 @@ export class OrbitSupervisor {
139
225
  run_id: state.run_id,
140
226
  phase: state.phase,
141
227
  status: state.status,
142
- message: `ORBIT_MUTATION_DRIVER_CONFLICT: ${competitors.join(', ')} already owns mutation in this workspace.`,
228
+ message: `ORBIT_MUTATION_DRIVER_CONFLICT: ${competitors.join(', ')} 已持有此 workspace 的修改权。`,
143
229
  };
144
230
  }
145
231
  if (state.phase === 'NEEDS_USER')
@@ -150,6 +236,11 @@ export class OrbitSupervisor {
150
236
  return this.result(state, true);
151
237
  if (state.phase === 'BUDGET_EXHAUSTED')
152
238
  return this.result(state, true);
239
+ if (preflight) {
240
+ const routeIssue = await this.preflightRoutes(state.routes, signal);
241
+ if (routeIssue)
242
+ return this.result(state, false, routeIssue);
243
+ }
153
244
  if (state.plan.steps.length === 0 && state.phase === 'PLAN') {
154
245
  const planOutcome = await this.makePlan(state, signal);
155
246
  if (planOutcome)
@@ -166,7 +257,7 @@ export class OrbitSupervisor {
166
257
  const applied = await this.applyCommanderOutcome(state, outcome, step, false, signal);
167
258
  if (applied)
168
259
  return applied;
169
- return this.run(state, signal);
260
+ return this.run(state, signal, false);
170
261
  }
171
262
  const step = state.plan.steps.find((candidate) => candidate.status === 'running') ??
172
263
  state.plan.steps.find((candidate) => candidate.status === 'pending');
@@ -175,7 +266,7 @@ export class OrbitSupervisor {
175
266
  const applied = await this.applyCommanderOutcome(state, outcome, undefined, true, signal);
176
267
  if (applied)
177
268
  return applied;
178
- return this.run(state, signal);
269
+ return this.run(state, signal, false);
179
270
  }
180
271
  if (state.loop.used >= state.loop.max) {
181
272
  enterBudgetExhausted(state, 'LOOP_BUDGET_EXHAUSTED');
@@ -187,7 +278,7 @@ export class OrbitSupervisor {
187
278
  const executed = await this.executeStep(state, step, signal);
188
279
  if (executed.done)
189
280
  return this.result(state, executed.ok, executed.message);
190
- return this.run(state, signal);
281
+ return this.run(state, signal, false);
191
282
  }
192
283
  // ── tool scoping ───────────────────────────────────────────────────────────
193
284
  /**
@@ -197,7 +288,7 @@ export class OrbitSupervisor {
197
288
  toolAllow(names, label) {
198
289
  const allowed = names.filter((name) => this.host.hasTool(name));
199
290
  if (allowed.length === 0) {
200
- throw new Error(`ORBIT_TOOL_FILTER_EMPTY: none of [${names.join(', ')}] are registered for ${label}`);
291
+ throw new Error(`ORBIT_TOOL_FILTER_EMPTY: ${label} 没有任何已注册的允许工具:[${names.join(', ')}]`);
201
292
  }
202
293
  return { allow: allowed };
203
294
  }
@@ -226,7 +317,7 @@ export class OrbitSupervisor {
226
317
  }
227
318
  async commanderEvaluate(state, step, final, signal) {
228
319
  const mode = final ? 'FINAL_EVALUATE' : 'STEP_EVALUATE';
229
- const evidence = this.evidenceFor(state, step) ?? state.commander?.summary ?? state.last_error ?? 'no evidence recorded';
320
+ const evidence = this.evidenceFor(state, step, final) ?? state.commander?.summary ?? state.last_error ?? '未记录执行证据';
230
321
  const prompt = final
231
322
  ? COMMANDER_FINAL_PROMPT(state.goal, state.plan, evidence, state)
232
323
  : COMMANDER_STEP_PROMPT(state.goal, step, evidence, state);
@@ -249,11 +340,13 @@ export class OrbitSupervisor {
249
340
  * qualifies; after a cold resume the bundle is gone and the caller falls back
250
341
  * to the durable state summary.
251
342
  */
252
- evidenceFor(state, step) {
343
+ evidenceFor(state, step, final = false) {
344
+ if (final)
345
+ return formatStepResults(state);
253
346
  const stepId = step?.id ?? state.current_step?.id;
254
- if (!this.stepEvidence || this.stepEvidence.stepId !== stepId)
255
- return undefined;
256
- return formatEvidenceBundle(this.stepEvidence.bundle);
347
+ if (this.stepEvidence && this.stepEvidence.stepId === stepId)
348
+ return formatEvidenceBundle(this.stepEvidence.bundle);
349
+ return state.step_results?.find((entry) => entry.step_id === stepId)?.evidence;
257
350
  }
258
351
  /**
259
352
  * The single adaptive Commander runner for PLAN, STEP_EVALUATE, FINAL_EVALUATE
@@ -270,7 +363,8 @@ export class OrbitSupervisor {
270
363
  label: `commander-${mode.toLowerCase()}`,
271
364
  prompt,
272
365
  route: state.routes.commander,
273
- toolFilter: this.toolAllow(this.config.commanderReadOnlyTools, `commander ${mode}`),
366
+ workspace: join(this.store.stateDir, '..'),
367
+ toolFilter: this.toolAllow(this.config.commanderReadOnlyTools.filter((name) => READ_ONLY_ROLE_TOOLS.includes(name)), `commander ${mode}`),
274
368
  outputSchema,
275
369
  ...(signal ? { signal } : {}),
276
370
  });
@@ -329,27 +423,29 @@ export class OrbitSupervisor {
329
423
  ...(signal ? { signal } : {}),
330
424
  });
331
425
  if (!result || result.interrupted) {
332
- return { decision: extensions === 0 ? 'EXTEND' : 'INTERRUPT', reason: 'Smart Watchdog unavailable' };
426
+ return { decision: extensions === 0 ? 'EXTEND' : 'INTERRUPT', reason: '监控模型暂时不可用' };
333
427
  }
334
428
  try {
335
429
  return assertTimeoutDecision(result.structured);
336
430
  }
337
431
  catch {
338
- return { decision: extensions === 0 ? 'EXTEND' : 'INTERRUPT', reason: 'Smart Watchdog invalid output' };
432
+ return { decision: extensions === 0 ? 'EXTEND' : 'INTERRUPT', reason: '监控模型返回了无效结果' };
339
433
  }
340
434
  }
341
435
  // ── executor runtime ───────────────────────────────────────────────────────
342
436
  async executeStep(state, step, signal) {
343
- const capabilities = step.capabilities ?? [];
344
- if (capabilities.includes('browser') && !this.host.hasTool(this.config.browserTools[0] ?? 'agent_browser')) {
437
+ const capabilities = normalizeCapabilities(step.capabilities) ?? [];
438
+ if (capabilities.includes('browser') && !this.config.browserTools.some((tool) => this.host.hasTool(tool))) {
345
439
  applyExecutorCapabilityUnavailable(state, step.id);
440
+ upsertStepResult(state, buildStepResult(step.id, state.current_step?.attempt ?? 1, buildEvidenceBundle({
441
+ executorOutput: state.commander?.summary,
442
+ })));
346
443
  this.store.writeState(state);
347
444
  return { done: false, ok: false };
348
445
  }
349
446
  let toolFilter;
350
447
  try {
351
- const capabilityTools = capabilities.includes('browser') ? [...this.config.browserTools] : [];
352
- toolFilter = this.toolAllow([...this.config.executorTools, ...capabilityTools], `executor ${step.id}`);
448
+ toolFilter = this.toolAllow(executorToolsFor(capabilities, this.config.browserTools, this.config.executorTools), `executor ${step.id}`);
353
449
  }
354
450
  catch (error) {
355
451
  const reason = truncateSafe(error instanceof Error ? error.message : String(error), 500);
@@ -364,6 +460,7 @@ export class OrbitSupervisor {
364
460
  label: `executor-${step.id}`,
365
461
  prompt: this.executorPrompt(state, step),
366
462
  route: state.routes.executor,
463
+ workspace: join(this.store.stateDir, '..'),
367
464
  toolFilter,
368
465
  capabilities,
369
466
  ...(signal ? { signal } : {}),
@@ -431,6 +528,7 @@ export class OrbitSupervisor {
431
528
  telemetry: result.telemetry,
432
529
  }),
433
530
  };
531
+ upsertStepResult(state, buildStepResult(step.id, state.current_step?.attempt ?? 1, this.stepEvidence.bundle, state.test_summary));
434
532
  this.store.writeState(state);
435
533
  await this.disposeHandle(handle);
436
534
  return { done: false, ok: false };
@@ -441,6 +539,7 @@ export class OrbitSupervisor {
441
539
  const raced = await this.raceWithSleep(handle.result, timeoutMs, signal);
442
540
  if (raced.kind === 'work')
443
541
  return raced.value;
542
+ await this.cancelHandle(handle, raced.kind === 'aborted' ? 'ORBIT_ABORTED' : 'EXECUTOR_TIMEOUT');
444
543
  const telemetry = await handle.runtimeSnapshot?.();
445
544
  return {
446
545
  ...(handle.childId ? { childId: handle.childId } : {}),
@@ -452,16 +551,19 @@ export class OrbitSupervisor {
452
551
  }
453
552
  executorPrompt(state, step) {
454
553
  const lines = [
455
- 'You are the Orbit Executor. Implement exactly the current step with real tools and real verification.',
456
- `Current step ${step.id}: ${step.goal}`,
457
- `Working directory: ${join(this.store.stateDir, '..')}`,
458
- `Hard constraints: ${state.user_hard_constraints.join('; ') || 'none'}`,
554
+ '你是 Orbit 执行员(Executor)。',
555
+ '你只负责执行当前步骤:不要重新规划整个任务,也不要自行改变当前步骤的目标。',
556
+ '必须使用真实工具完成实际操作,并对结果进行真实验证。',
557
+ '完成后用简体中文提交简洁的执行证据:做了什么、运行了哪些命令/测试、验证结果以及仍存在的风险。',
558
+ '你的自然语言输出、执行说明和总结默认全部使用简体中文;代码、命令、路径、provider/model ID 等机器标识保持原样。',
559
+ `当前步骤 ${step.id}:${step.goal}`,
560
+ `工作目录:${join(this.store.stateDir, '..')}`,
561
+ `硬性约束:${state.user_hard_constraints.join(';') || '无'}`,
459
562
  ];
460
563
  if (state.pending_user_reply)
461
- lines.push(`User reply (the user's answer to the previous question): ${state.pending_user_reply}`);
564
+ lines.push(`用户回复(对上一个问题的回答):${state.pending_user_reply}`);
462
565
  if ((step.capabilities ?? []).length > 0)
463
- lines.push(`Capabilities: ${(step.capabilities ?? []).join(', ')}`);
464
- lines.push('Return a compact evidence summary: what changed, commands/tests run, and residual risks.');
566
+ lines.push(`能力:${(step.capabilities ?? []).join(', ')}`);
465
567
  return lines.join('\n');
466
568
  }
467
569
  // ── decision application ───────────────────────────────────────────────────
@@ -497,7 +599,7 @@ export class OrbitSupervisor {
497
599
  if (decision.decision === 'APPEND') {
498
600
  const append = applyFinalAppend(state, decision);
499
601
  if (append === 'invalid') {
500
- return this.setNeedsUser(state, 'COMMANDER_EVALUATION_OUTPUT_INVALID: append needs next_steps or next_step_goal');
602
+ return this.setNeedsUser(state, 'COMMANDER_EVALUATION_OUTPUT_INVALID: APPEND 需要 next_steps 或 next_step_goal');
501
603
  }
502
604
  this.store.writeState(state);
503
605
  if (append === 'budget_exhausted')
@@ -514,7 +616,7 @@ export class OrbitSupervisor {
514
616
  }
515
617
  async applyCorrection(state, step, decision, signal) {
516
618
  if (!step || !decision.next_step_goal) {
517
- return this.setNeedsUser(state, 'COMMANDER_EVALUATION_OUTPUT_INVALID: correction needs next_step_goal');
619
+ return this.setNeedsUser(state, 'COMMANDER_EVALUATION_OUTPUT_INVALID: CORRECT_CURRENT_STEP 需要 next_step_goal');
518
620
  }
519
621
  const base = baseStepIdOf(step.id);
520
622
  const correctionDepth = correctionDepthOf(step.id);
@@ -559,7 +661,7 @@ export class OrbitSupervisor {
559
661
  const challengeValue = challengeResult.structured;
560
662
  const challenge = typeof challengeValue.question === 'string' ? challengeValue.question.trim() : '';
561
663
  if (!challenge)
562
- return { kind: 'interrupted', reason: 'SMART_WATCHDOG_STRATEGY_OUTPUT_INVALID: question is required' };
664
+ return { kind: 'interrupted', reason: 'SMART_WATCHDOG_STRATEGY_OUTPUT_INVALID: 缺少 question' };
563
665
  const outcome = await this.runCommander(state, 'STRATEGY_RECONSIDER', COMMANDER_STRATEGY_PROMPT(state.goal, base, challenge, state), COMMANDER_STRATEGY_SCHEMA, signal);
564
666
  if (outcome.kind === 'needs_user')
565
667
  return { kind: 'needs_user', reason: outcome.reason };
@@ -674,7 +776,9 @@ export class OrbitSupervisor {
674
776
  * even on interruption or timeout.
675
777
  */
676
778
  async runAuxRole(state, request) {
677
- const names = request.role === 'watchdog' ? this.config.watchdogTools : this.config.commanderReadOnlyTools;
779
+ const configured = request.role === 'watchdog' ? this.config.watchdogTools : this.config.commanderReadOnlyTools;
780
+ const readOnly = request.role === 'watchdog' ? EXECUTOR_READ_ONLY_TOOLS : READ_ONLY_ROLE_TOOLS;
781
+ const names = configured.filter((name) => readOnly.includes(name));
678
782
  let handle;
679
783
  try {
680
784
  handle = await this.host.startRole({
@@ -682,6 +786,7 @@ export class OrbitSupervisor {
682
786
  label: request.label,
683
787
  prompt: request.prompt,
684
788
  route: state.routes[request.role],
789
+ workspace: join(this.store.stateDir, '..'),
685
790
  toolFilter: this.toolAllow(names, request.role),
686
791
  ...(request.outputSchema ? { outputSchema: request.outputSchema } : {}),
687
792
  ...(request.signal ? { signal: request.signal } : {}),
@@ -755,7 +860,7 @@ export class OrbitSupervisor {
755
860
  stop(action, runId) {
756
861
  const state = this.store.readState();
757
862
  if (!state)
758
- return { ok: false, action, message: 'ORBIT_RUN_NOT_FOUND: no active run.' };
863
+ return { ok: false, action, message: 'ORBIT_RUN_NOT_FOUND: 没有活动中的 Run。' };
759
864
  if (runId && runId !== state.run_id)
760
865
  return { ok: false, action, message: `ORBIT_RUN_NOT_FOUND: ${runId}` };
761
866
  stopRun(state);
@@ -765,7 +870,7 @@ export class OrbitSupervisor {
765
870
  async status() {
766
871
  const state = this.store.readState();
767
872
  if (!state)
768
- return { ok: false, action: 'status', message: 'ORBIT_RUN_NOT_FOUND: no run state.' };
873
+ return { ok: false, action: 'status', message: 'ORBIT_RUN_NOT_FOUND: 没有 Run 状态。' };
769
874
  return this.result(state, true);
770
875
  }
771
876
  result(state, ok, message) {
@@ -785,6 +890,7 @@ export class OrbitSupervisor {
785
890
  guard_recovery: state.guard_recovery,
786
891
  last_error: state.last_error,
787
892
  pending_user_reply: state.pending_user_reply,
893
+ step_results: state.step_results,
788
894
  changed_files: state.changed_files,
789
895
  test_summary: state.test_summary,
790
896
  driver_ownership: state.driver_ownership,
package/lib/tool.js CHANGED
@@ -2,11 +2,10 @@ import { defineTool } from '@deepseek-ai/dsh-tools';
2
2
  export const ORBIT_TOOL_NAME = 'orbit_controller';
3
3
  /** Legacy tool name kept as a backward-compatible alias. */
4
4
  export const LEGACY_CX_TOOL_NAME = 'cx_controller';
5
- const TOOL_DESCRIPTION = 'Drive Orbit engineering autonomy for the current project. Orbit runs a deterministic Supervisor ' +
6
- '(Commander -> Executor -> Smart Watchdog) over a durable .cx/state.json. Use action "run" with a ' +
7
- 'goal to start or continue, "resume" to continue a persisted run, "status" to inspect, "stop" to ' +
8
- 'close the run, and "doctor" to check the environment. Only Orbit writes .cx durable state.';
9
- const LEGACY_TOOL_DESCRIPTION = `Legacy compatibility alias. Prefer ${ORBIT_TOOL_NAME}. ${TOOL_DESCRIPTION}`;
5
+ const TOOL_DESCRIPTION = '驱动当前项目的 Orbit 工程编排。Orbit 由确定性的 Supervisor 控制 Commander、Executor 和 Smart Watchdog,' +
6
+ '并将状态保存到 .cx/state.json。使用 action "run" 启动或继续,"resume" 继续持久化运行,"status" 查看状态,' +
7
+ '"stop" 关闭运行,"doctor" 检查环境。只有 Orbit 可以写入 .cx 持久状态。';
8
+ const LEGACY_TOOL_DESCRIPTION = `Legacy compatibility alias(兼容旧接口),请优先使用 ${ORBIT_TOOL_NAME}。${TOOL_DESCRIPTION}`;
10
9
  function summarize(result) {
11
10
  const lines = [`orbit ${result.action}: ok=${result.ok} phase=${result.phase ?? '-'} status=${result.status ?? '-'}`];
12
11
  if (result.run_id)
@@ -37,12 +36,12 @@ export function createOrbitTool(ctx, options = {}) {
37
36
  description: legacy ? LEGACY_TOOL_DESCRIPTION : TOOL_DESCRIPTION,
38
37
  parameters: {
39
38
  action: { type: 'string', required: true, enum: ['run', 'start', 'resume', 'stop', 'status', 'doctor'] },
40
- goal: { type: 'string', description: 'The engineering goal (required for run/start).' },
41
- preset: { type: 'string', description: 'Run preset id.' },
42
- approved_loop_count: { type: 'integer', description: 'Explicit loop budget (positive, <= 10).' },
43
- run_id: { type: 'string', description: 'Target run id for resume/stop.' },
39
+ goal: { type: 'string', description: '工程目标,run/start 时必填。' },
40
+ preset: { type: 'string', description: 'Run preset id。' },
41
+ approved_loop_count: { type: 'integer', description: '用户显式批准的 loop 预算,正整数且不超过 10。' },
42
+ run_id: { type: 'string', description: 'resume/stop 的目标 run id。' },
44
43
  user_hard_constraints: { type: 'array', items: { type: 'string' } },
45
- github_allowed: { type: 'boolean', description: 'Allow GitHub remote writes for this run.' },
44
+ github_allowed: { type: 'boolean', description: '是否允许此 Run 执行 GitHub 远程写入。' },
46
45
  },
47
46
  output: {
48
47
  schema: { type: 'json' },
@@ -71,7 +70,7 @@ export function createOrbitTool(ctx, options = {}) {
71
70
  if (args.action === 'status')
72
71
  return (await service.status(cwd));
73
72
  if (args.action === 'stop')
74
- return service.stop(args.run_id, cwd);
73
+ return (await service.stop(args.run_id, cwd));
75
74
  if (args.action === 'doctor')
76
75
  return (await service.doctor(cwd));
77
76
  if (args.action === 'resume')
package/lib/types.js CHANGED
@@ -12,8 +12,9 @@ export const MAX_WATCHDOG_CALLS_PER_STEP = 2;
12
12
  export const MAX_EXECUTOR_INTERRUPT_RETRIES = 2;
13
13
  export const MAX_PLAN_STEPS = 5;
14
14
  export const MIN_PLAN_STEPS = 1;
15
- export const GUARD_FIRST_INSTRUCTION = 'Use a safer method and continue the current task. Do not retry the same blocked operation unchanged.';
16
- export const GUARD_REPEAT_INSTRUCTION = 'The same blocked operation was attempted again. Stop repeating it and choose a different safe approach.';
17
- export const GUARD_RETRY_INSTRUCTION = 'The previous approach repeatedly hit Orbit safety guards. Use a different safe approach. Do not retry the blocked operation.';
18
- export const GUARD_NEEDS_USER_INSTRUCTION = 'This restricted action appears necessary for the user goal. Orbit has paused for user guidance.';
19
- export const DEFAULT_CAPABILITIES = ['browser', 'web-api-recon'];
15
+ export const DEFAULT_LOOP_BUDGET = 5;
16
+ export const GUARD_FIRST_INSTRUCTION = '请改用更安全的方法继续当前任务,不要原样重试刚被阻断的操作。';
17
+ export const GUARD_REPEAT_INSTRUCTION = '同一操作再次被阻断。请停止重复,并选择不同的安全方案。';
18
+ export const GUARD_RETRY_INSTRUCTION = '之前的方案反复触发 Orbit 安全护栏。请改用不同的安全方案,不要重试被阻断的操作。';
19
+ export const GUARD_NEEDS_USER_INSTRUCTION = '受限操作可能是完成目标所必需的。Orbit 已暂停并等待用户指引。';
20
+ export const DEFAULT_CAPABILITIES = ['filesystem', 'shell', 'web', 'browser'];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kasenri/dsh-orbit",
3
- "version": "0.5.6",
3
+ "version": "0.5.8",
4
4
  "type": "module",
5
5
  "description": "Deterministic engineering orchestration for DeepSeek Harness with Commander, Executor, Smart Watchdog, bounded recovery and durable execution state.",
6
6
  "license": "MIT",