@kasenri/dsh-orbit 0.5.2 → 0.5.4
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 +72 -5
- package/lib/client.js +75 -24
- package/lib/index.js +12 -2
- package/lib/session-state.js +80 -0
- package/package.json +1 -1
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
|
|
96
|
-
|
|
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:
|
|
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);
|
|
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);text-overflow:ellipsis;white-space:nowrap;padding:6px 8px 2px;font-size:11px;overflow:hidden}.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
|
-
"
|
|
135
|
+
"check": "f77nca_check",
|
|
136
136
|
"back": "f77nca_back",
|
|
137
|
+
"switchRow": "f77nca_switchRow",
|
|
138
|
+
"hint": "f77nca_hint",
|
|
139
|
+
"rowDetail": "f77nca_rowDetail",
|
|
140
|
+
"group": "f77nca_group",
|
|
141
|
+
"switchControl": "f77nca_switchControl",
|
|
137
142
|
"rowValue": "f77nca_rowValue",
|
|
138
|
-
"
|
|
143
|
+
"chevron": "f77nca_chevron",
|
|
139
144
|
"title": "f77nca_title",
|
|
140
|
-
"panel": "f77nca_panel",
|
|
141
|
-
"row": "f77nca_row",
|
|
142
|
-
"rowDetail": "f77nca_rowDetail",
|
|
143
|
-
"separator": "f77nca_separator",
|
|
144
145
|
"error": "f77nca_error",
|
|
145
|
-
"group": "f77nca_group",
|
|
146
146
|
"rowLabel": "f77nca_rowLabel",
|
|
147
|
-
"
|
|
148
|
-
"
|
|
147
|
+
"separator": "f77nca_separator",
|
|
148
|
+
"panel": "f77nca_panel",
|
|
149
|
+
"row": "f77nca_row",
|
|
150
|
+
"rowValueActive": "f77nca_rowValueActive"
|
|
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);
|
|
@@ -233,6 +249,7 @@ window.__ModuleLoader__.load({
|
|
|
233
249
|
};
|
|
234
250
|
const currentRouteOf = (role) => role === "commander" ? config.commander : role === "watchdog" ? config.watchdog : executorRoute;
|
|
235
251
|
const roleLabelOf = (role) => role === "commander" ? commanderLabel : role === "watchdog" ? watchdogLabel : executorLabel;
|
|
252
|
+
const roleModelLabelOf = (role) => t(role === "commander" ? "commanderModel" : role === "executor" ? "executorModel" : "watchdogModel");
|
|
236
253
|
const roleEffortLabelOf = (role) => role === "commander" ? commanderEffort : role === "watchdog" ? watchdogEffort : executorEffort;
|
|
237
254
|
const commitEffort = async (role, route, effort) => {
|
|
238
255
|
const next = {
|
|
@@ -294,11 +311,11 @@ window.__ModuleLoader__.load({
|
|
|
294
311
|
setOpen((value) => !value);
|
|
295
312
|
},
|
|
296
313
|
children: [
|
|
297
|
-
commanderLabel,
|
|
298
|
-
commanderEffort
|
|
314
|
+
orbitEnabled ? commanderLabel : t("orbitOff"),
|
|
315
|
+
orbitEnabled && commanderEffort !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
299
316
|
style: { color: "var(--dsw-alias-label-tertiary)" },
|
|
300
317
|
children: ` ${commanderEffort}`
|
|
301
|
-
}),
|
|
318
|
+
}) : null,
|
|
302
319
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
303
320
|
className: OrbitModelSelect_module_css_default.chevron,
|
|
304
321
|
children: CARET
|
|
@@ -351,6 +368,22 @@ window.__ModuleLoader__.load({
|
|
|
351
368
|
})]
|
|
352
369
|
}),
|
|
353
370
|
pane.kind === "root" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
371
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
372
|
+
className: OrbitModelSelect_module_css_default.switchRow,
|
|
373
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
374
|
+
className: OrbitModelSelect_module_css_default.rowLabel,
|
|
375
|
+
children: t("enableLongRun")
|
|
376
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Switch, {
|
|
377
|
+
className: OrbitModelSelect_module_css_default.switchControl,
|
|
378
|
+
checked: orbitEnabled,
|
|
379
|
+
disabled: orbitPending,
|
|
380
|
+
label: t("orbitSwitchLabel"),
|
|
381
|
+
onChange: (next) => {
|
|
382
|
+
toggleOrbit(next);
|
|
383
|
+
}
|
|
384
|
+
})]
|
|
385
|
+
}),
|
|
386
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: OrbitModelSelect_module_css_default.separator }),
|
|
354
387
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
355
388
|
type: "button",
|
|
356
389
|
role: "menuitem",
|
|
@@ -435,10 +468,6 @@ window.__ModuleLoader__.load({
|
|
|
435
468
|
})
|
|
436
469
|
] }) : null,
|
|
437
470
|
pane.kind === "role" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
438
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
439
|
-
className: OrbitModelSelect_module_css_default.group,
|
|
440
|
-
children: t(pane.role)
|
|
441
|
-
}),
|
|
442
471
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
443
472
|
type: "button",
|
|
444
473
|
role: "menuitem",
|
|
@@ -453,7 +482,7 @@ window.__ModuleLoader__.load({
|
|
|
453
482
|
children: [
|
|
454
483
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
455
484
|
className: OrbitModelSelect_module_css_default.rowLabel,
|
|
456
|
-
children:
|
|
485
|
+
children: roleModelLabelOf(pane.role)
|
|
457
486
|
}),
|
|
458
487
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
459
488
|
className: OrbitModelSelect_module_css_default.rowValue,
|
|
@@ -532,7 +561,7 @@ window.__ModuleLoader__.load({
|
|
|
532
561
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: OrbitModelSelect_module_css_default.separator }),
|
|
533
562
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
534
563
|
className: OrbitModelSelect_module_css_default.hint,
|
|
535
|
-
children: t("
|
|
564
|
+
children: t("applyOnNextSend")
|
|
536
565
|
})
|
|
537
566
|
]
|
|
538
567
|
}), document.body) : null] });
|
|
@@ -544,10 +573,16 @@ window.__ModuleLoader__.load({
|
|
|
544
573
|
const en = {
|
|
545
574
|
triggerFallback: "Orbit",
|
|
546
575
|
tooltip: "Orbit model configuration",
|
|
547
|
-
title: "Orbit",
|
|
576
|
+
title: "Orbit — Self-reviewing continuous execution",
|
|
577
|
+
enableLongRun: "Enable long-running execution",
|
|
578
|
+
orbitOff: "Orbit Off",
|
|
579
|
+
orbitSwitchLabel: "Orbit for this chat",
|
|
548
580
|
commander: "Commander",
|
|
549
581
|
executor: "Executor",
|
|
550
582
|
watchdog: "Watchdog",
|
|
583
|
+
commanderModel: "Commander model",
|
|
584
|
+
executorModel: "Executor model",
|
|
585
|
+
watchdogModel: "Watchdog model",
|
|
551
586
|
followsSession: "Follows current session model",
|
|
552
587
|
models: "Model",
|
|
553
588
|
effort: "Reasoning effort",
|
|
@@ -557,16 +592,22 @@ window.__ModuleLoader__.load({
|
|
|
557
592
|
loadFailed: "Failed to load models",
|
|
558
593
|
settingsFailed: "Failed to load Orbit settings",
|
|
559
594
|
saveFailed: "Failed to save Orbit settings",
|
|
560
|
-
|
|
595
|
+
applyOnNextSend: "Changes apply to the next message"
|
|
561
596
|
};
|
|
562
597
|
/** Chinese strings. */
|
|
563
598
|
const zh = {
|
|
564
599
|
triggerFallback: "Orbit",
|
|
565
600
|
tooltip: "Orbit 模型配置",
|
|
566
|
-
title: "Orbit",
|
|
601
|
+
title: "Orbit 让AI自我审核连续执行",
|
|
602
|
+
enableLongRun: "启用一键长执行",
|
|
603
|
+
orbitOff: "Orbit Off",
|
|
604
|
+
orbitSwitchLabel: "本会话 Orbit 开关",
|
|
567
605
|
commander: "指挥官",
|
|
568
606
|
executor: "执行员",
|
|
569
607
|
watchdog: "监控模型",
|
|
608
|
+
commanderModel: "指挥官模型",
|
|
609
|
+
executorModel: "执行员模型",
|
|
610
|
+
watchdogModel: "监控模型",
|
|
570
611
|
followsSession: "跟随当前会话模型",
|
|
571
612
|
models: "模型",
|
|
572
613
|
effort: "推理等级",
|
|
@@ -576,7 +617,7 @@ window.__ModuleLoader__.load({
|
|
|
576
617
|
loadFailed: "模型列表加载失败",
|
|
577
618
|
settingsFailed: "Orbit 设置加载失败",
|
|
578
619
|
saveFailed: "Orbit 设置保存失败",
|
|
579
|
-
|
|
620
|
+
applyOnNextSend: "更改将会在下一次发送时生效"
|
|
580
621
|
};
|
|
581
622
|
//#endregion
|
|
582
623
|
//#region client/index.ts
|
|
@@ -691,6 +732,16 @@ window.__ModuleLoader__.load({
|
|
|
691
732
|
},
|
|
692
733
|
selectModel: (selection) => available ? directory.select(selection).then(() => true, () => false) : Promise.resolve(false),
|
|
693
734
|
writeRole,
|
|
735
|
+
setOrbitEnabled: async (enabled) => {
|
|
736
|
+
const session = sessions.binding(sessionId)?.session;
|
|
737
|
+
if (session === void 0) return false;
|
|
738
|
+
try {
|
|
739
|
+
const result = await session.command(`/orbit-toggle ${enabled ? "on" : "off"}`);
|
|
740
|
+
return result.ok && result.value.matched;
|
|
741
|
+
} catch {
|
|
742
|
+
return false;
|
|
743
|
+
}
|
|
744
|
+
},
|
|
694
745
|
reloadSettings
|
|
695
746
|
};
|
|
696
747
|
};
|
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.
|
|
3
|
+
"version": "0.5.4",
|
|
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",
|