@kasenri/dsh-orbit 0.5.7 → 0.5.9

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,14 +1,16 @@
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";
10
+ import { resolveEffectiveRoutes } from "./routes.js";
9
11
  const COMMANDER_PLAN_PROMPT = (goal, constraints, userReply) => `你是 Orbit 指挥官(Commander),当前阶段:PLAN。
10
- 请为下述目标制定最小化的 2-5 个逻辑工程步骤。
11
- 规则:普通工程步骤不要声明 capabilities;仅当该步骤必须驱动真实网页时才添加 capability "browser",仅当必须分析抓取到的网络/API 流量时才添加 "web-api-recon"。保持最小化。
12
+ 请为下述目标制定最小化的 1-5 个逻辑工程步骤。
13
+ 规则:基础读取不声明 capabilities;修改文件使用 "filesystem",执行命令使用 "shell",访问网页/API 使用 "web",驱动真实浏览器使用 "browser"。只选择当前步骤真正需要的能力,可组合,保持最小化。
12
14
  请通过结构化结果协议提交最终计划。
13
15
  你的自然语言输出、推理说明和总结默认全部使用简体中文;decision 枚举、capability id、代码、命令、路径、provider/model ID 等机器标识保持原样。
14
16
  目标:${goal}
@@ -96,40 +98,108 @@ export class OrbitSupervisor {
96
98
  return this.host.now();
97
99
  }
98
100
  createState(input) {
101
+ const ownerSessionId = this.config.resolveOwnerSessionId?.();
99
102
  return createInitialState({
100
103
  runId: typeof input.run_id === 'string' && input.run_id.length > 0 ? input.run_id : randomUUID(),
101
104
  now: this.now(),
102
105
  goal: (input.goal ?? '').trim(),
103
106
  ...(input.preset !== undefined ? { preset: input.preset } : {}),
104
- routes: this.config.resolveRoutes?.() ?? this.config.defaultRoutes,
107
+ routes: this.resolveNewRoutes(),
105
108
  ...(input.approved_loop_count !== undefined ? { approvedLoopCount: input.approved_loop_count } : {}),
106
109
  ...(input.max_loops !== undefined ? { maxLoops: input.max_loops } : {}),
107
110
  ...(input.user_hard_constraints ? { userHardConstraints: input.user_hard_constraints } : {}),
108
111
  githubAllowed: input.github_allowed === true,
112
+ ...(ownerSessionId === undefined ? {} : { ownerSessionId }),
109
113
  });
110
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
+ }
111
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
+ };
112
132
  const requestedGoal = (input.goal ?? '').trim();
113
133
  let state = this.store.readState();
114
134
  const raw = this.store.readRawState();
115
135
  const legacy = raw !== null && raw['schema_version'] !== 2;
116
136
  if (legacy && requestedGoal) {
117
- 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);
118
148
  }
119
149
  else if (!state) {
120
150
  if (!requestedGoal)
121
- return { ok: false, action: 'run', message: 'ORBIT_GOAL_REQUIRED: provide a goal to start a run.' };
122
- 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);
123
163
  }
124
164
  else if (input.run_id && input.run_id !== state.run_id && !legacy) {
125
165
  return { ok: false, action: 'run', message: `ORBIT_RUN_NOT_FOUND: ${input.run_id}` };
126
166
  }
127
167
  if (['SUCCESS', 'STOPPED', 'BUDGET_EXHAUSTED'].includes(state.phase) && requestedGoal) {
128
- 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);
129
179
  }
130
180
  if (state.phase === 'NEEDS_USER' && requestedGoal) {
131
181
  // A reply to the Commander's question is not a new goal: keep the run,
132
- // 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
+ }
133
203
  resumeFromNeedsUser(state, requestedGoal);
134
204
  this.store.writeState(state);
135
205
  }
@@ -138,12 +208,12 @@ export class OrbitSupervisor {
138
208
  ok: false,
139
209
  action: 'run',
140
210
  run_id: state.run_id,
141
- 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。',
142
212
  };
143
213
  }
144
214
  return this.run(state, signal);
145
215
  }
146
- async run(state, signal) {
216
+ async run(state, signal, preflight = true) {
147
217
  if (signal?.aborted)
148
218
  return this.result(state, false, 'ORBIT_ABORTED');
149
219
  const projectDir = join(this.store.stateDir, '..');
@@ -155,7 +225,7 @@ export class OrbitSupervisor {
155
225
  run_id: state.run_id,
156
226
  phase: state.phase,
157
227
  status: state.status,
158
- message: `ORBIT_MUTATION_DRIVER_CONFLICT: ${competitors.join(', ')} already owns mutation in this workspace.`,
228
+ message: `ORBIT_MUTATION_DRIVER_CONFLICT: ${competitors.join(', ')} 已持有此 workspace 的修改权。`,
159
229
  };
160
230
  }
161
231
  if (state.phase === 'NEEDS_USER')
@@ -166,6 +236,11 @@ export class OrbitSupervisor {
166
236
  return this.result(state, true);
167
237
  if (state.phase === 'BUDGET_EXHAUSTED')
168
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
+ }
169
244
  if (state.plan.steps.length === 0 && state.phase === 'PLAN') {
170
245
  const planOutcome = await this.makePlan(state, signal);
171
246
  if (planOutcome)
@@ -182,7 +257,7 @@ export class OrbitSupervisor {
182
257
  const applied = await this.applyCommanderOutcome(state, outcome, step, false, signal);
183
258
  if (applied)
184
259
  return applied;
185
- return this.run(state, signal);
260
+ return this.run(state, signal, false);
186
261
  }
187
262
  const step = state.plan.steps.find((candidate) => candidate.status === 'running') ??
188
263
  state.plan.steps.find((candidate) => candidate.status === 'pending');
@@ -191,7 +266,7 @@ export class OrbitSupervisor {
191
266
  const applied = await this.applyCommanderOutcome(state, outcome, undefined, true, signal);
192
267
  if (applied)
193
268
  return applied;
194
- return this.run(state, signal);
269
+ return this.run(state, signal, false);
195
270
  }
196
271
  if (state.loop.used >= state.loop.max) {
197
272
  enterBudgetExhausted(state, 'LOOP_BUDGET_EXHAUSTED');
@@ -203,7 +278,7 @@ export class OrbitSupervisor {
203
278
  const executed = await this.executeStep(state, step, signal);
204
279
  if (executed.done)
205
280
  return this.result(state, executed.ok, executed.message);
206
- return this.run(state, signal);
281
+ return this.run(state, signal, false);
207
282
  }
208
283
  // ── tool scoping ───────────────────────────────────────────────────────────
209
284
  /**
@@ -213,7 +288,7 @@ export class OrbitSupervisor {
213
288
  toolAllow(names, label) {
214
289
  const allowed = names.filter((name) => this.host.hasTool(name));
215
290
  if (allowed.length === 0) {
216
- 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(', ')}]`);
217
292
  }
