@kasenri/dsh-orbit 0.5.2 → 0.5.3

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/activation.js CHANGED
@@ -12,6 +12,7 @@
12
12
  * tool + `OrbitService` + Supervisor remain the sole Orbit runtime.
13
13
  */
14
14
  import { createUserMessage } from '@deepseek-ai/dsh-llm';
15
+ import { ORBIT_TOGGLE_COMMAND, parseOrbitToggle } from "./session-state.js";
15
16
  export const AGENT_ORBIT_COMMAND = 'agent-orbit';
16
17
  /** Strict gesture: the command must begin a genuine user message line. */
17
18
  const GESTURE = /^\/agent-orbit(?=$|[\t\n\r ])/u;
@@ -38,6 +39,22 @@ export function invokedOrbitActivation(messages) {
38
39
  }
39
40
  return undefined;
40
41
  }
42
+ /** The newest genuine user message text, as the implicit activation's goal. */
43
+ export function invokedOrbitMessage(messages) {
44
+ for (let index = messages.length - 1; index >= 0; index -= 1) {
45
+ const message = messages[index];
46
+ if (message === undefined || message.source.kind !== 'user')
47
+ continue;
48
+ const text = message.content
49
+ .filter((block) => block.type === 'text')
50
+ .map((block) => block.text)
51
+ .join('\n')
52
+ .trim();
53
+ if (text !== '')
54
+ return { goal: text };
55
+ }
56
+ return undefined;
57
+ }
41
58
  /**
42
59
  * The one deterministic directive injected for an explicit activation. It only
43
60
  * states the activation and carries the goal verbatim; every Orbit rule still
@@ -53,6 +70,17 @@ export function buildOrbitActivationDirective(goal) {
53
70
  : `Goal: ${goal}`);
54
71
  return lines.join('\n');
55
72
  }
73
+ /**
74
+ * The directive for a Session whose toggle is ON: an ordinary user message
75
+ * becomes the Orbit goal without changing the activation machinery.
76
+ */
77
+ export function buildOrbitSessionDirective(goal) {
78
+ return [
79
+ 'Orbit is enabled for this chat, so treat this ordinary user message as the requested goal. Start or resume Orbit through the existing orbit_controller tool; do not ask the user to confirm the mode.',
80
+ 'Use the message as the goal, without summarizing or rewriting it.',
81
+ `Goal: ${goal}`,
82
+ ].join('\n');
83
+ }
56
84
  /**
57
85
  * Register the closed-namespace `/agent-orbit` host command.
58
86
  *
@@ -79,29 +107,68 @@ export function registerOrbitCommand(ctx) {
79
107
  },
80
108
  }), 'dsh-orbit: /agent-orbit host command');
81
109
  }
110
+ /**
111
+ * Register the `/orbit-toggle on|off` host command.
112
+ *
113
+ * The command writes nothing itself: the commands runtime logs its own
114
+ * `command/run` record, and the `orbitSession` projection folds that record
115
+ * into the per-Session state the toggle UI reads. Registering it also gives
116
+ * CLI surfaces the same switch.
117
+ */
118
+ export function registerOrbitToggleCommand(ctx) {
119
+ ctx.effect(() => ctx.commands.register({
120
+ name: ORBIT_TOGGLE_COMMAND,
121
+ description: 'Turn the per-chat Orbit default on or off without starting a run',
122
+ input: { hint: 'on or off' },
123
+ handler(invocation) {
124
+ const enabled = parseOrbitToggle(invocation.rawInput);
125
+ if (enabled === undefined) {
126
+ return { kind: 'error', text: `Usage: /${ORBIT_TOGGLE_COMMAND} on|off — 请输入 on 或 off。` };
127
+ }
128
+ return {
129
+ kind: 'success',
130
+ text: enabled
131
+ ? 'Orbit is on for this chat — ordinary messages will enter Orbit.'
132
+ : 'Orbit is off for this chat — ordinary messages stay native.',
133
+ };
134
+ },
135
+ }), 'dsh-orbit: /orbit-toggle host command');
136
+ }
82
137
  /**
83
138
  * Install the gesture boundary. It runs for every proposed step and injects
84
139
  * the activation directive once, for the step that carries the genuine user
85
140
  * message. The in-step guard keeps a step that already holds an Orbit
86
141
  * directive from gaining a second one, without any durable state.
142
+ *
143
+ * An explicit `/agent-orbit` always wins and always activates, whatever the
144
+ * Session toggle says. Only when no explicit activation is present does the
145
+ * Session toggle (when the composition provides it) promote an ordinary user
146
+ * message into an Orbit goal.
87
147
  */
