@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/README.md CHANGED
@@ -94,14 +94,13 @@ The `orbit_controller` tool drives the run:
94
94
  Orbit supports three activation styles:
95
95
 
96
96
  1. `/agent-orbit <goal>` — deterministic slash-command activation. In the Web
97
- GUI it appears in the `/` menu (`Run a goal with Orbit deterministic
98
- engineering orchestration`); on headless/CLI surfaces a genuine user message
97
+ GUI it appears in the `/` menu (`使用 Orbit 确定性工程编排执行目标`); on headless/CLI surfaces a genuine user message
99
98
  that starts with `/agent-orbit` activates Orbit directly. The original
100
99
  command line stays visible in the conversation, the goal is passed through
101
100
  without rewriting, and no goal asks for one instead of starting an empty run.
102
- 2. `orbit模式` — recommended natural-language activation, e.g.
101
+ 2. `orbit模式` — recommended natural-language activation, e.g.
103
102
  “用 orbit模式完成这个项目”.
104
- 3. `cx模式` — legacy compatibility; still resolves to Orbit.
103
+ 3. `cx模式` — legacy compatibility; still resolves to Orbit.
105
104
 
106
105
  All three routes converge on the existing `orbit_controller` tool and
107
106
  `OrbitService`; the activation layer never starts a run of its own.
@@ -113,20 +112,21 @@ seat (`conversation.input.right` renders before `conversation.input.model`).
113
112
  Its button names the Commander's model; the menu edits three roles:
114
113
 
115
114
  - **Commander** and **Watchdog** pick from the same native model catalog and