218
293
  return { allow: allowed };
219
294
  }
@@ -242,7 +317,7 @@ export class OrbitSupervisor {
242
317
  }
243
318
  async commanderEvaluate(state, step, final, signal) {
244
319
  const mode = final ? 'FINAL_EVALUATE' : 'STEP_EVALUATE';
245
- 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 ?? '未记录执行证据';
246
321
  const prompt = final
247
322
  ? COMMANDER_FINAL_PROMPT(state.goal, state.plan, evidence, state)
248
323
  : COMMANDER_STEP_PROMPT(state.goal, step, evidence, state);
@@ -253,7 +328,7 @@ export class OrbitSupervisor {
253
328
  return { kind: 'interrupted', reason: outcome.reason };
254
329
  try {
255
330
  const decision = assertCommanderDecision(outcome.structured, mode);
256
- return { kind: 'decision', decision };
331
+ return { kind: 'decision', decision, output: outcome.output };
257
332
  }
258
333
  catch (error) {
259
334
  // Invalid/temporary output is recoverable; it must not become NEEDS_USER.
@@ -265,11 +340,13 @@ export class OrbitSupervisor {
265
340
  * qualifies; after a cold resume the bundle is gone and the caller falls back
266
341
  * to the durable state summary.
267
342
  */
268
- evidenceFor(state, step) {
343
+ evidenceFor(state, step, final = false) {
344
+ if (final)
345
+ return formatStepResults(state);
269
346
  const stepId = step?.id ?? state.current_step?.id;
270
- if (!this.stepEvidence || this.stepEvidence.stepId !== stepId)
271
- return undefined;
272
- 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;
273
350
  }
274
351
  /**
275
352
  * The single adaptive Commander runner for PLAN, STEP_EVALUATE, FINAL_EVALUATE
@@ -286,7 +363,8 @@ export class OrbitSupervisor {
286
363
  label: `commander-${mode.toLowerCase()}`,
287
364
  prompt,
288
365
  route: state.routes.commander,
289
- 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}`),
290
368
  outputSchema,
291
369
  ...(signal ? { signal } : {}),
292
370
  });
@@ -309,7 +387,7 @@ export class OrbitSupervisor {
309
387
  if (raced.value.structured === undefined) {
310
388
  return { kind: 'interrupted', reason: `${mode}_STRUCTURED_OUTPUT_MISSING` };
311
389
  }
312
- return { kind: 'output', output: raced.value.output, structured: raced.value.structured };
390
+ return { kind: 'output', output: raced.value.visibleOutput ?? raced.value.output, structured: raced.value.structured };
313
391
  }
314
392
  if (raced.kind === 'aborted' || signal?.aborted) {
315
393
  await this.cancelHandle(handle, 'ORBIT_ABORTED');
@@ -345,27 +423,29 @@ export class OrbitSupervisor {
345
423
  ...(signal ? { signal } : {}),
346
424
  });
347
425
  if (!result || result.interrupted) {
348
- return { decision: extensions === 0 ? 'EXTEND' : 'INTERRUPT', reason: 'Smart Watchdog unavailable' };
426
+ return { decision: extensions === 0 ? 'EXTEND' : 'INTERRUPT', reason: '监控模型暂时不可用' };
349
427
  }
350
428
  try {
351
429
  return assertTimeoutDecision(result.structured);
352
430
  }
353
431
  catch {
354
- return { decision: extensions === 0 ? 'EXTEND' : 'INTERRUPT', reason: 'Smart Watchdog invalid output' };
432
+ return { decision: extensions === 0 ? 'EXTEND' : 'INTERRUPT', reason: '监控模型返回了无效结果' };
355
433
  }
356
434
  }
357
435
  // ── executor runtime ───────────────────────────────────────────────────────
358
436
  async executeStep(state, step, signal) {
359
- const capabilities = step.capabilities ?? [];
360
- 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))) {
361
439
  applyExecutorCapabilityUnavailable(state, step.id);
440
+ upsertStepResult(state, buildStepResult(step.id, state.current_step?.attempt ?? 1, buildEvidenceBundle({
441
+ executorOutput: state.commander?.summary,
442
+ })));
362
443
  this.store.writeState(state);
363
444
  return { done: false, ok: false };
364
445
  }
365
446
  let toolFilter;
366
447
  try {
367
- const capabilityTools = capabilities.includes('browser') ? [...this.config.browserTools] : [];
368
- 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}`);
369
449
  }
370
450
  catch (error) {
371
451
  const reason = truncateSafe(error instanceof Error ? error.message : String(error), 500);
@@ -380,6 +460,7 @@ export class OrbitSupervisor {
380
460
  label: `executor-${step.id}`,
381
461
  prompt: this.executorPrompt(state, step),
382
462
  route: state.routes.executor,
463
+ workspace: join(this.store.stateDir, '..'),
383
464
  toolFilter,
384
465
  capabilities,
385
466
  ...(signal ? { signal } : {}),
@@ -447,6 +528,7 @@ export class OrbitSupervisor {
447
528
  telemetry: result.telemetry,
448
529
  }),
449
530
  };
531
+ upsertStepResult(state, buildStepResult(step.id, state.current_step?.attempt ?? 1, this.stepEvidence.bundle, state.test_summary));
450
532
  this.store.writeState(state);
451
533
  await this.disposeHandle(handle);
452
534
  return { done: false, ok: false };
@@ -457,6 +539,7 @@ export class OrbitSupervisor {
457
539
  const raced = await this.raceWithSleep(handle.result, timeoutMs, signal);
458
540
  if (raced.kind === 'work')
459
541
  return raced.value;
542
+ await this.cancelHandle(handle, raced.kind === 'aborted' ? 'ORBIT_ABORTED' : 'EXECUTOR_TIMEOUT');
460
543
  const telemetry = await handle.runtimeSnapshot?.();
461
544
  return {
462
545
  ...(handle.childId ? { childId: handle.childId } : {}),
@@ -480,7 +563,7 @@ export class OrbitSupervisor {
480
563
  if (state.pending_user_reply)
481
564
  lines.push(`用户回复(对上一个问题的回答):${state.pending_user_reply}`);
482
565
  if ((step.capabilities ?? []).length > 0)
483
- lines.push(`Capabilities: ${(step.capabilities ?? []).join(', ')}`);
566
+ lines.push(`能力:${(step.capabilities ?? []).join(', ')}`);
484
567
  return lines.join('\n');
485
568
  }
486
569
  // ── decision application ───────────────────────────────────────────────────
@@ -503,7 +586,7 @@ export class OrbitSupervisor {
503
586
  if (decision.decision === 'SUCCESS') {
504
587
  applyFinalSuccess(state, decision.summary);
505
588
  this.store.writeState(state);
506
- return this.result(state, true);
589
+ return this.result(state, true, undefined, outcome.output);
507
590
  }
508
591
  if (decision.decision === 'PASS_CURRENT_STEP') {
509
592
  applyStepPass(state, step, decision.summary);
@@ -516,7 +599,7 @@ export class OrbitSupervisor {
516
599
  if (decision.decision === 'APPEND') {
517
600
  const append = applyFinalAppend(state, decision);
518
601
  if (append === 'invalid') {
519
- 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');
520
603
  }
521
604
  this.store.writeState(state);
522
605
  if (append === 'budget_exhausted')
@@ -533,7 +616,7 @@ export class OrbitSupervisor {
533
616
  }
534
617
  async applyCorrection(state, step, decision, signal) {
535
618
  if (!step || !decision.next_step_goal) {
536
- 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');
537
620
  }
538
621
  const base = baseStepIdOf(step.id);
539
622
  const correctionDepth = correctionDepthOf(step.id);
@@ -578,7 +661,7 @@ export class OrbitSupervisor {
578
661
  const challengeValue = challengeResult.structured;
579
662
  const challenge = typeof challengeValue.question === 'string' ? challengeValue.question.trim() : '';
580
663
  if (!challenge)
581
- return { kind: 'interrupted', reason: 'SMART_WATCHDOG_STRATEGY_OUTPUT_INVALID: question is required' };
664
+ return { kind: 'interrupted', reason: 'SMART_WATCHDOG_STRATEGY_OUTPUT_INVALID: 缺少 question' };
582
665
  const outcome = await this.runCommander(state, 'STRATEGY_RECONSIDER', COMMANDER_STRATEGY_PROMPT(state.goal, base, challenge, state), COMMANDER_STRATEGY_SCHEMA, signal);
583
666
  if (outcome.kind === 'needs_user')
584
667
  return { kind: 'needs_user', reason: outcome.reason };
@@ -693,7 +776,9 @@ export class OrbitSupervisor {
693
776
  * even on interruption or timeout.
694
777
  */
695
778
  async runAuxRole(state, request) {
696
- 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));
697
782
  let handle;
698
783
  try {
699
784
  handle = await this.host.startRole({
@@ -701,6 +786,7 @@ export class OrbitSupervisor {
701
786
  label: request.label,
702
787
  prompt: request.prompt,
703
788
  route: state.routes[request.role],
789
+ workspace: join(this.store.stateDir, '..'),
704
790
  toolFilter: this.toolAllow(names, request.role),
705
791
  ...(request.outputSchema ? { outputSchema: request.outputSchema } : {}),
706
792
  ...(request.signal ? { signal: request.signal } : {}),
@@ -774,7 +860,7 @@ export class OrbitSupervisor {
774
860
  stop(action, runId) {
775
861
  const state = this.store.readState();
776
862
  if (!state)
777
- return { ok: false, action, message: 'ORBIT_RUN_NOT_FOUND: no active run.' };
863
+ return { ok: false, action, message: 'ORBIT_RUN_NOT_FOUND: 没有活动中的 Run。' };
778
864
  if (runId && runId !== state.run_id)
779
865
  return { ok: false, action, message: `ORBIT_RUN_NOT_FOUND: ${runId}` };
780
866
  stopRun(state);
@@ -784,10 +870,10 @@ export class OrbitSupervisor {
784
870
  async status() {
785
871
  const state = this.store.readState();
786
872
  if (!state)
787
- 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 状态。' };
788
874
  return this.result(state, true);
789
875
  }
790
- result(state, ok, message) {
876
+ result(state, ok, message, finalOutput) {
791
877
  // Tool output must be lossless JSON: optional state fields are omitted, not
792
878
  // emitted as `undefined`.
793
879
  const data = {
@@ -804,11 +890,13 @@ export class OrbitSupervisor {
804
890
  guard_recovery: state.guard_recovery,
805
891
  last_error: state.last_error,
806
892
  pending_user_reply: state.pending_user_reply,
893
+ step_results: state.step_results,
807
894
  changed_files: state.changed_files,
808
895
  test_summary: state.test_summary,
809
896
  driver_ownership: state.driver_ownership,
810
897
  state_revision: state.state_revision,
811
898
  };
899
+ const displayText = state.phase === 'SUCCESS' ? finalOutput?.trim() || state.commander?.summary?.trim() : undefined;
812
900
  return {
813
901
  ok,
814
902
  action: 'run',
@@ -816,6 +904,13 @@ export class OrbitSupervisor {
816
904
  phase: state.phase,
817
905
  status: state.status,
818
906
  ...(message ? { message } : {}),
907
+ ...(displayText ? {
908
+ final_output: {
909
+ text: displayText,
910
+ provider: state.routes.commander.provider,
911
+ model: state.routes.commander.model,
912
+ },
913
+ } : {}),
819
914
  data: Object.fromEntries(Object.entries(data).filter(([, value]) => value !== undefined)),
820
915
  };
821
916
  }
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.7",
3
+ "version": "0.5.9",
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",