88
- export function installOrbitGestureBoundary(ctx) {
89
- ctx.on('agent/pre-step', async ({ messages, signal }, next) => {
148
+ export function installOrbitGestureBoundary(ctx, options = {}) {
149
+ ctx.on('agent/pre-step', async ({ agent, messages, signal }, next) => {
90
150
  const decision = await next();
91
151
  if (decision.kind === 'reject')
92
152
  return decision;
93
153
  if (decision.messages.some((message) => message.source.kind === 'orbit-command'))
94
154
  return decision;
95
- const activation = invokedOrbitActivation(messages);
96
- if (activation === undefined)
155
+ const explicit = invokedOrbitActivation(messages);
156
+ const implicit = explicit === undefined && options.sessionEnabled?.(agent.session) === true
157
+ ? invokedOrbitMessage(messages)
158
+ : undefined;
159
+ if (explicit === undefined && implicit === undefined)
97
160
  return decision;
98
161
  signal.throwIfAborted();
162
+ const activation = explicit ?? implicit ?? { goal: '' };
163
+ const directive = explicit === undefined
164
+ ? buildOrbitSessionDirective(activation.goal)
165
+ : buildOrbitActivationDirective(activation.goal);
99
166
  return {
100
167
  kind: 'enter',
101
168
  messages: [
102
169
  ...decision.messages,
103
170
  createUserMessage({
104
- content: [{ type: 'text', text: buildOrbitActivationDirective(activation.goal) }],
171
+ content: [{ type: 'text', text: directive }],
105
172
  source: { kind: 'orbit-command', ...(activation.goal === '' ? {} : { goal: activation.goal }) },
106
173
  }),
107
174
  ],
package/lib/client.js CHANGED
@@ -122,7 +122,7 @@ window.__ModuleLoader__.load({
122
122
  }
123
123
  //#endregion
124
124
  //#region \0dsh-css:/root/code/dsh-pi-parity/packages/orbit/client/OrbitModelSelect.module.css.mjs
125
- const css = ".f77nca_panel{z-index:60;border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-specific-menu);min-width:260px;max-width:420px;max-height:min(55vh,420px);box-shadow:var(--dsw-elevation-prominent);color:var(--dsw-alias-label-primary);border-radius:12px;flex-direction:column;padding:6px;font-size:13px;line-height:1.4;display:flex;position:fixed;overflow:auto}.f77nca_title{color:var(--dsw-alias-label-tertiary);letter-spacing:.04em;text-transform:uppercase;padding:6px 8px 2px;font-size:11px}.f77nca_group{color:var(--dsw-alias-label-tertiary);padding:8px 8px 2px;font-size:11px}.f77nca_row{width:100%;color:inherit;font:inherit;text-align:left;cursor:pointer;background:0 0;border:0;border-radius:8px;align-items:center;gap:8px;padding:7px 8px;display:flex}.f77nca_row:hover{background:var(--dsw-alias-interactive-bg-hover)}.f77nca_rowLabel{flex:none}.f77nca_rowValue{max-width:190px;color:var(--dsw-alias-label-tertiary);text-overflow:ellipsis;white-space:nowrap;margin-left:auto;overflow:hidden}.f77nca_rowValueActive{color:var(--dsw-alias-label-primary)}.f77nca_rowDetail{color:var(--dsw-alias-label-tertiary);margin-left:auto;font-size:11px}.f77nca_check{color:var(--dsw-alias-state-business-primary);flex:none;margin-left:6px}.f77nca_chevron{color:var(--dsw-alias-label-tertiary);flex:none;margin-left:6px}.f77nca_separator{background:var(--dsw-alias-border-l1);height:1px;margin:6px 4px}.f77nca_hint{color:var(--dsw-alias-label-tertiary);padding:6px 8px;font-size:11px}.f77nca_error{color:var(--dsw-alias-state-error-primary);align-items:center;gap:8px;padding:8px;font-size:12px;display:flex}.f77nca_back{width:100%;color:var(--dsw-alias-label-tertiary);font:inherit;text-align:left;cursor:pointer;background:0 0;border:0;align-items:center;gap:6px;padding:6px 8px 8px;font-size:12px;display:flex}";
125
+ const css = ".f77nca_panel{z-index:60;border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-specific-menu);min-width:260px;max-width:420px;max-height:min(55vh,420px);box-shadow:var(--dsw-elevation-prominent);color:var(--dsw-alias-label-primary);border-radius:12px;flex-direction:column;padding:6px;font-size:13px;line-height:1.4;display:flex;position:fixed;overflow:auto}.f77nca_title{color:var(--dsw-alias-label-tertiary);letter-spacing:.04em;text-transform:uppercase;padding:6px 8px 2px;font-size:11px}.f77nca_group{color:var(--dsw-alias-label-tertiary);padding:8px 8px 2px;font-size:11px}.f77nca_row{width:100%;color:inherit;font:inherit;text-align:left;cursor:pointer;background:0 0;border:0;border-radius:8px;align-items:center;gap:8px;padding:7px 8px;display:flex}.f77nca_row:hover{background:var(--dsw-alias-interactive-bg-hover)}.f77nca_switchRow{align-items:center;gap:8px;padding:4px 8px 6px;display:flex}.f77nca_switchControl{margin-left:auto}.f77nca_rowLabel{flex:none}.f77nca_rowValue{max-width:190px;color:var(--dsw-alias-label-tertiary);text-overflow:ellipsis;white-space:nowrap;margin-left:auto;overflow:hidden}.f77nca_rowValueActive{color:var(--dsw-alias-label-primary)}.f77nca_rowDetail{color:var(--dsw-alias-label-tertiary);margin-left:auto;font-size:11px}.f77nca_check{color:var(--dsw-alias-state-business-primary);flex:none;margin-left:6px}.f77nca_chevron{color:var(--dsw-alias-label-tertiary);flex:none;margin-left:6px}.f77nca_separator{background:var(--dsw-alias-border-l1);height:1px;margin:6px 4px}.f77nca_hint{color:var(--dsw-alias-label-tertiary);padding:6px 8px;font-size:11px}.f77nca_error{color:var(--dsw-alias-state-error-primary);align-items:center;gap:8px;padding:8px;font-size:12px;display:flex}.f77nca_back{width:100%;color:var(--dsw-alias-label-tertiary);font:inherit;text-align:left;cursor:pointer;background:0 0;border:0;align-items:center;gap:6px;padding:6px 8px 8px;font-size:12px;display:flex}";
126
126
  const tagId = "@kasenri/dsh-orbit/OrbitModelSelect.module.css";
127
127
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
128
128
  const tag = document.createElement("style");
@@ -132,20 +132,22 @@ window.__ModuleLoader__.load({
132
132
  document.head.appendChild(tag);
133
133
  }
134
134
  var OrbitModelSelect_module_css_default = {
135
- "hint": "f77nca_hint",
135
+ "error": "f77nca_error",
136
+ "chevron": "f77nca_chevron",
136
137
  "back": "f77nca_back",
137
- "rowValue": "f77nca_rowValue",
138
138
  "rowValueActive": "f77nca_rowValueActive",
139
- "title": "f77nca_title",
139
+ "hint": "f77nca_hint",
140
+ "switchControl": "f77nca_switchControl",
141
+ "group": "f77nca_group",
140
142
  "panel": "f77nca_panel",
141
- "row": "f77nca_row",
142
- "rowDetail": "f77nca_rowDetail",
143
143
  "separator": "f77nca_separator",
144
- "error": "f77nca_error",
145
- "group": "f77nca_group",
146
- "rowLabel": "f77nca_rowLabel",
144
+ "title": "f77nca_title",
145
+ "switchRow": "f77nca_switchRow",
146
+ "rowValue": "f77nca_rowValue",
147
+ "rowDetail": "f77nca_rowDetail",
147
148
  "check": "f77nca_check",
148
- "chevron": "f77nca_chevron"
149
+ "row": "f77nca_row",
150
+ "rowLabel": "f77nca_rowLabel"
149
151
  };
150
152
  //#endregion
151
153
  //#region client/OrbitModelSelect.tsx
@@ -178,9 +180,23 @@ window.__ModuleLoader__.load({
178
180
  * @param props - injected directory/settings faces plus the locale seat.
179
181
  * @returns the trigger and, while open, the panel.
180
182
  */
181
- function OrbitModelSelect({ t, available, directory, settings, loadModels, selectModel, writeRole, reloadSettings }) {
183
+ function OrbitModelSelect({ t, available, directory, settings, loadModels, selectModel, writeRole, setOrbitEnabled, reloadSettings, useProjection }) {
182
184
  const dir = (0, react.useSyncExternalStore)((listener) => directory.subscribe(listener), () => directory.getSnapshot());
183
185
  const config = (0, react.useSyncExternalStore)((listener) => settings.subscribe(listener), () => settings.getSnapshot());
186
+ const orbitSession = useProjection("orbitSession");
187
+ const [orbitOverride, setOrbitOverride] = (0, react.useState)(null);
188
+ const [orbitPending, setOrbitPending] = (0, react.useState)(false);
189
+ const orbitEnabled = orbitOverride ?? orbitSession?.enabled === true;
190
+ (0, react.useEffect)(() => {
191
+ if (orbitOverride !== null && orbitSession?.enabled === orbitOverride) setOrbitOverride(null);
192
+ }, [orbitOverride, orbitSession?.enabled]);
193
+ const toggleOrbit = async (next) => {
194
+ setOrbitOverride(next);
195
+ setOrbitPending(true);
196
+ const ok = await setOrbitEnabled(next);
197
+ setOrbitPending(false);
198
+ if (!ok) setOrbitOverride(null);
199
+ };
184
200
  const [open, setOpen] = (0, react.useState)(false);
185
201
  const [pane, setPane] = (0, react.useState)({ kind: "root" });
186
202
  const [busy, setBusy] = (0, react.useState)(false);
@@ -294,11 +310,11 @@ window.__ModuleLoader__.load({
294
310
  setOpen((value) => !value);
295
311
  },
296
312
  children: [
297
- commanderLabel,
298
- commanderEffort === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
313
+ orbitEnabled ? commanderLabel : t("orbitOff"),
314
+ orbitEnabled && commanderEffort !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
299
315
  style: { color: "var(--dsw-alias-label-tertiary)" },
300
316
  children: ` ${commanderEffort}`
301
- }),
317
+ }) : null,
302
318
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
303
319
  className: OrbitModelSelect_module_css_default.chevron,
304
320
  children: CARET
@@ -351,6 +367,22 @@ window.__ModuleLoader__.load({
351
367
  })]
352
368
  }),
353
369
  pane.kind === "root" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
370
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
371
+ className: OrbitModelSelect_module_css_default.switchRow,
372
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
373
+ className: OrbitModelSelect_module_css_default.rowLabel,
374
+ children: t("orbit")
375
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Switch, {
376
+ className: OrbitModelSelect_module_css_default.switchControl,
377
+ checked: orbitEnabled,
378
+ disabled: orbitPending,
379
+ label: t("orbitSwitchLabel"),
380
+ onChange: (next) => {
381
+ toggleOrbit(next);
382
+ }
383
+ })]
384
+ }),
385
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: OrbitModelSelect_module_css_default.separator }),
354
386
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
355
387
  type: "button",
356
388
  role: "menuitem",
@@ -545,6 +577,9 @@ window.__ModuleLoader__.load({
545
577
  triggerFallback: "Orbit",
546
578
  tooltip: "Orbit model configuration",
547
579
  title: "Orbit",
580
+ orbit: "Orbit",
581
+ orbitOff: "Orbit Off",
582
+ orbitSwitchLabel: "Orbit for this chat",
548
583
  commander: "Commander",
549
584
  executor: "Executor",
550
585
  watchdog: "Watchdog",
@@ -564,6 +599,9 @@ window.__ModuleLoader__.load({
564
599
  triggerFallback: "Orbit",
565
600
  tooltip: "Orbit 模型配置",
566
601
  title: "Orbit",
602
+ orbit: "Orbit",
603
+ orbitOff: "Orbit Off",
604
+ orbitSwitchLabel: "本会话 Orbit 开关",
567
605
  commander: "指挥官",
568
606
  executor: "执行员",
569
607
  watchdog: "监控模型",
@@ -691,6 +729,16 @@ window.__ModuleLoader__.load({
691
729
  },
692
730
  selectModel: (selection) => available ? directory.select(selection).then(() => true, () => false) : Promise.resolve(false),
693
731
  writeRole,
732
+ setOrbitEnabled: async (enabled) => {
733
+ const session = sessions.binding(sessionId)?.session;
734
+ if (session === void 0) return false;
735
+ try {
736
+ const result = await session.command(`/orbit-toggle ${enabled ? "on" : "off"}`);
737
+ return result.ok && result.value.matched;
738
+ } catch {
739
+ return false;
740
+ }
741
+ },
694
742
  reloadSettings
695
743
  };
696
744
  };
package/lib/index.js CHANGED
@@ -1,7 +1,8 @@
1
1
  import z from '@deepseek-ai/schemastery';
2
- import { installOrbitGestureBoundary, registerOrbitCommand } from "./activation.js";
2
+ import { installOrbitGestureBoundary, registerOrbitCommand, registerOrbitToggleCommand } from "./activation.js";
3
3
  import { createOrbitPreExecuteHandler } from "./pipeline-guard.js";
4
4
  import { resolveEffectiveRoutes, sessionSelectionOf } from "./routes.js";
5
+ import { installOrbitSessionProjection, orbitEnabledOf } from "./session-state.js";
5
6
  import { OrbitService } from "./service.js";
6
7
  import { createOrbitTool } from "./tool.js";
7
8
  export const name = 'dsh-orbit';
@@ -112,9 +113,18 @@ export function apply(ctx, config) {
112
113
  if (config.slashCommand) {
113
114
  ctx.inject(['commands'], (commandCtx) => {
114
115
  registerOrbitCommand(commandCtx);
116
+ registerOrbitToggleCommand(commandCtx);
117
+ });
118
+ installOrbitGestureBoundary(ctx, {
119
+ sessionEnabled: (session) => orbitEnabledOf(ctx, session),
115
120
  });
116
- installOrbitGestureBoundary(ctx);
117
121
  }
122
+ // Per-Session Orbit enable state: natively durable through the Session log
123
+ // and the projection registry. Minimal compositions without the registry
124
+ // keep the feature inert (OFF) while `/agent-orbit` keeps working.
125
+ ctx.inject(['sessionProjections'], (projectionCtx) => {
126
+ installOrbitSessionProjection(projectionCtx);
127
+ });
118
128
  if (config.registerGuards) {
119
129
  const handler = createOrbitPreExecuteHandler(service, {
120
130
  competingDriver: (agent) => {
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Per-Session Orbit enable state, native to DeepSeek Harness.
3
+ *
4
+ * The write is the ordinary `/orbit-toggle on|off` command: the commands
5
+ * runtime appends its own `command/run` record to the Session log (a known
6
+ * event type, durable with the Session). The state is a session projection
7
+ * unit folding those records, so it is per-Session by construction, survives
8
+ * reloads with the log, and reaches the browser through the normal projection
9
+ * wire — no separate store, no Orbit settings entry, no `.cx/state.json`.
10
+ *
11
+ * This is UI/runtime preference state for one chat, never the durable
12
+ * execution state of an Orbit run.
13
+ */
14
+ /** The command whose `command/run` records carry the toggle. */
15
+ export const ORBIT_TOGGLE_COMMAND = 'orbit-toggle';
16
+ /** The projection key carrying the per-Session enable state. */
17
+ export const ORBIT_SESSION_KEY = 'orbitSession';
18
+ /**
19
+ * Parse one `/orbit-toggle` argument; `undefined` when it names neither state,
20
+ * so a malformed toggle never flips the switch as a side effect.
21
+ */
22
+ export function parseOrbitToggle(rawInput) {
23
+ const argument = rawInput.trim().toLowerCase();
24
+ if (argument === 'on')
25
+ return true;
26
+ if (argument === 'off')
27
+ return false;
28
+ return undefined;
29
+ }
30
+ /**
31
+ * Fold one Session event into the enable state. A new Session starts OFF;
32
+ * every unrelated event keeps the same state reference so the projection
33
+ * drive does no downstream work.
34
+ */
35
+ export function foldOrbitSession(state, event) {
36
+ if (event.type !== 'command/run')
37
+ return state;
38
+ const data = event.data;
39
+ if (data?.name !== ORBIT_TOGGLE_COMMAND)
40
+ return state;
41
+ const enabled = parseOrbitToggle(typeof data.args === 'string' ? data.args : '');
42
+ if (enabled === undefined || enabled === state.enabled)
43
+ return state;
44
+ return { enabled };
45
+ }
46
+ /**
47
+ * Minimal `ZodType`-shaped validator for the projection schemas. The registry
48
+ * only ever calls `parse`, and Orbit must not add a `zod` runtime dependency
49
+ * that a strict package manager would not hoist for the plugin.
50
+ */
51
+ function orbitSessionSchema() {
52
+ return {
53
+ parse(value) {
54
+ const enabled = value?.enabled;
55
+ if (typeof enabled !== 'boolean')
56
+ throw new Error('orbitSession projection value must be { enabled: boolean }');
57
+ return { enabled };
58
+ },
59
+ };
60
+ }
61
+ /** Register the per-Session enable projection on the session projection registry. */
62
+ export function installOrbitSessionProjection(ctx) {
63
+ ctx.sessionProjections.register({
64
+ key: ORBIT_SESSION_KEY,
65
+ stateSchema: orbitSessionSchema(),
66
+ init: () => ({ enabled: false }),
67
+ apply: (state, event) => foldOrbitSession(state, event),
68
+ wire: {
69
+ viewSchema: orbitSessionSchema(),
70
+ view: (state) => ({ enabled: state.enabled }),
71
+ },
72
+ stateVersion: 1,
73
+ });
74
+ }
75
+ /** Whether the Session currently defaults ordinary messages into Orbit. */
76
+ export function orbitEnabledOf(ctx, session) {
77
+ const reflect = ctx.reflect;
78
+ const registry = reflect?.get('sessionProjections');
79
+ return registry?.stateOf(session, ORBIT_SESSION_KEY)?.enabled === true;
80
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kasenri/dsh-orbit",
3
- "version": "0.5.2",
3
+ "version": "0.5.3",
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",