116
- persist into the DSH `orbit` settings namespace (`settings.yaml`), with the
117
- composition `config.routes` as the base/default. Switching a model uses that
118
- model's own default reasoning effort a previous model's effort is never
119
- inherited.
115
+ persist into the DSH `orbit` settings namespace (`settings.yaml`). Picking a
116
+ model first opens reasoning selection; nothing is saved until the user chooses
117
+ a catalog-declared effort or provider default. No effort is inferred.
120
118
  - **Executor** follows the current session model ("Follows current session
121
119
  model"). Both the Orbit row and the native seat read and write the SAME
122
120
  per-session `ModelDirectory`, so a change in either place updates the other.
123
121
 
124
122
  A new run resolves its routes exactly once — Commander/Watchdog from `orbit`
125
- settings, Executor from the initiating session's current selection, each
126
- falling back to `config.routes` and freezes them into `state.routes`.
123
+ settings, Executor from the initiating session's current selection — and freezes
124
+ them into `state.routes`. Orbit ships no provider/model or reasoning defaults.
127
125
  Changes made while a run is active apply to the next run; resumed runs keep
128
- their frozen routes. Headless/CLI profiles without a settings provider keep
129
- running from `config.routes` unchanged.
126
+ their frozen routes. Explicit profile routes are permitted for headless/CLI:
127
+ Commander/Watchdog use settings then explicit config; Executor uses the Session
128
+ selection, with explicit config only when there is no Session. Missing or
129
+ unavailable roles block creation of a Run before any child starts.
130
130
 
131
131
  ## State machine
132
132
 
@@ -158,11 +158,16 @@ PLAN → EXECUTE → EVALUATE → SUCCESS
158
158
  ## Safety boundaries
159
159
 
160
160
  - Commander and Watchdog receive read-only tool allowlists.
161
- - Executors receive the configured writer allowlist; browser tools are added
162
- only for steps that declare the `browser` capability.
161
+ - Executors always receive only base read tools (`read/read_image/glob/grep`).
162
+ Each Step explicitly declares `filesystem`, `shell`, `web`, or `browser` to
163
+ add only the corresponding tools. Legacy `web-api-recon` normalizes to `web`
164
+ plus `browser`. No capability means no bash, writers, web, or browser tools.
163
165
  - While Orbit owns a workspace, other top-level autonomous drivers
164
166
  (`create_goal`, `ralph`, `workflow`) are refused, and Orbit refuses to start
165
- while an active goal driver owns the same workspace.
167
+ while an active Goal driver owns the same workspace. Only an Orbit-owned
168
+ Executor child can use mutation tools while Orbit owns that workspace.
169
+ - Bounded, redacted `step_results` preserve each Step's summary, changes and
170
+ test evidence. Cold resume retains them and FINAL_EVALUATE sees every Step.
166
171
  - `.cx` durable state is written only by the Orbit service (atomic write, short
167
172
  lock transaction, monotonic revision).
168
173
 
@@ -171,10 +176,10 @@ PLAN → EXECUTE → EVALUATE → SUCCESS
171
176
  | Key | Default | Meaning |
172
177
  |---|---|---|
173
178
  | `projectDir` | session cwd | Project the run operates on. |
174
- | `routes.commander` | `deepseek-official` / `deepseek-v4-pro` / `high` | Commander model route. |
175
- | `routes.executor` | `deepseek-official` / `deepseek-v4-flash` / `high` | Executor model route. |
176
- | `routes.watchdog` | `deepseek-official` / `deepseek-v4-flash` / `low` | Watchdog model route. |
177
- | `executorTools` | read/glob/grep/bash/edit/write/… | Executor allowlist. |
179
+ | `routes.commander` | none | Explicit profile fallback; normally user-selected in Orbit settings. |
180
+ | `routes.executor` | none | Explicit config only without a Session; otherwise follows DSH Session selection. |
181
+ | `routes.watchdog` | none | Explicit profile fallback; normally user-selected in Orbit settings. |
182
+ | `executorTools` | read/read_image/glob/grep | Base read-only subset; mutation tools require Step capabilities. |
178
183
  | `browserTools` | `["agent_browser"]` | Browser capability tool names. |
179
184
  | `commanderReadOnlyTools` | read/glob/grep/web… | Commander allowlist. |
180
185
  | `watchdogTools` | read/glob/grep | Watchdog allowlist. |
package/cordis.patch.yml CHANGED
@@ -2,8 +2,8 @@
2
2
  #
3
3
  # Adds the Orbit engineering orchestration plugin (OrbitService + orbit_controller
4
4
  # tool; `cx_controller` remains a legacy alias) on top of the active bundle stack.
5
- # Model routes use the plugin defaults and can be overridden in the profile's own
6
- # cordis.patch.yml.
5
+ # Orbit does not ship model-route defaults. Users select Commander/Watchdog in
6
+ # Orbit settings; Executor follows the current DSH Session selection.
7
7
  - insert:
8
8
  - id: dsh-orbit
9
9
  name: '@kasenri/dsh-orbit'
package/lib/activation.js CHANGED
@@ -65,18 +65,18 @@ export function invokedOrbitMessage(messages) {
65
65
  export function registerOrbitCommand(ctx) {
66
66
  ctx.effect(() => ctx.commands.register({
67
67
  name: AGENT_ORBIT_COMMAND,
68
- description: 'Run a goal with Orbit deterministic engineering orchestration',
69
- input: { hint: 'Describe the engineering goal for Orbit' },
68
+ description: '使用 Orbit 确定性工程编排执行目标',
69
+ input: { hint: '描述要交给 Orbit 完成的工程目标' },
70
70
  handler(invocation) {
71
71
  const goal = invocation.rawInput.trim();
72
72
  if (goal === '') {
73
- return { kind: 'error', text: `Usage: /${AGENT_ORBIT_COMMAND} <goal> — 请输入要交给 Orbit 完成的任务。` };
73
+ return { kind: 'error', text: `用法:/${AGENT_ORBIT_COMMAND} <goal>,请输入任务。` };
74
74
  }
75
75
  invocation.agent.followup(createUserMessage({
76
76
  content: [{ type: 'text', text: `/${AGENT_ORBIT_COMMAND}${invocation.rawInput}` }],
77
77
  source: { kind: 'user' },
78
78
  }));
79
- return { kind: 'success', text: 'Orbit activated the existing supervisor will handle this goal.' };
79
+ return { kind: 'success', text: 'Orbit 已激活,现有 Supervisor 将处理此目标。' };
80
80
  },
81
81
  }), 'dsh-orbit: /agent-orbit host command');
82
82
  }
@@ -91,18 +91,18 @@ export function registerOrbitCommand(ctx) {
91
91
  export function registerOrbitToggleCommand(ctx) {
92
92
  ctx.effect(() => ctx.commands.register({
93
93
  name: ORBIT_TOGGLE_COMMAND,
94
- description: 'Turn the per-chat Orbit default on or off without starting a run',
95
- input: { hint: 'on or off' },
94
+ description: '启用或关闭本会话 Orbit,不启动 Run',
95
+ input: { hint: '请输入 on off' },
96
96
  handler(invocation) {
97
97
  const enabled = parseOrbitToggle(invocation.rawInput);
98
98
  if (enabled === undefined) {
99
- return { kind: 'error', text: `Usage: /${ORBIT_TOGGLE_COMMAND} on|off — 请输入 on 或 off。` };
99
+ return { kind: 'error', text: `用法:/${ORBIT_TOGGLE_COMMAND} on|off,请输入 on 或 off。` };
100
100
  }
101
101
  return {
102
102
  kind: 'success',
103
103
  text: enabled
104
- ? 'Orbit is on for this chat — ordinary messages will enter Orbit.'
105
- : 'Orbit is off for this chat — ordinary messages stay native.',
104
+ ? '本会话 Orbit 已启用,普通消息将进入 Orbit'
105
+ : '本会话 Orbit 已关闭,普通消息使用原生执行方式。',
106
106
  };
107
107
  },
108
108
  }), 'dsh-orbit: /orbit-toggle host command');
@@ -0,0 +1,23 @@
1
+ export const EXECUTOR_READ_ONLY_TOOLS = ['read', 'read_image', 'glob', 'grep'];
2
+ export const FILESYSTEM_TOOLS = ['write', 'edit', 'str_replace_editor'];
3
+ export const SHELL_TOOLS = ['bash'];
4
+ export const WEB_TOOLS = ['web_search', 'web_fetch'];
5
+ export const READ_ONLY_ROLE_TOOLS = [...EXECUTOR_READ_ONLY_TOOLS, ...WEB_TOOLS];
6
+ const READ_ONLY_SET = new Set(EXECUTOR_READ_ONLY_TOOLS);
7
+ /** Deterministically map bounded capability ids to tool names. */
8
+ export function executorToolsFor(capabilities, browserTools, configuredReadOnly = EXECUTOR_READ_ONLY_TOOLS) {
9
+ const tools = configuredReadOnly.filter((tool) => READ_ONLY_SET.has(tool));
10
+ if (capabilities.includes('filesystem'))
11
+ tools.push(...FILESYSTEM_TOOLS);
12
+ if (capabilities.includes('shell'))
13
+ tools.push(...SHELL_TOOLS);
14
+ if (capabilities.includes('web'))
15
+ tools.push(...WEB_TOOLS);
16
+ if (capabilities.includes('browser'))
17
+ tools.push(...browserTools);
18
+ return [...new Set(tools)];
19
+ }
20
+ /** Tools that can mutate workspace or browser state while Orbit owns it. */
21
+ export function mutationTools(browserTools) {
22
+ return new Set([...FILESYSTEM_TOOLS, ...SHELL_TOOLS, 'pwsh', 'apply_patch', 'delete_file', 'move_file', ...browserTools]);
23
+ }
package/lib/client.js CHANGED
@@ -19,46 +19,22 @@ window.__ModuleLoader__.load({
19
19
  }
20
20
  }
21
21
  /**
22
- * UI-only fallback ladder for models whose catalog entry declares no reasoning
23
- * metadata. The DSH catalog is never modified: this only fills the Orbit
24
- * picker, and a provider that rejects an effort still fails through the normal
25
- * selection error path.
26
- */
27
- const FALLBACK_REASONING_EFFORTS = [
28
- "low",
29
- "medium",
30
- "high",
31
- "xhigh",
32
- "max"
33
- ];
34
- const FALLBACK_EFFORT_LABELS = {
35
- low: "Low",
36
- medium: "Medium",
37
- high: "High",
38
- xhigh: "XHigh",
39
- max: "Max"
40
- };
41
- /**
42
- * Effort options for one model: the adapter's explicit list when the catalog
43
- * declares one, otherwise the Orbit UI fallback ladder.
22
+ * Effort options for one model: exactly the DSH model metadata's explicit
23
+ * `reasoning.efforts`. A model without reasoning metadata yields none; the
24
+ * picker then offers only Provider default.
44
25
  */
45
26
  function effortsOf(model) {
46
- const explicit = model?.reasoning?.efforts ?? [];
47
- if (explicit.length > 0) return explicit.map((effort) => ({
27
+ return (model?.reasoning?.efforts ?? []).map((effort) => ({
48
28
  id: effort.id,
49
29
  name: effort.name
50
30
  }));
51
- return FALLBACK_REASONING_EFFORTS.map((id) => ({
52
- id,
53
- name: FALLBACK_EFFORT_LABELS[id] ?? id
54
- }));
55
31
  }
56
32
  /** Human effort label for a stored effort id; falls back to the raw id. */
57
33
  function effortLabelOf(model, effort) {
58
34
  if (effort === void 0 || effort === "") return void 0;
59
35
  const explicit = model?.reasoning?.efforts.find((entry) => entry.id === effort);
60
36
  if (explicit !== void 0) return explicit.name;
61
- return FALLBACK_EFFORT_LABELS[effort] ?? effort;
37
+ return effort;
62
38
  }
63
39
  /** Display name for a stored route: catalog name, then its model id. */
64
40
  function routeLabelOf(state, route) {
@@ -66,33 +42,19 @@ window.__ModuleLoader__.load({
66
42
  return modelOf(state, route)?.name ?? route.model;
67
43
  }
68
44
  /**
69
- * Route for a freshly picked model: the model's own default effort. A model
70
- * without effort metadata yields no effort a previous model's effort is
71
- * never inherited.
45
+ * Route for a freshly picked model: no reasoning effort is adopted, not even
46
+ * the catalog metadata's `defaultEffort`. Provider default means the absent
47
+ * effort, and a previous model's effort is never inherited.
72
48
  */
73
49
  function routeForModel(provider, model) {
74
- const defaultEffort = model.reasoning?.defaultEffort;
75
- return {
76
- provider,
77
- model: model.id,
78
- ...defaultEffort === void 0 ? {} : { reasoningEffort: defaultEffort }
79
- };
80
- }
81
- /**
82
- * Selection for the shared Session directory: preserve the current effort
83
- * when the route did not change, otherwise use the picked model's default.
84
- */
85
- function selectionForModel(state, provider, model) {
86
- const effort = state.current?.provider === provider && state.current.model === model.id ? state.current?.reasoningEffort ?? model.reasoning?.defaultEffort : model.reasoning?.defaultEffort;
87
50
  return {
88
51
  provider,
89
- model: model.id,
90
- ...effort === void 0 ? {} : { reasoningEffort: effort }
52
+ model: model.id
91
53
  };
92
54
  }
93
55
  /**
94
56
  * Effort rows for one model: Provider default (an undefined effort) first,
95
- * then the model's explicit ladder or the Orbit UI fallback.
57
+ * then the model's explicit metadata efforts, if any.
96
58
  */
97
59
  function effortChoicesOf(model, providerDefaultLabel) {
98
60
  return [{
@@ -132,22 +94,22 @@ window.__ModuleLoader__.load({
132
94
  document.head.appendChild(tag);
133
95
  }
134
96
  var OrbitModelSelect_module_css_default = {
135
- "rowDetail": "f77nca_rowDetail",
136
- "switchControl": "f77nca_switchControl",
97
+ "hint": "f77nca_hint",
98
+ "rowValueActive": "f77nca_rowValueActive",
137
99
  "rowValue": "f77nca_rowValue",
138
- "title": "f77nca_title",
139
- "chevron": "f77nca_chevron",
140
- "switchRow": "f77nca_switchRow",
141
- "group": "f77nca_group",
142
- "row": "f77nca_row",
143
- "back": "f77nca_back",
144
- "check": "f77nca_check",
100
+ "switchControl": "f77nca_switchControl",
101
+ "rowDetail": "f77nca_rowDetail",
145
102
  "rowLabel": "f77nca_rowLabel",
103
+ "switchRow": "f77nca_switchRow",
146
104
  "separator": "f77nca_separator",
105
+ "chevron": "f77nca_chevron",
106
+ "row": "f77nca_row",
107
+ "group": "f77nca_group",
147
108
  "error": "f77nca_error",
148
- "hint": "f77nca_hint",
149
- "panel": "f77nca_panel",
150
- "rowValueActive": "f77nca_rowValueActive"
109
+ "check": "f77nca_check",
110
+ "title": "f77nca_title",
111
+ "back": "f77nca_back",
112
+ "panel": "f77nca_panel"
151
113
  };
152
114
  //#endregion
153
115
  //#region client/OrbitModelSelect.tsx
@@ -200,6 +162,7 @@ window.__ModuleLoader__.load({
200
162
  const [open, setOpen] = (0, react.useState)(false);
201
163
  const [pane, setPane] = (0, react.useState)({ kind: "root" });
202
164
  const [busy, setBusy] = (0, react.useState)(false);
165
+ const submitting = (0, react.useRef)(false);
203
166
  const triggerRef = (0, react.useRef)(null);
204
167
  const panelRef = (0, react.useRef)(null);
205
168
  const position = (0, _deepseek_ai_dsh_client_ui_primitives.useAnchoredPosition)({
@@ -236,16 +199,26 @@ window.__ModuleLoader__.load({
236
199
  const watchdogLabel = watchdogRoute === void 0 ? void 0 : routeLabelOf(dir, watchdogRoute) ?? watchdogRoute.model;
237
200
  const watchdogEffort = effortLabelOf(watchdogModel, watchdogRoute?.reasoningEffort);
238
201
  const commitRole = async (role, route) => {
202
+ if (submitting.current || config.status !== "ready") return;
203
+ submitting.current = true;
239
204
  setBusy(true);
240
- const ok = await writeRole(role, route);
241
- setBusy(false);
242
- if (ok) setOpen(false);
205
+ try {
206
+ if (await writeRole(role, route)) setOpen(false);
207
+ } finally {
208
+ submitting.current = false;
209
+ setBusy(false);
210
+ }
243
211
  };
244
212
  const commitExecutor = async (selection) => {
213
+ if (submitting.current) return;
214
+ submitting.current = true;
245
215
  setBusy(true);
246
- const ok = await selectModel(selection);
247
- setBusy(false);
248
- if (ok) setOpen(false);
216
+ try {
217
+ if (await selectModel(selection)) setOpen(false);
218
+ } finally {
219
+ submitting.current = false;
220
+ setBusy(false);
221
+ }
249
222
  };
250
223
  const currentRouteOf = (role) => role === "commander" ? config.commander : role === "watchdog" ? config.watchdog : executorRoute;
251
224
  const roleLabelOf = (role) => role === "commander" ? commanderLabel : role === "watchdog" ? watchdogLabel : executorLabel;
@@ -272,15 +245,11 @@ window.__ModuleLoader__.load({
272
245
  className: OrbitModelSelect_module_css_default.row,
273
246
  disabled: busy,
274
247
  onClick: () => {
275
- if (active) {
276
- setOpen(false);
277
- return;
278
- }
279
- if (role === "executor") {
280
- commitExecutor(selectionForModel(dir, provider, model));
281
- return;
282
- }
283
- commitRole(role, routeForModel(provider, model));
248
+ setPane({
249
+ kind: "efforts",
250
+ role,
251
+ draft: routeForModel(provider, model)
252
+ });
284
253
  },
285
254
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
286
255
  className: OrbitModelSelect_module_css_default.rowLabel,
@@ -294,7 +263,7 @@ window.__ModuleLoader__.load({
294
263
  }) : null]
295
264
  }, `${provider}/${model.id}`);
296
265
  };
297
- const effortPaneRoute = pane.kind === "efforts" ? currentRouteOf(pane.role) : void 0;
266
+ const effortPaneRoute = pane.kind === "efforts" ? pane.draft ?? currentRouteOf(pane.role) : void 0;
298
267
  const effortPaneModel = modelOf(dir, effortPaneRoute);
299
268
  const effortPaneChoices = pane.kind === "efforts" ? effortChoicesOf(effortPaneModel, t("providerDefault")) : [];
300
269
  const settingsError = config.error;
@@ -553,7 +522,7 @@ window.__ModuleLoader__.load({
553
522
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
554
523
  className: OrbitModelSelect_module_css_default.rowLabel,
555
524
  children: choice.label
556
- }), effortPaneRoute.reasoningEffort === choice.effort ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
525
+ }), pane.draft === void 0 && effortPaneRoute.reasoningEffort === choice.effort ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
557
526
  className: OrbitModelSelect_module_css_default.check,
558
527
  children: "✓"
559
528
  }) : null]
@@ -571,7 +540,7 @@ window.__ModuleLoader__.load({
571
540
  const NS = "orbit-model";
572
541
  /** English strings (the key-set source of truth for this pair). */
573
542
  const en = {
574
- triggerFallback: "Orbit",
543
+ triggerFallback: "Not configured",
575
544
  tooltip: "Orbit model configuration",
576
545
  title: "Orbit — Self-reviewing continuous execution",
577
546
  enableLongRun: "Enable long-running execution",
@@ -596,7 +565,7 @@ window.__ModuleLoader__.load({
596
565
  };
597
566
  /** Chinese strings. */
598
567
  const zh = {
599
- triggerFallback: "Orbit",
568
+ triggerFallback: "未配置",
600
569
  tooltip: "Orbit 模型配置",
601
570
  title: "Orbit 让AI自我审核连续执行",
602
571
  enableLongRun: "启用一键长执行",
@@ -690,6 +659,7 @@ window.__ModuleLoader__.load({
690
659
  }, "dsh-orbit: settings mirror");
691
660
  const writeRole = async (role, route) => {
692
661
  const snapshot = settings.getSnapshot();
662
+ if (snapshot.status !== "ready" || snapshot.revision === void 0) return false;
693
663
  try {
694
664
  const response = await ctx.remote.settings.update(ORBIT_SETTINGS_NS, { [role]: {
695
665
  provider: route.provider,
package/lib/decisions.js CHANGED
@@ -14,16 +14,16 @@ export const COMMANDER_PLAN_SCHEMA = {
14
14
  additionalProperties: false,
15
15
  required: ['summary', 'steps'],
16
16
  properties: {
17
- summary: { type: 'string', description: 'One-sentence plan summary.' },
17
+ summary: { type: 'string', description: '一句话计划摘要。' },
18
18
  steps: {
19
19
  type: 'array',
20
- description: '2-5 logical engineering steps.',
20
+ description: '1-5 个逻辑工程步骤。',
21
21
  items: {
22
22
  type: 'object',
23
23
  additionalProperties: false,
24
24
  required: ['goal'],
25
25
  properties: {
26
- id: { type: 'string', description: 'Stable id such as P0.' },
26
+ id: { type: 'string', description: '稳定标识,例如 P0' },
27
27
  goal: { type: 'string' },
28
28
  capabilities: { type: 'array', items: { type: 'string', enum: CAPABILITY_ENUM } },
29
29
  },
@@ -94,7 +94,7 @@ export const WATCHDOG_STRATEGY_SCHEMA = {
94
94
  additionalProperties: false,
95
95
  required: ['question'],
96
96
  properties: {
97
- question: { type: 'string', description: 'The single strategy question to put to the Commander.' },
97
+ question: { type: 'string', description: '提交给 Commander 的单个策略质疑问题。' },
98
98
  },
99
99
  };
100
100
  export const WATCHDOG_GUARD_SCHEMA = {