@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/README.md +27 -19
- package/cordis.patch.yml +2 -2
- package/lib/activation.js +12 -11
- package/lib/capabilities.js +23 -0
- package/lib/client.js +47 -77
- package/lib/decisions.js +4 -4
- package/lib/dsh-host.js +112 -30
- package/lib/evidence.js +24 -0
- package/lib/guard.js +17 -17
- package/lib/index.js +64 -43
- package/lib/kernel.js +33 -21
- package/lib/pipeline-guard.js +16 -4
- package/lib/routes.js +58 -56
- package/lib/sanitize.js +3 -2
- package/lib/service.js +75 -15
- package/lib/settlement.js +4 -1
- package/lib/state-store.js +15 -7
- package/lib/supervisor.js +135 -40
- package/lib/tool.js +10 -11
- package/lib/types.js +6 -5
- package/package.json +1 -1
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
|
-
请为下述目标制定最小化的
|
|
11
|
-
|
|
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.
|
|
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
|
-
|
|
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:
|
|
122
|
-
|
|
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
|
-
|
|
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:
|
|
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(', ')}
|
|
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:
|
|
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 ?? '
|
|
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 (
|
|
271
|
-
return
|
|
272
|
-
return
|
|
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
|
-
|
|
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: '
|
|
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: '
|
|
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.
|
|
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
|
-
|
|
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(
|
|
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:
|
|
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:
|
|
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
|
|
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
|
|
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:
|
|
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:
|
|
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 = '
|
|
6
|
-
'
|
|
7
|
-
'
|
|
8
|
-
|
|
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: '
|
|
41
|
-
preset: { type: 'string', description: 'Run preset id
|
|
42
|
-
approved_loop_count: { type: 'integer', description: '
|
|
43
|
-
run_id: { type: 'string', description: '
|
|
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: '
|
|
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
|
|
16
|
-
export const
|
|
17
|
-
export const
|
|
18
|
-
export const
|
|
19
|
-
export const
|
|
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.
|
|
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",
|