iterate-plugin 3.14.0 → 3.15.0
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 +4 -3
- package/README.zh-CN.md +2 -1
- package/lib/client.js +86 -3
- package/package.json +24 -20
- package/src/client/context-menu.ts +11 -2
- package/src/client/index.ts +46 -4
- package/src/client/shortcuts.ts +152 -0
package/README.md
CHANGED
|
@@ -115,6 +115,7 @@ Both modes share:
|
|
|
115
115
|
- **SettingsPanel** (`settings.section`) — theme toggle, triage-persistence notes, config-management guide (covers `validation.commands` / `language` / `personalization.known_intentional` with an explicit "empty validation.commands = unprotected" warning; a field picker generates the matching `iterate_config write` instruction), **v3.7: a 回读当前配置 (read back current config) button that closes the write → read-back loop** — it copies an `iterate_config read` instruction and, once the model echoes the result into the session, shows the current value of the selected field, runtime status overview (artifact layout + view/cleanup tool guide + disk-snapshot pull), one-click triage data reset.
|
|
116
116
|
- **Bilingual UI (v3.7)** — every piece of UI chrome (panels, empty states, tabs, badges, settings, stop-reason and completion labels) ships in 简体中文 and English. Pick the language under **Settings → 界面语言 / Interface language**; the choice persists in `localStorage` (`iterate-ui-lang`) and otherwise auto-detects from `navigator.language` (a stored choice always wins, and auto-detection never writes a preference). **Model-facing copy deliberately stays Chinese** — the `build*Instruction` payloads and the copy-paste guides are contract text mirroring `SKILL.md`, so the two expanded guides carry a 「中文(粘贴给模型的契约文案)」 chip instead of looking like a missed translation.
|
|
117
117
|
- **Right-click menu (v3.14, dsh ≥ 0.2.2-alpha.3)** — the iterate entry points now also hang off the **native unified context menus**: right-click a **Session** in the sidebar to `打开 iterate 质量指挥中心` (expands the docked observatory and jumps to the live stream), copy the preflight / dry-run / checkpoint-resume / disk-snapshot instructions, or right-click a **file** (with line) / **directory** to copy a *scoped* dry-run review instruction. Bilingual labels, copy-failure recovery guidance, and a shared success toast; a host without `contextMenus` degrades to no menu items (the docked panel remains the entry point).
|
|
118
|
+
- **Keyboard shortcut (v3.15, dsh ≥ 0.2.2-rc.1)** — the iterate command center is now one keystroke away: **Cmd/Ctrl+Shift+O** opens the docked observatory from anywhere in a session (dsh's new unified keyboard-command capability). The shortcut is registered on dsh's `shortcuts` service with the same command id the right-click "open command center" action carries, so the context menu shows the live keycap and dsh's own shortcut settings page lists it as an editable binding. A host that predates the `shortcuts` service degrades to no keyboard command (the docked panel and the context menu remain the entry points); a conflicting default binding is logged and skipped rather than breaking the plugin.
|
|
118
119
|
|
|
119
120
|
The UI layer is **defensive by design**: it degrades gracefully if any of `slots` / `theme` / `React` is unavailable — it never crashes the client.
|
|
120
121
|
|
|
@@ -348,12 +349,12 @@ All runtime state lives under `.iterate/` at the project root (can be excluded v
|
|
|
348
349
|
`0.2.0-rc.2`, `0.2.1-alpha.1`, `0.2.1-alpha.2`, `0.2.1-alpha.3`,
|
|
349
350
|
`0.2.1-alpha.4`, `0.2.1-alpha.5`, `0.2.1-rc.1`, `0.2.1-rc.2`,
|
|
350
351
|
`0.2.1-rc.3`, `0.2.1-rc.4`, `0.2.1-rc.5`, `0.2.2-alpha.2`,
|
|
351
|
-
`0.2.2-alpha.3` (
|
|
352
|
+
`0.2.2-alpha.3`, `0.2.2-rc.1` (30 releases, `0.1.1-rc.1` … `0.2.2-rc.1`).
|
|
352
353
|
The plugin is built against `@deepseek-ai/dsh-tools` /
|
|
353
|
-
`@deepseek-ai/dsh-util-values` `0.2.2-
|
|
354
|
+
`@deepseek-ai/dsh-util-values` `0.2.2-rc.1` and uses only public contracts
|
|
354
355
|
(tool registration, client slots + theme, bundle patch, and — since v3.9 —
|
|
355
356
|
the session state-record API for session-linked run records).
|
|
356
|
-
It also declares `engines.dsh` (`>=0.1.1-rc.1`) so the dsh ≥ 0.2.2-
|
|
357
|
+
It also declares `engines.dsh` (`>=0.1.1-rc.1`) so the dsh ≥ 0.2.2-rc.1
|
|
357
358
|
runtime evaluates compatibility from that single range (earlier runtimes fall
|
|
358
359
|
back to the `@deepseek-ai/dsh-*` peer versions listed in `package.json`).
|
|
359
360
|
- **Disposable-Profile evidence (real, `dsh` CLI `0.1.1-rc.1`)**: on a temp
|
package/README.zh-CN.md
CHANGED
|
@@ -114,6 +114,7 @@ dsh plugin --profile web add iterate-plugin
|
|
|
114
114
|
- **iterate 设置区 `SettingsPanel`**(`settings.section`)— 主题开关、分诊持久化说明、配置管理指引(覆盖 `validation.commands` / `language` / `personalization.known_intentional`,并显式警告"validation.commands 为空 = 本轮不受任何测试保护";字段选择器可生成对应的 `iterate_config write` 指令),**v3.7:新增「回读当前配置」补齐写入 → 回读闭环**——复制 `iterate_config read` 指令,模型把结果回显进会话后,卡片就地展示所选字段的当前值;运行时状态概览(产物布局 + 查看/清理工具指引 + 磁盘快照拉取)、一键清空分诊数据。
|
|
115
115
|
- **双语 UI(v3.7)**——所有界面文案(面板、空态、页签、徽标、设置、停止原因与完成汇总)均提供简体中文与 English。在**设置 → 界面语言 / Interface language**切换;选择持久化在 `localStorage`(`iterate-ui-lang`),否则按 `navigator.language` 自动探测(已保存的选择始终优先,且自动探测不会写入偏好)。**面向模型的文案有意保持中文**——`build*Instruction` 指令与可粘贴指引是与 `SKILL.md` 对齐的契约文本,因此两处展开的指引带「中文(粘贴给模型的契约文案)」标签,避免被误当成漏翻。
|
|
116
116
|
- **右键菜单(v3.14,dsh ≥ 0.2.2-alpha.3)**——iterate 入口现在也挂在**原生统一右键菜单**上:右键侧边栏的**会话**可 `打开 iterate 质量指挥中心`(展开停靠观测台并跳到实时流)、复制预检 / dry-run / 断点恢复 / 磁盘快照指令;右键**文件**(带行号)/ **目录**可复制**定向** dry-run 审查指令(仅审查该文件/目录)。双语标签、复制失败的自救指引、共享成功 toast;宿主无 `contextMenus` 时降级为无菜单项(停靠面板仍是入口)。
|
|
117
|
+
- **键盘快捷键(v3.15,dsh ≥ 0.2.2-rc.1)**——iterate 指挥中心现在一键可达:任意会话内按 **Cmd/Ctrl+Shift+O** 即可打开停靠观测台(dsh 新增的统一键盘命令能力)。快捷键注册在 dsh 的 `shortcuts` 服务上,与右键「打开指挥中心」动作共用同一命令 id,因此右键菜单会显示实时键帽、dsh 自身的快捷键设置页也会将其列为可编辑绑定。宿主早于 `shortcuts` 服务时降级为无键盘命令(停靠面板与右键菜单仍是入口);默认绑定冲突时仅记录并跳过,绝不拖垮插件。
|
|
117
118
|
|
|
118
119
|
UI 层为**防御式设计**:`slots` / `theme` / `React` 任一不可用时自动降级,不会崩溃客户端。
|
|
119
120
|
|
|
@@ -337,7 +338,7 @@ validation:
|
|
|
337
338
|
### Node.js / DSH 兼容范围
|
|
338
339
|
|
|
339
340
|
- **Node.js**:`>=20`(`package.json` 的 `engines.node`)。
|
|
340
|
-
- **DSH**:`package.json` 的 `dsh.compatibility.dshReleases` 字段是发布兼容性的**唯一真源**(本 README 逐字镜像该字段;两者需同步更新)。以下官方版本声明为 `compatible`:`0.1.1-rc.1`、`0.1.2-alpha.4`、`0.1.2-alpha.5`、`0.1.2-rc.1`、`0.1.3-alpha.1`、`0.1.5-alpha.1`、`0.1.5-alpha.2`、`0.1.5-rc.1`、`0.1.5-rc.2`、`0.1.6-alpha.1`、`0.1.6-alpha.2`、`0.1.7-alpha.1`、`0.1.7-alpha.2`、`0.1.7-rc.1`、`0.1.7-rc.2`、`0.2.0-rc.1`、`0.2.0-rc.2`、`0.2.1-alpha.1`、`0.2.1-alpha.2`、`0.2.1-alpha.3`、`0.2.1-alpha.4`、`0.2.1-alpha.5`、`0.2.1-rc.1`、`0.2.1-rc.2`、`0.2.1-rc.3`、`0.2.1-rc.4`、`0.2.1-rc.5`、`0.2.2-alpha.2`、`0.2.2-alpha.3`(共
|
|
341
|
+
- **DSH**:`package.json` 的 `dsh.compatibility.dshReleases` 字段是发布兼容性的**唯一真源**(本 README 逐字镜像该字段;两者需同步更新)。以下官方版本声明为 `compatible`:`0.1.1-rc.1`、`0.1.2-alpha.4`、`0.1.2-alpha.5`、`0.1.2-rc.1`、`0.1.3-alpha.1`、`0.1.5-alpha.1`、`0.1.5-alpha.2`、`0.1.5-rc.1`、`0.1.5-rc.2`、`0.1.6-alpha.1`、`0.1.6-alpha.2`、`0.1.7-alpha.1`、`0.1.7-alpha.2`、`0.1.7-rc.1`、`0.1.7-rc.2`、`0.2.0-rc.1`、`0.2.0-rc.2`、`0.2.1-alpha.1`、`0.2.1-alpha.2`、`0.2.1-alpha.3`、`0.2.1-alpha.4`、`0.2.1-alpha.5`、`0.2.1-rc.1`、`0.2.1-rc.2`、`0.2.1-rc.3`、`0.2.1-rc.4`、`0.2.1-rc.5`、`0.2.2-alpha.2`、`0.2.2-alpha.3`、`0.2.2-rc.1`(共 30 个版本,`0.1.1-rc.1` … `0.2.2-rc.1`)。插件基于 `@deepseek-ai/dsh-tools` / `@deepseek-ai/dsh-util-values` `0.2.2-rc.1` 构建,只使用公开契约(工具注册、客户端 slots + theme、bundle patch,以及 v3.9 起的会话状态记录接口)。插件同时声明 `engines.dsh`(`>=0.1.1-rc.1`),使 dsh ≥ 0.2.2-rc.1 运行时只依据该范围判断兼容性(更早的运行时仍回退校验 `package.json` 中列出的 `@deepseek-ai/dsh-*` peer 版本)。
|
|
341
342
|
- **一次性 Profile 证据(真实,`dsh` CLI `0.1.1-rc.1`)**:在临时 `$DSH_HOME` 下
|
|
342
343
|
`dsh plugin --profile <p> add <本仓库>` 约 449ms 完成安装;`dsh --profile <p>
|
|
343
344
|
--dump-config` 正确合成 `iterate-plugin` bundle patch;`dsh plugin --profile <p>
|
package/lib/client.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* iterate-plugin client source-digest:
|
|
1
|
+
/* iterate-plugin client source-digest: 4f3bcac77cef83b80ea8fa2387f254121b9f94f7a2a2ca9422dd6342581c3463 */
|
|
2
2
|
window.__ModuleLoader__.load({ id: "iterate-plugin", factory: (require) => {
|
|
3
3
|
var module = { exports: {} };
|
|
4
4
|
var exports = module.exports;
|
|
@@ -2865,6 +2865,53 @@ function subscribeSession(listener) {
|
|
|
2865
2865
|
};
|
|
2866
2866
|
}
|
|
2867
2867
|
|
|
2868
|
+
// src/client/shortcuts.ts
|
|
2869
|
+
var OBSERVATORY_SHORTCUT_ID = "iterate.open-observatory";
|
|
2870
|
+
var DEFAULT_BINDING = { code: "KeyO", modifiers: ["primary", "shift"] };
|
|
2871
|
+
var DEFAULT_PROFILES = [
|
|
2872
|
+
"desktop:macos",
|
|
2873
|
+
"desktop:windows",
|
|
2874
|
+
"desktop:linux",
|
|
2875
|
+
"web:macos",
|
|
2876
|
+
"web:windows",
|
|
2877
|
+
"web:linux"
|
|
2878
|
+
];
|
|
2879
|
+
var OBSERVATORY_DEFAULT_BINDINGS = Object.fromEntries(
|
|
2880
|
+
DEFAULT_PROFILES.map((profile) => [profile, DEFAULT_BINDING])
|
|
2881
|
+
);
|
|
2882
|
+
function buildObservatoryShortcutCommand(deps) {
|
|
2883
|
+
const label = () => deps.lang() === "zh" ? "\u6253\u5F00 iterate \u89C2\u6D4B\u53F0" : "Open iterate observatory";
|
|
2884
|
+
return {
|
|
2885
|
+
id: OBSERVATORY_SHORTCUT_ID,
|
|
2886
|
+
label,
|
|
2887
|
+
aliases: [],
|
|
2888
|
+
defaults: OBSERVATORY_DEFAULT_BINDINGS,
|
|
2889
|
+
regions: ["page", "editable", "terminal"],
|
|
2890
|
+
modals: [],
|
|
2891
|
+
resolve: () => ({ status: "handled", run: () => deps.openObservatory({}) })
|
|
2892
|
+
};
|
|
2893
|
+
}
|
|
2894
|
+
function registerIterateShortcuts(service, deps) {
|
|
2895
|
+
try {
|
|
2896
|
+
const dispose = service.register(buildObservatoryShortcutCommand(deps));
|
|
2897
|
+
let done = false;
|
|
2898
|
+
return () => {
|
|
2899
|
+
if (done) return;
|
|
2900
|
+
done = true;
|
|
2901
|
+
try {
|
|
2902
|
+
dispose();
|
|
2903
|
+
} catch {
|
|
2904
|
+
}
|
|
2905
|
+
};
|
|
2906
|
+
} catch (err) {
|
|
2907
|
+
if (typeof console !== "undefined" && typeof console.error === "function") {
|
|
2908
|
+
console.error("[iterate-ui] keyboard-command registration failed; continuing without it.", err);
|
|
2909
|
+
}
|
|
2910
|
+
return () => {
|
|
2911
|
+
};
|
|
2912
|
+
}
|
|
2913
|
+
}
|
|
2914
|
+
|
|
2868
2915
|
// src/client/context-menu.ts
|
|
2869
2916
|
var OBS_OPEN_EVENT = "iterate-plugin:open-observatory";
|
|
2870
2917
|
function openObservatory(detail) {
|
|
@@ -2906,14 +2953,14 @@ function buildSessionMenuEntries(lang, opts) {
|
|
|
2906
2953
|
function buildFileMenuEntries(lang) {
|
|
2907
2954
|
return [
|
|
2908
2955
|
{ id: "iterate.review-file", label: pick("iterate.review-file", lang) },
|
|
2909
|
-
{ id: "iterate.open-panel", label: pick("iterate.open-panel", lang) }
|
|
2956
|
+
{ id: "iterate.open-panel", label: pick("iterate.open-panel", lang), openTab: "live" }
|
|
2910
2957
|
];
|
|
2911
2958
|
}
|
|
2912
2959
|
function buildDirectoryMenuEntries(lang) {
|
|
2913
2960
|
return [
|
|
2914
2961
|
{ id: "iterate.review-dir", label: pick("iterate.review-dir", lang) },
|
|
2915
2962
|
{ id: "iterate.preflight", label: pick("iterate.preflight", lang), separatorBefore: true },
|
|
2916
|
-
{ id: "iterate.open-panel", label: pick("iterate.open-panel", lang) }
|
|
2963
|
+
{ id: "iterate.open-panel", label: pick("iterate.open-panel", lang), openTab: "live" }
|
|
2917
2964
|
];
|
|
2918
2965
|
}
|
|
2919
2966
|
function toAction(entry, deps, lang) {
|
|
@@ -2925,6 +2972,7 @@ function toAction(entry, deps, lang) {
|
|
|
2925
2972
|
};
|
|
2926
2973
|
if (entry.disabled) action.disabled = true;
|
|
2927
2974
|
if (entry.openTab) {
|
|
2975
|
+
action.commandId = OBSERVATORY_SHORTCUT_ID;
|
|
2928
2976
|
action.run = () => {
|
|
2929
2977
|
deps.openPanel({ tab: entry.openTab });
|
|
2930
2978
|
deps.notify(lang === "zh" ? "\u5DF2\u6253\u5F00 iterate \u89C2\u6D4B\u53F0\u3002" : "Iterate observatory opened.", true);
|
|
@@ -3439,6 +3487,14 @@ function readContextMenus(ctx) {
|
|
|
3439
3487
|
if (typeof svc.notify !== "function") return void 0;
|
|
3440
3488
|
return svc;
|
|
3441
3489
|
}
|
|
3490
|
+
function readShortcuts(ctx) {
|
|
3491
|
+
if (!ctx || typeof ctx.get !== "function") return void 0;
|
|
3492
|
+
const raw = ctx.get("shortcuts", false);
|
|
3493
|
+
if (!raw || typeof raw !== "object") return void 0;
|
|
3494
|
+
const svc = raw;
|
|
3495
|
+
if (typeof svc.register !== "function") return void 0;
|
|
3496
|
+
return svc;
|
|
3497
|
+
}
|
|
3442
3498
|
function sessionIdOfSession(session) {
|
|
3443
3499
|
if (!session || typeof session !== "object") return null;
|
|
3444
3500
|
const s = session;
|
|
@@ -3466,6 +3522,7 @@ var themeDisposer = null;
|
|
|
3466
3522
|
var themeListener = null;
|
|
3467
3523
|
var themeEnabled = true;
|
|
3468
3524
|
var menuDisposer = null;
|
|
3525
|
+
var shortcutDisposer = null;
|
|
3469
3526
|
var roundPulseListeners = [];
|
|
3470
3527
|
function emitRoundPulse(round, converged) {
|
|
3471
3528
|
const payload = { round, converged: converged === true };
|
|
@@ -6446,6 +6503,32 @@ function apply(ctx) {
|
|
|
6446
6503
|
}
|
|
6447
6504
|
}
|
|
6448
6505
|
}
|
|
6506
|
+
{
|
|
6507
|
+
const shortcutSvc = readShortcuts(ctx);
|
|
6508
|
+
if (shortcutSvc) {
|
|
6509
|
+
if (shortcutDisposer) {
|
|
6510
|
+
try {
|
|
6511
|
+
shortcutDisposer();
|
|
6512
|
+
} catch {
|
|
6513
|
+
}
|
|
6514
|
+
shortcutDisposer = null;
|
|
6515
|
+
}
|
|
6516
|
+
shortcutDisposer = registerIterateShortcuts(shortcutSvc, {
|
|
6517
|
+
openObservatory: (detail) => {
|
|
6518
|
+
openObservatory(detail);
|
|
6519
|
+
},
|
|
6520
|
+
lang: () => getLang()
|
|
6521
|
+
});
|
|
6522
|
+
if (typeof ctx.effect === "function") {
|
|
6523
|
+
ctx.effect(() => {
|
|
6524
|
+
if (shortcutDisposer) {
|
|
6525
|
+
shortcutDisposer();
|
|
6526
|
+
shortcutDisposer = null;
|
|
6527
|
+
}
|
|
6528
|
+
});
|
|
6529
|
+
}
|
|
6530
|
+
}
|
|
6531
|
+
}
|
|
6449
6532
|
registerThemeListener(ctx);
|
|
6450
6533
|
}
|
|
6451
6534
|
return module.exports; } });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "iterate-plugin",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.15.0",
|
|
4
4
|
"description": "dsh plugin that turns the iterate skill into an autonomous closed-loop harness with quality command center and experience bank (v3.3). Features: plan -> parallel review xN -> atomic fixes -> validate -> loop -> auto-stop, plus dry-run pure-review mode, quality gate compute/persist, writable experience bank, defense events stream (record + bilingual labels), and native command buttons — with a live quality command center (F8/F9/F10 render real session data), §8 assign-fix instruction, and task_mode indicator wired end to end.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -56,7 +56,8 @@
|
|
|
56
56
|
"0.2.1-rc.4": "compatible",
|
|
57
57
|
"0.2.1-rc.5": "compatible",
|
|
58
58
|
"0.2.2-alpha.2": "compatible",
|
|
59
|
-
"0.2.2-alpha.3": "compatible"
|
|
59
|
+
"0.2.2-alpha.3": "compatible",
|
|
60
|
+
"0.2.2-rc.1": "compatible"
|
|
60
61
|
}
|
|
61
62
|
},
|
|
62
63
|
"client": {
|
|
@@ -93,14 +94,14 @@
|
|
|
93
94
|
"test:validate": "tsx --test test/validate.test.ts"
|
|
94
95
|
},
|
|
95
96
|
"dependencies": {
|
|
96
|
-
"iterate-kernel": "0.1.
|
|
97
|
+
"iterate-kernel": "0.1.10",
|
|
97
98
|
"js-yaml": "4.3.2"
|
|
98
99
|
},
|
|
99
100
|
"peerDependencies": {
|
|
100
101
|
"@deepseek-ai/cordis": "^4.0.5-alpha.2 || ~4.0.6-alpha.1 || ~4.0.6-alpha.2",
|
|
101
|
-
"@deepseek-ai/dsh-session": "0.1.1-rc.1 || 0.1.2-alpha.4 || 0.1.2-alpha.5 || 0.1.2-rc.1 || 0.1.5-alpha.1 || 0.1.5-alpha.2 || 0.1.5-rc.1 || 0.1.5-rc.2 || 0.1.6-alpha.1 || 0.1.6-alpha.2 || 0.1.7-alpha.1 || 0.1.7-alpha.2 || 0.1.7-rc.1 || 0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-alpha.1 || 0.2.1-alpha.2 || 0.2.1-alpha.3 || 0.2.1-alpha.4 || 0.2.1-alpha.5 || 0.2.1-rc.1 || 0.2.1-rc.2 || 0.2.1-rc.3 || 0.2.1-rc.4 || 0.2.1-rc.5 || 0.2.2-alpha.2 || 0.2.2-alpha.3",
|
|
102
|
-
"@deepseek-ai/dsh-tools": "0.1.1-rc.1 || 0.1.2-alpha.4 || 0.1.2-alpha.5 || 0.1.2-rc.1 || 0.1.5-alpha.1 || 0.1.5-alpha.2 || 0.1.5-rc.1 || 0.1.5-rc.2 || 0.1.6-alpha.1 || 0.1.6-alpha.2 || 0.1.7-alpha.1 || 0.1.7-alpha.2 || 0.1.7-rc.1 || 0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-alpha.1 || 0.2.1-alpha.2 || 0.2.1-alpha.3 || 0.2.1-alpha.4 || 0.2.1-alpha.5 || 0.2.1-rc.1 || 0.2.1-rc.2 || 0.2.1-rc.3 || 0.2.1-rc.4 || 0.2.1-rc.5 || 0.2.2-alpha.2 || 0.2.2-alpha.3",
|
|
103
|
-
"@deepseek-ai/dsh-util-values": "0.1.2-alpha.5 || 0.1.2-rc.1 || 0.1.5-alpha.1 || 0.1.5-alpha.2 || 0.1.5-rc.1 || 0.1.5-rc.2 || 0.1.6-alpha.1 || 0.1.6-alpha.2 || 0.1.7-alpha.1 || 0.1.7-alpha.2 || 0.1.7-rc.1 || 0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-alpha.1 || 0.2.1-alpha.2 || 0.2.1-alpha.3 || 0.2.1-alpha.4 || 0.2.1-alpha.5 || 0.2.1-rc.1 || 0.2.1-rc.2 || 0.2.1-rc.3 || 0.2.1-rc.4 || 0.2.1-rc.5 || 0.2.2-alpha.2 || 0.2.2-alpha.3"
|
|
102
|
+
"@deepseek-ai/dsh-session": "0.1.1-rc.1 || 0.1.2-alpha.4 || 0.1.2-alpha.5 || 0.1.2-rc.1 || 0.1.5-alpha.1 || 0.1.5-alpha.2 || 0.1.5-rc.1 || 0.1.5-rc.2 || 0.1.6-alpha.1 || 0.1.6-alpha.2 || 0.1.7-alpha.1 || 0.1.7-alpha.2 || 0.1.7-rc.1 || 0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-alpha.1 || 0.2.1-alpha.2 || 0.2.1-alpha.3 || 0.2.1-alpha.4 || 0.2.1-alpha.5 || 0.2.1-rc.1 || 0.2.1-rc.2 || 0.2.1-rc.3 || 0.2.1-rc.4 || 0.2.1-rc.5 || 0.2.2-alpha.2 || 0.2.2-alpha.3 || 0.2.2-rc.1",
|
|
103
|
+
"@deepseek-ai/dsh-tools": "0.1.1-rc.1 || 0.1.2-alpha.4 || 0.1.2-alpha.5 || 0.1.2-rc.1 || 0.1.5-alpha.1 || 0.1.5-alpha.2 || 0.1.5-rc.1 || 0.1.5-rc.2 || 0.1.6-alpha.1 || 0.1.6-alpha.2 || 0.1.7-alpha.1 || 0.1.7-alpha.2 || 0.1.7-rc.1 || 0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-alpha.1 || 0.2.1-alpha.2 || 0.2.1-alpha.3 || 0.2.1-alpha.4 || 0.2.1-alpha.5 || 0.2.1-rc.1 || 0.2.1-rc.2 || 0.2.1-rc.3 || 0.2.1-rc.4 || 0.2.1-rc.5 || 0.2.2-alpha.2 || 0.2.2-alpha.3 || 0.2.2-rc.1",
|
|
104
|
+
"@deepseek-ai/dsh-util-values": "0.1.2-alpha.5 || 0.1.2-rc.1 || 0.1.5-alpha.1 || 0.1.5-alpha.2 || 0.1.5-rc.1 || 0.1.5-rc.2 || 0.1.6-alpha.1 || 0.1.6-alpha.2 || 0.1.7-alpha.1 || 0.1.7-alpha.2 || 0.1.7-rc.1 || 0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-alpha.1 || 0.2.1-alpha.2 || 0.2.1-alpha.3 || 0.2.1-alpha.4 || 0.2.1-alpha.5 || 0.2.1-rc.1 || 0.2.1-rc.2 || 0.2.1-rc.3 || 0.2.1-rc.4 || 0.2.1-rc.5 || 0.2.2-alpha.2 || 0.2.2-alpha.3 || 0.2.2-rc.1"
|
|
104
105
|
},
|
|
105
106
|
"peerDependenciesMeta": {
|
|
106
107
|
"@deepseek-ai/cordis": {
|
|
@@ -117,20 +118,20 @@
|
|
|
117
118
|
}
|
|
118
119
|
},
|
|
119
120
|
"devDependencies": {
|
|
120
|
-
"@deepseek-ai/cordis": "4.0
|
|
121
|
-
"@deepseek-ai/dsh-agent": "0.2.2-
|
|
122
|
-
"@deepseek-ai/dsh-jobs": "0.2.2-
|
|
123
|
-
"@deepseek-ai/dsh-llm": "0.2.2-
|
|
124
|
-
"@deepseek-ai/dsh-ptc-runtime": "0.2.2-
|
|
125
|
-
"@deepseek-ai/dsh-sandbox": "0.2.2-
|
|
126
|
-
"@deepseek-ai/dsh-sandbox-policy": "0.2.2-
|
|
127
|
-
"@deepseek-ai/dsh-scope": "0.2.2-
|
|
128
|
-
"@deepseek-ai/dsh-session": "0.2.2-
|
|
129
|
-
"@deepseek-ai/dsh-system-prompt": "0.2.2-
|
|
130
|
-
"@deepseek-ai/dsh-tools": "0.2.2-
|
|
131
|
-
"@deepseek-ai/dsh-user-approval": "0.2.2-
|
|
132
|
-
"@deepseek-ai/dsh-util-values": "0.2.2-
|
|
133
|
-
"@deepseek-ai/dsh-working-directory": "0.2.2-
|
|
121
|
+
"@deepseek-ai/cordis": "4.1.0",
|
|
122
|
+
"@deepseek-ai/dsh-agent": "0.2.2-rc.1",
|
|
123
|
+
"@deepseek-ai/dsh-jobs": "0.2.2-rc.1",
|
|
124
|
+
"@deepseek-ai/dsh-llm": "0.2.2-rc.1",
|
|
125
|
+
"@deepseek-ai/dsh-ptc-runtime": "0.2.2-rc.1",
|
|
126
|
+
"@deepseek-ai/dsh-sandbox": "0.2.2-rc.1",
|
|
127
|
+
"@deepseek-ai/dsh-sandbox-policy": "0.2.2-rc.1",
|
|
128
|
+
"@deepseek-ai/dsh-scope": "0.2.2-rc.1",
|
|
129
|
+
"@deepseek-ai/dsh-session": "0.2.2-rc.1",
|
|
130
|
+
"@deepseek-ai/dsh-system-prompt": "0.2.2-rc.1",
|
|
131
|
+
"@deepseek-ai/dsh-tools": "0.2.2-rc.1",
|
|
132
|
+
"@deepseek-ai/dsh-user-approval": "0.2.2-rc.1",
|
|
133
|
+
"@deepseek-ai/dsh-util-values": "0.2.2-rc.1",
|
|
134
|
+
"@deepseek-ai/dsh-working-directory": "0.2.2-rc.1",
|
|
134
135
|
"@types/js-yaml": "4.0.9",
|
|
135
136
|
"@types/node": "22.15.0",
|
|
136
137
|
"@types/react": "19.2.2",
|
|
@@ -144,5 +145,8 @@
|
|
|
144
145
|
"engines": {
|
|
145
146
|
"node": ">=20",
|
|
146
147
|
"dsh": ">=0.1.1-rc.1"
|
|
148
|
+
},
|
|
149
|
+
"overrides": {
|
|
150
|
+
"zod": "4.6.7"
|
|
147
151
|
}
|
|
148
152
|
}
|
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
*/
|
|
41
41
|
|
|
42
42
|
import { getLang, type Lang } from './i18n.ts'
|
|
43
|
+
import { OBSERVATORY_SHORTCUT_ID } from './shortcuts.ts'
|
|
43
44
|
|
|
44
45
|
// ─── Structural service types (defensive; mirrors SlotsService) ──────────────
|
|
45
46
|
//
|
|
@@ -63,6 +64,8 @@ export interface IterateMenuAction {
|
|
|
63
64
|
separatorBefore?: boolean
|
|
64
65
|
/** Localized operation + recovery guidance when `run` rejects. */
|
|
65
66
|
failureLabel?: string
|
|
67
|
+
/** Live-shortcut display for an action backed by a registered command. */
|
|
68
|
+
commandId?: string
|
|
66
69
|
run?: () => void | Promise<void>
|
|
67
70
|
}
|
|
68
71
|
|
|
@@ -168,7 +171,7 @@ export function buildSessionMenuEntries(lang: Lang, opts?: { hasCheckpoint?: boo
|
|
|
168
171
|
export function buildFileMenuEntries(lang: Lang): MenuEntry[] {
|
|
169
172
|
return [
|
|
170
173
|
{ id: 'iterate.review-file', label: pick('iterate.review-file', lang) },
|
|
171
|
-
{ id: 'iterate.open-panel', label: pick('iterate.open-panel', lang) },
|
|
174
|
+
{ id: 'iterate.open-panel', label: pick('iterate.open-panel', lang), openTab: 'live' },
|
|
172
175
|
]
|
|
173
176
|
}
|
|
174
177
|
|
|
@@ -177,7 +180,7 @@ export function buildDirectoryMenuEntries(lang: Lang): MenuEntry[] {
|
|
|
177
180
|
return [
|
|
178
181
|
{ id: 'iterate.review-dir', label: pick('iterate.review-dir', lang) },
|
|
179
182
|
{ id: 'iterate.preflight', label: pick('iterate.preflight', lang), separatorBefore: true },
|
|
180
|
-
{ id: 'iterate.open-panel', label: pick('iterate.open-panel', lang) },
|
|
183
|
+
{ id: 'iterate.open-panel', label: pick('iterate.open-panel', lang), openTab: 'live' },
|
|
181
184
|
]
|
|
182
185
|
}
|
|
183
186
|
|
|
@@ -203,6 +206,12 @@ function toAction(entry: MenuEntry, deps: IterateMenuDeps, lang: Lang): IterateM
|
|
|
203
206
|
}
|
|
204
207
|
if (entry.disabled) action.disabled = true
|
|
205
208
|
if (entry.openTab) {
|
|
209
|
+
// The "open command center" action is backed by the registered
|
|
210
|
+
// `iterate.open-observatory` keyboard command, so the context menu shows
|
|
211
|
+
// its live keycap (Cmd/Ctrl+Shift+O) — the shortcut is discoverable in the
|
|
212
|
+
// same place the user would look for it. Display only: selection still
|
|
213
|
+
// runs this action's `run`.
|
|
214
|
+
action.commandId = OBSERVATORY_SHORTCUT_ID
|
|
206
215
|
action.run = () => {
|
|
207
216
|
deps.openPanel({ tab: entry.openTab })
|
|
208
217
|
deps.notify(lang === 'zh' ? '已打开 iterate 观测台。' : 'Iterate observatory opened.', true)
|
package/src/client/index.ts
CHANGED
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
* 4. iterate theme skin -> theme.overrideTokens
|
|
25
25
|
* 5. Progress event linkage -> ctx.on('slots/changed') + round-pulse + shell.overlay capsule
|
|
26
26
|
* 6. Settings page section -> settings.section
|
|
27
|
-
* 7. Runtime observatory -> conversation.input.dock (order 91, F1–
|
|
27
|
+
* 7. Runtime observatory -> conversation.input.dock (order 91, F1–F11 board)
|
|
28
28
|
*
|
|
29
29
|
* Design notes:
|
|
30
30
|
* - All styles are injected via one <style> tag; colors use dsh token
|
|
@@ -141,6 +141,11 @@ import {
|
|
|
141
141
|
type ContextMenusService,
|
|
142
142
|
type ObsOpenDetail,
|
|
143
143
|
} from './context-menu.ts'
|
|
144
|
+
// v3.15: keyboard-command contribution (dsh 0.2.2-rc.1 unified shortcuts).
|
|
145
|
+
import {
|
|
146
|
+
registerIterateShortcuts,
|
|
147
|
+
type ShortcutsService,
|
|
148
|
+
} from './shortcuts.ts'
|
|
144
149
|
|
|
145
150
|
// ─── Module contract ─────────────────────────────────────────────────────────
|
|
146
151
|
|
|
@@ -932,6 +937,21 @@ function readContextMenus(ctx: ClientContext): ContextMenusService | undefined {
|
|
|
932
937
|
return svc as unknown as ContextMenusService
|
|
933
938
|
}
|
|
934
939
|
|
|
940
|
+
/**
|
|
941
|
+
* Resolve the dsh 0.2.2-rc.1 `shortcuts` service. Like contextMenus it is NOT
|
|
942
|
+
* in the typed `ClientContext`, so it is probed through `ctx.get` and
|
|
943
|
+
* structurally validated. A host that predates the capability returns
|
|
944
|
+
* `undefined` and the plugin simply has no keyboard command.
|
|
945
|
+
*/
|
|
946
|
+
function readShortcuts(ctx: ClientContext): ShortcutsService | undefined {
|
|
947
|
+
if (!ctx || typeof ctx.get !== 'function') return undefined
|
|
948
|
+
const raw = ctx.get('shortcuts', false)
|
|
949
|
+
if (!raw || typeof raw !== 'object') return undefined
|
|
950
|
+
const svc = raw as { register?: unknown }
|
|
951
|
+
if (typeof svc.register !== 'function') return undefined
|
|
952
|
+
return svc as unknown as ShortcutsService
|
|
953
|
+
}
|
|
954
|
+
|
|
935
955
|
/**
|
|
936
956
|
* Extract a session identifier from an opaque host-shaped session object.
|
|
937
957
|
* The slot `props.session` shape is host-defined; this probes the common
|
|
@@ -967,6 +987,8 @@ let themeListener: (() => void) | null = null
|
|
|
967
987
|
let themeEnabled = true
|
|
968
988
|
/** v3.14: right-click menu contribution disposer (re-apply() never stacks). */
|
|
969
989
|
let menuDisposer: (() => void) | null = null
|
|
990
|
+
/** v3.15: keyboard-command contribution disposer (re-apply() never stacks). */
|
|
991
|
+
let shortcutDisposer: (() => void) | null = null
|
|
970
992
|
const roundPulseListeners: Array<(payload: { round: number; converged: boolean }) => void> = []
|
|
971
993
|
|
|
972
994
|
/**
|
|
@@ -2232,7 +2254,7 @@ function SettingsPanel(props: SlotProps) {
|
|
|
2232
2254
|
)
|
|
2233
2255
|
}
|
|
2234
2256
|
|
|
2235
|
-
// ─── Runtime observatory panel (F1–
|
|
2257
|
+
// ─── Runtime observatory panel (F1–F11) ─────────────────────────────────────
|
|
2236
2258
|
|
|
2237
2259
|
/** Tab id → label for the observatory's eleven panels. Live (real-time
|
|
2238
2260
|
* activity stream) is first so it is the default view; F-panels follow.
|
|
@@ -2359,7 +2381,7 @@ function buildObsFixInstruction(f: ObsFinding): string {
|
|
|
2359
2381
|
}
|
|
2360
2382
|
|
|
2361
2383
|
/**
|
|
2362
|
-
* ObservatoryPanel:
|
|
2384
|
+
* ObservatoryPanel: a 12-tab runtime observatory that renders the latest
|
|
2363
2385
|
* iterate_transcript manifest found in the dsh session stream. The panel is
|
|
2364
2386
|
* collapsed by default to a single entry row so it never re-occludes the main
|
|
2365
2387
|
* convergence dashboard; clicking expands it into tabs. Every field read is
|
|
@@ -3907,7 +3929,7 @@ export function apply(ctx: ClientContext): void {
|
|
|
3907
3929
|
),
|
|
3908
3930
|
)
|
|
3909
3931
|
|
|
3910
|
-
// F1–
|
|
3932
|
+
// F1–F11: runtime observatory board (order 91, below the dashboard's 90 so
|
|
3911
3933
|
// its collapsed header never occludes the main convergence dashboard).
|
|
3912
3934
|
slotsSvc.inject('conversation.input.dock', () =>
|
|
3913
3935
|
slotsSvc?.register(
|
|
@@ -3984,6 +4006,26 @@ export function apply(ctx: ClientContext): void {
|
|
|
3984
4006
|
}
|
|
3985
4007
|
}
|
|
3986
4008
|
|
|
4009
|
+
// 5c. v3.15: keyboard-command contribution (dsh 0.2.2-rc.1 unified
|
|
4010
|
+
// shortcuts). Optional service: a host that predates the capability has no
|
|
4011
|
+
// keyboard command, and the docked observatory stays the entry point.
|
|
4012
|
+
{
|
|
4013
|
+
const shortcutSvc = readShortcuts(ctx)
|
|
4014
|
+
if (shortcutSvc) {
|
|
4015
|
+
if (shortcutDisposer) {
|
|
4016
|
+
try { shortcutDisposer() } catch { /* noop */ }
|
|
4017
|
+
shortcutDisposer = null
|
|
4018
|
+
}
|
|
4019
|
+
shortcutDisposer = registerIterateShortcuts(shortcutSvc, {
|
|
4020
|
+
openObservatory: (detail) => { openObservatory(detail) },
|
|
4021
|
+
lang: () => getLang(),
|
|
4022
|
+
})
|
|
4023
|
+
if (typeof ctx.effect === 'function') {
|
|
4024
|
+
ctx.effect(() => { if (shortcutDisposer) { shortcutDisposer(); shortcutDisposer = null } })
|
|
4025
|
+
}
|
|
4026
|
+
}
|
|
4027
|
+
}
|
|
4028
|
+
|
|
3987
4029
|
// 6: keep theme toggle state consistent across theme/change events.
|
|
3988
4030
|
// Registered through registerThemeListener so repeated apply() and client
|
|
3989
4031
|
// unmounts never leak a second listener.
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/client/shortcuts.ts — iterate-plugin keyboard-command contributions.
|
|
3
|
+
*
|
|
4
|
+
* dsh 0.2.2-rc.1 added the *unified keyboard-command* capability
|
|
5
|
+
* (`@deepseek-ai/dsh-client-shortcuts`): any client plugin may register a
|
|
6
|
+
* command with a default key binding and a target resolver, and dsh's own
|
|
7
|
+
* keyboard adapter dispatches the gesture to it. The same capability also lets
|
|
8
|
+
* a context-menu action carry a `commandId` so the menu shows the live
|
|
9
|
+
* keycap next to the action (selection still invokes the action's `run`).
|
|
10
|
+
*
|
|
11
|
+
* This module turns that new upstream capability into a *user-perceivable*
|
|
12
|
+
* iterate surface: instead of hunting for the docked observatory (or
|
|
13
|
+
* right-clicking a session), the user can now press **Cmd/Ctrl+Shift+O** to
|
|
14
|
+
* open the iterate quality command center from anywhere in the session. The
|
|
15
|
+
* matching context-menu action carries the same `commandId`, so the shortcut
|
|
16
|
+
* is discoverable in the right-click menu ("open command center" shows the
|
|
17
|
+
* keycap), and dsh's own shortcut settings page lists it as an editable
|
|
18
|
+
* binding.
|
|
19
|
+
*
|
|
20
|
+
* Design (mirrors `context-menu.ts` and the rest of the client half):
|
|
21
|
+
* - **Defensive**: the `shortcuts` service is optional and probed at
|
|
22
|
+
* apply-time (see `readShortcuts` in `index.ts`); a host that predates
|
|
23
|
+
* 0.2.2-rc.1 simply has no keyboard command (the docked panel stays the
|
|
24
|
+
* entry point). Nothing throws.
|
|
25
|
+
* - **Pure builder**: `buildObservatoryShortcutCommand` returns the command
|
|
26
|
+
* shape with no `ctx`/DOM side effects, so it is unit-testable on its own
|
|
27
|
+
* (label resolution, binding defaults, resolve→run). The imperative
|
|
28
|
+
* wiring (`openObservatory` / `lang`) is injected by
|
|
29
|
+
* `registerIterateShortcuts`.
|
|
30
|
+
* - **Bilingual**: the label resolves against the language passed in at
|
|
31
|
+
* registration (a language switch is picked up by dsh's own command
|
|
32
|
+
* catalog on the next render).
|
|
33
|
+
* - **No external import**: like the rest of the client half, the dsh
|
|
34
|
+
* `@deepseek-ai/dsh-client-shortcuts` types are NOT imported (no
|
|
35
|
+
* compile-time coupling to a package a pre-0.2.2-rc.1 host may not have);
|
|
36
|
+
* the surface below is the structural subset this plugin consumes.
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
import type { Lang } from './i18n.ts'
|
|
40
|
+
|
|
41
|
+
// ─── Structural service types (defensive; mirrors ContextMenusService) ───────
|
|
42
|
+
|
|
43
|
+
/** Stable command identity owned by this plugin (a branded string upstream). */
|
|
44
|
+
export const OBSERVATORY_SHORTCUT_ID = 'iterate.open-observatory'
|
|
45
|
+
|
|
46
|
+
/** One physical key + modifiers; `primary` expands to Meta on macOS. */
|
|
47
|
+
export interface ShortcutBinding {
|
|
48
|
+
code: string
|
|
49
|
+
secondCode?: string
|
|
50
|
+
modifiers: readonly string[]
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** The dsh command catalog shape this plugin registers. */
|
|
54
|
+
export interface ShortcutCommand {
|
|
55
|
+
id: string
|
|
56
|
+
label: () => string
|
|
57
|
+
aliases: readonly string[]
|
|
58
|
+
defaults: Readonly<Record<string, ShortcutBinding>>
|
|
59
|
+
regions: readonly string[]
|
|
60
|
+
modals: readonly string[]
|
|
61
|
+
resolve(context: { region?: string; modal?: string | null; target?: Element | null }): ShortcutResolution
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** A resolved command either runs, is blocked with a reason, or passes. */
|
|
65
|
+
export type ShortcutResolution =
|
|
66
|
+
| { status: 'handled'; run: () => void }
|
|
67
|
+
| { status: 'blocked'; reason: string }
|
|
68
|
+
| { status: 'pass' }
|
|
69
|
+
|
|
70
|
+
/** Structural view of the dsh `shortcuts` service this plugin consumes. */
|
|
71
|
+
export interface ShortcutsService {
|
|
72
|
+
register(command: ShortcutCommand): () => void
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Injection points the shortcut wiring needs (kept dependency-light for tests). */
|
|
76
|
+
export interface ShortcutDeps {
|
|
77
|
+
/** Open the observatory panel (optionally for a session, on a tab). */
|
|
78
|
+
openObservatory: (detail?: { sessionId?: string; tab?: string }) => void
|
|
79
|
+
/** Resolve the current UI language at label-build time. */
|
|
80
|
+
lang: () => Lang
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Default key binding for the observatory command: Cmd/Ctrl+Shift+O
|
|
85
|
+
* ("O" = Open), applied uniformly across every runtime/platform profile so the
|
|
86
|
+
* feature is on by default on desktop and web. If an existing dsh command
|
|
87
|
+
* already owns this combination, `register` throws and the plugin degrades
|
|
88
|
+
* gracefully (no keyboard command; the observatory stays mouse-reachable).
|
|
89
|
+
*/
|
|
90
|
+
const DEFAULT_BINDING: ShortcutBinding = { code: 'KeyO', modifiers: ['primary', 'shift'] }
|
|
91
|
+
|
|
92
|
+
const DEFAULT_PROFILES = [
|
|
93
|
+
'desktop:macos',
|
|
94
|
+
'desktop:windows',
|
|
95
|
+
'desktop:linux',
|
|
96
|
+
'web:macos',
|
|
97
|
+
'web:windows',
|
|
98
|
+
'web:linux',
|
|
99
|
+
] as const
|
|
100
|
+
|
|
101
|
+
/** Every runtime/platform profile maps to the same default binding. */
|
|
102
|
+
export const OBSERVATORY_DEFAULT_BINDINGS: Readonly<Record<string, ShortcutBinding>> = Object.fromEntries(
|
|
103
|
+
DEFAULT_PROFILES.map((profile) => [profile, DEFAULT_BINDING]),
|
|
104
|
+
) as Readonly<Record<string, ShortcutBinding>>
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Build the pure command descriptor for "open iterate observatory". The
|
|
108
|
+
* resolver always handles the gesture and runs the injected opener; it never
|
|
109
|
+
* blocks (opening the observatory is safe from any region and no modal).
|
|
110
|
+
*/
|
|
111
|
+
export function buildObservatoryShortcutCommand(deps: ShortcutDeps): ShortcutCommand {
|
|
112
|
+
const label = (): string =>
|
|
113
|
+
deps.lang() === 'zh' ? '打开 iterate 观测台' : 'Open iterate observatory'
|
|
114
|
+
return {
|
|
115
|
+
id: OBSERVATORY_SHORTCUT_ID,
|
|
116
|
+
label,
|
|
117
|
+
aliases: [],
|
|
118
|
+
defaults: OBSERVATORY_DEFAULT_BINDINGS,
|
|
119
|
+
regions: ['page', 'editable', 'terminal'],
|
|
120
|
+
modals: [],
|
|
121
|
+
resolve: () => ({ status: 'handled', run: () => deps.openObservatory({}) }),
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Register the observatory keyboard command on the (already-validated)
|
|
127
|
+
* `shortcuts` service. Returns an unregister function; a host without the
|
|
128
|
+
* service is a no-op that returns `() => {}` (callers guard).
|
|
129
|
+
*/
|
|
130
|
+
export function registerIterateShortcuts(service: ShortcutsService, deps: ShortcutDeps): () => void {
|
|
131
|
+
try {
|
|
132
|
+
const dispose = service.register(buildObservatoryShortcutCommand(deps))
|
|
133
|
+
let done = false
|
|
134
|
+
return () => {
|
|
135
|
+
if (done) return
|
|
136
|
+
done = true
|
|
137
|
+
try {
|
|
138
|
+
dispose()
|
|
139
|
+
} catch {
|
|
140
|
+
/* noop */
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
} catch (err) {
|
|
144
|
+
// A duplicate id or an overlapping default binding must not take the
|
|
145
|
+
// plugin down — log and continue with no keyboard command (the docked
|
|
146
|
+
// panel and the context-menu "open" action remain the entry points).
|
|
147
|
+
if (typeof console !== 'undefined' && typeof console.error === 'function') {
|
|
148
|
+
console.error('[iterate-ui] keyboard-command registration failed; continuing without it.', err)
|
|
149
|
+
}
|
|
150
|
+
return () => {}
|
|
151
|
+
}
|
|
152
|
+
}
|