weir-harness 1.2.0 → 1.2.2

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.
Files changed (99) hide show
  1. package/cordis.patch.yml +0 -2
  2. package/lib/client.blackboard-model.js +172 -56
  3. package/lib/client.capability-manager.js +0 -1
  4. package/lib/client.capability-model.js +378 -31
  5. package/lib/client.chain-model.js +80 -0
  6. package/lib/client.disabled-categories-editor.js +22 -0
  7. package/lib/client.edit-lock-maintenance.js +239 -246
  8. package/lib/client.edit-lock-panel.js +110 -0
  9. package/lib/client.hash-edit-model.js +48 -0
  10. package/lib/client.js +267 -1549
  11. package/lib/client.lsp-model.js +92 -0
  12. package/lib/client.lsp-panel.js +11 -16
  13. package/lib/client.lsp-toggle.js +18 -0
  14. package/lib/client.notify-permissions.js +112 -20
  15. package/lib/client.notify-web.js +14 -11
  16. package/lib/client.robash-model.js +26 -0
  17. package/lib/client.settings-page.js +287 -17
  18. package/lib/client.worktree-model.js +225 -0
  19. package/package.json +5 -2
  20. package/src/blackboard/durable.js +12 -0
  21. package/src/blackboard/index.js +2 -2
  22. package/src/blackboard/kernel.js +23 -6
  23. package/src/blackboard/notify.js +10 -3
  24. package/src/blackboard/remote.js +31 -13
  25. package/src/blackboard/storage.js +13 -5
  26. package/src/capabilities/apply-engine.js +189 -101
  27. package/src/capabilities/defaults-transaction.js +136 -1
  28. package/src/capabilities/lifecycle-snapshot.js +83 -204
  29. package/src/capabilities/mcp-admission.js +1 -23
  30. package/src/capabilities/mcp-drain.js +30 -19
  31. package/src/capabilities/mcp-facade.js +23 -3
  32. package/src/capabilities/preset-library.js +247 -0
  33. package/src/capabilities/preset-package.js +212 -1
  34. package/src/capabilities/same-name-policy.js +3 -41
  35. package/src/capabilities/selection-notify.js +324 -21
  36. package/src/capabilities/skill-office-adapter.js +7 -1
  37. package/src/capabilities/skill-selection-plugin.js +315 -729
  38. package/src/capabilities/store/checked-contract.d.ts +5 -0
  39. package/src/capabilities/store/fs-adapter.js +52 -0
  40. package/src/capabilities/store/lock.js +86 -0
  41. package/src/capabilities/store/paths.js +5 -0
  42. package/src/capabilities/store/store.js +105 -5
  43. package/src/creative-guide/guide.js +1 -1
  44. package/src/delegate/child-composition.js +258 -0
  45. package/src/delegate/group-coordinator.js +25 -27
  46. package/src/delegate/index.js +21 -2
  47. package/src/delegate/rehydrate.js +24 -4
  48. package/src/delegate/robash-guard-core.js +2 -2
  49. package/src/delegate/spawn-adapter.js +214 -100
  50. package/src/delegate/status-tool.js +9 -6
  51. package/src/delegate/supervision-mount.js +35 -6
  52. package/src/delegate/tool.js +29 -4
  53. package/src/{shared → delegate}/whitelist-defaults.js +2 -2
  54. package/src/edit-lock/admin-ledger.js +8 -11
  55. package/src/edit-lock/admin-recovery.js +2 -1
  56. package/src/edit-lock/admission.js +12 -2
  57. package/src/edit-lock/binding-guidance.js +28 -10
  58. package/src/edit-lock/binding-machine.js +312 -0
  59. package/src/edit-lock/derived-facts.js +67 -0
  60. package/src/edit-lock/index.js +44 -244
  61. package/src/edit-lock/inspect.js +113 -1
  62. package/src/edit-lock/lifecycle.js +8 -5
  63. package/src/edit-lock/limits.js +61 -0
  64. package/src/edit-lock/maintenance.js +5 -2
  65. package/src/edit-lock/manager.js +6 -2
  66. package/src/edit-lock/operation-history.js +1 -1
  67. package/src/edit-lock/peer-client.js +9 -2
  68. package/src/edit-lock/peer-transport.js +7 -1
  69. package/src/edit-lock/remote.js +11 -4
  70. package/src/edit-lock/state.js +2 -4
  71. package/src/edit-lock/view.js +10 -11
  72. package/src/lsp/admin.js +2 -24
  73. package/src/lsp/index.js +1 -1
  74. package/src/lsp/manager.js +1 -1
  75. package/src/notify/permissions-admin.js +1 -1
  76. package/src/{lsp/child-process.js → process/index.js} +23 -6
  77. package/src/settings/env-admin.js +1 -1
  78. package/src/settings/index.js +1 -1
  79. package/src/settings/sections.js +0 -54
  80. package/src/shared/audit.js +12 -0
  81. package/src/shared/child-scope.js +3 -1
  82. package/src/shared/http-json.js +28 -0
  83. package/src/{delegate/audit-readers.js → shared/terminal-evidence.js} +34 -4
  84. package/src/worktree/errors.js +3 -0
  85. package/src/worktree/index.js +1 -2
  86. package/src/worktree/lane-core.js +641 -0
  87. package/src/worktree/lane-views.js +147 -0
  88. package/src/worktree/lane-workflows.js +853 -0
  89. package/src/worktree/lanes.js +24 -1513
  90. package/src/worktree/runner.js +5 -4
  91. package/src/capabilities/content-pin.js +0 -88
  92. package/src/capabilities/content-refresh.js +0 -331
  93. package/src/capabilities/distribution-manifest.js +0 -78
  94. package/src/capabilities/isolated-exec.js +0 -106
  95. package/src/capabilities/publisher.js +0 -129
  96. package/src/capabilities/skill-admission.js +0 -181
  97. package/src/capabilities/update-lifecycle.js +0 -106
  98. package/src/edit-lock/remote-service.js +0 -28
  99. package/src/worktree/contract-guidance.test.js +0 -100
@@ -39,6 +39,98 @@ window.__ModuleLoader__.load({
39
39
  if (parts.length === 0) return undefined;
40
40
  return { command: parts[0], args: parts.slice(1) };
41
41
  }
42
+ // Locale data face (pure data, NO ctx calls — a chunk factory re-executes
43
+ // under HMR, so a chunk-local register would throw on the duplicate
44
+ // (namespace, locale) pair): the entry registers these under "settings.weir.page"
45
+ // inside a ctx.effect when this chunk arrives (lib/client.js
46
+ // registerChunkDictionaries). Pinned by test/client.test.js's dictionary
47
+ // union pin.
48
+ // (The entry merges every settings-batch slice into the single "settings.weir.page"
49
+ // registration — the whole settings card tree shares one t.)
50
+ const dictionaries = {
51
+ en: {
52
+ lspManager: "Manage LSP services",
53
+ lspManagerHint: "Check which language servers are installed and install the missing ones from the community catalog.",
54
+ lspManagerLoading: "Loading…",
55
+ lspManagerFailed: "The LSP manager failed; check the runtime log.",
56
+ lspManagerUnavailable: "LSP management endpoints unavailable in this deployment:",
57
+ lspManagerRetry: "Retry",
58
+ lspManagerInstall: "Install",
59
+ lspManagerCancel: "Cancel",
60
+ lspManagerConfirmInstall: "Run install",
61
+ lspManagerInstalling: "Installing…",
62
+ lspManagerInstalled: "Installed",
63
+ lspManagerMissing: "Not installed",
64
+ lspManagerExitCode: "Exit code",
65
+ lspManagerTimedOut: "(timed out)",
66
+ lspManagerCustom: "Custom servers",
67
+ lspManagerCustomHint: "Add your own language servers; saved with the settings and detected on the next status load.",
68
+ lspManagerAddServer: "Add server",
69
+ lspManagerRemove: "Remove",
70
+ lspManagerFamily: "family name",
71
+ lspManagerCommand: "command",
72
+ lspManagerArgs: "args (space separated)",
73
+ lspManagerInstallCmd: "install command (optional)",
74
+ lspManagerPendingStatus: "detected after save",
75
+ lspManagerInstallerMissing: "Installer unavailable: install it first, then retry.",
76
+ lspManagerInvalidJson: "The stored custom-servers JSON is malformed; the panel opened with an empty list — saving replaces the stored value.",
77
+ lspFamily_typescript: "TypeScript",
78
+ lspFamily_python: "Python",
79
+ lspFamily_go: "Go",
80
+ lspFamily_rust: "Rust",
81
+ lspFamily_json: "JSON",
82
+ lspFamily_html: "HTML",
83
+ lspFamily_css: "CSS",
84
+ lspFamily_markdown: "Markdown",
85
+ lspFamily_bash: "Bash",
86
+ lspFamily_dockerfile: "Dockerfile",
87
+ lspFamily_yaml: "YAML",
88
+ lspFamily_lua: "Lua",
89
+ lspFamily_cpp: "C/C++",
90
+ },
91
+ zh: {
92
+ lspManager: "管理 LSP 服务",
93
+ lspManagerHint: "查看各语言服务器安装状态,一键安装缺失的社区服务器。",
94
+ lspManagerLoading: "加载中…",
95
+ lspManagerFailed: "LSP 管理面板异常,请查看运行时日志。",
96
+ lspManagerUnavailable: "此部署不提供 LSP 管理端点:",
97
+ lspManagerRetry: "重试",
98
+ lspManagerInstall: "安装",
99
+ lspManagerCancel: "取消",
100
+ lspManagerConfirmInstall: "确认安装",
101
+ lspManagerInstalling: "安装中…",
102
+ lspManagerInstalled: "已安装",
103
+ lspManagerMissing: "未安装",
104
+ lspManagerExitCode: "退出码",
105
+ lspManagerTimedOut: "(超时)",
106
+ lspManagerCustom: "自定义服务器",
107
+ lspManagerCustomHint: "添加你自己的语言服务器;随设置保存,下次状态加载时检测。",
108
+ lspManagerAddServer: "添加服务器",
109
+ lspManagerRemove: "删除",
110
+ lspManagerFamily: "族名",
111
+ lspManagerCommand: "命令",
112
+ lspManagerArgs: "参数(空格分隔)",
113
+ lspManagerInstallCmd: "安装命令(可空)",
114
+ lspManagerPendingStatus: "保存后检测",
115
+ lspManagerInstallerMissing: "安装器不可用:请先安装安装器,再重试。",
116
+ lspManagerInvalidJson: "已保存的自定义服务器 JSON 格式有误;面板已以空列表打开——保存将覆盖该值。",
117
+ lspFamily_typescript: "TypeScript",
118
+ lspFamily_python: "Python",
119
+ lspFamily_go: "Go",
120
+ lspFamily_rust: "Rust",
121
+ lspFamily_json: "JSON",
122
+ lspFamily_html: "HTML",
123
+ lspFamily_css: "CSS",
124
+ lspFamily_markdown: "Markdown",
125
+ lspFamily_bash: "Bash",
126
+ lspFamily_dockerfile: "Dockerfile",
127
+ lspFamily_yaml: "YAML",
128
+ lspFamily_lua: "Lua",
129
+ lspFamily_cpp: "C/C++",
130
+ },
131
+ };
132
+
133
+ exports.dictionaries = dictionaries;
42
134
  exports.jsonToLspServers = jsonToLspServers;
43
135
  exports.lspServersJsonValid = lspServersJsonValid;
44
136
  exports.lspServersToJson = lspServersToJson;
@@ -9,8 +9,10 @@ window.__ModuleLoader__.load({
9
9
  let react_jsx_runtime = require("react/jsx-runtime");
10
10
  let primitives = require("@deepseek-ai/dsh-client-ui-primitives");
11
11
  // LSP service manager panel: status over the catalog + one-click
12
- // install + custom servers, talking to the host over raw fetch
13
- // ("/api/weir-lsp/*"). The lsp model trio (client.lsp-model.js) and
12
+ // install + custom servers, talking to the host over the composition
13
+ // root's HTTP channel adapter (props.channel: (endpoint, payload?) =>
14
+ // Promise<{ ok: true, value } | { ok: false, error }>, never rejecting;
15
+ // "/api/weir-lsp/*"). The lsp model trio (client.lsp-model.js) and
14
16
  // `t` arrive via props. Style constants are verbatim copies of the
15
17
  // settings-page constants this panel uses (same-package sync require
16
18
  // is impossible in the ModuleLoader).
@@ -63,10 +65,9 @@ window.__ModuleLoader__.load({
63
65
  const serversInvalid = model.lspServersJsonValid(props.serversText) === false;
64
66
  const load = () => {
65
67
  setView({ status: "loading" });
66
- fetch("api/weir-lsp/status", { method: "POST", credentials: "include" })
67
- .then((response) => response.json())
68
- .then((payload) => {
69
- setView(payload?.ok ? { status: "ready", servers: payload.value.servers } : { status: "error", message: payload?.error?.message ?? "unknown" });
68
+ props.channel("api/weir-lsp/status")
69
+ .then((result) => {
70
+ setView(result.ok === true ? { status: "ready", servers: result.value.servers } : { status: "error", message: result.error.message });
70
71
  })
71
72
  .catch((error) => setView({ status: "error", message: String(error?.message ?? error) }));
72
73
  };
@@ -81,17 +82,11 @@ window.__ModuleLoader__.load({
81
82
  setConfirming(null);
82
83
  setBusy(family);
83
84
  setResult(null);
84
- fetch("api/weir-lsp/install", {
85
- method: "POST",
86
- credentials: "include",
87
- headers: { "content-type": "application/json" },
88
- body: JSON.stringify({ family })
89
- })
90
- .then((response) => response.json())
91
- .then((payload) => {
85
+ props.channel("api/weir-lsp/install", { family })
86
+ .then((result) => {
92
87
  setBusy(null);
93
- setResult(payload?.ok ? { family, output: payload.value.output, exitCode: payload.value.exitCode, timedOut: payload.value.timedOut } : { family, error: payload?.error?.message ?? "unknown" });
94
- if (payload?.ok) load();
88
+ setResult(result.ok === true ? { family, output: result.value.output, exitCode: result.value.exitCode, timedOut: result.value.timedOut } : { family, error: result.error.message });
89
+ if (result.ok === true) load();
95
90
  })
96
91
  .catch((error) => {
97
92
  setBusy(null);
@@ -113,6 +113,24 @@ window.__ModuleLoader__.load({
113
113
  ]
114
114
  });
115
115
  }
116
+ // Locale data face (pure data, NO ctx calls — a chunk factory re-executes
117
+ // under HMR, so a chunk-local register would throw on the duplicate
118
+ // (namespace, locale) pair): the entry registers these under "settings.weir.lsp-toggle"
119
+ // inside a ctx.effect when this chunk arrives (lib/client.js
120
+ // registerChunkDictionaries). Pinned by test/client.test.js's dictionary
121
+ // union pin.
122
+ const dictionaries = {
123
+ en: {
124
+ lspToggleLabel: "LSP",
125
+ lspToggleTitle: "Toggle LSP semantic tools for this session",
126
+ },
127
+ zh: {
128
+ lspToggleLabel: "LSP",
129
+ lspToggleTitle: "为本会话启用/禁用 LSP 语义工具",
130
+ },
131
+ };
132
+
133
+ exports.dictionaries = dictionaries;
116
134
  exports.LSP_PROJECTION_KEY = LSP_PROJECTION_KEY;
117
135
  exports.LspToggle = LspToggle;
118
136
  return module.exports;
@@ -11,8 +11,10 @@ window.__ModuleLoader__.load({
11
11
  // macOS notification permission panel: an entry row plus a modal that
12
12
  // detects the permission and walks the user through allowing it. The
13
13
  // host decides everything (platform, state, actions) over
14
- // "api/weir-notify/permissions/*"; this chunk only renders the answer
15
- // and calls the two whitelisted actions. The entry renders NOTHING until
14
+ // "api/weir-notify/permissions/*" via the composition root's HTTP
15
+ // channel adapter (props.channel: (endpoint, payload?) => Promise<
16
+ // { ok: true, value } | { ok: false, error }>, never rejecting); this
17
+ // chunk only renders the answer and calls the two whitelisted actions. The entry renders NOTHING until
16
18
  // the host confirms it is macOS, so other platforms never see it. Style
17
19
  // constants are verbatim copies of the settings-page ones (same-package
18
20
  // sync require is impossible in the ModuleLoader).
@@ -56,14 +58,6 @@ window.__ModuleLoader__.load({
56
58
  return "wait";
57
59
  }
58
60
  const INITIAL_FLOW = { testSent: false, settingsOpened: false, seen: null };
59
- function post(path, body) {
60
- return fetch(path, {
61
- method: "POST",
62
- credentials: "include",
63
- headers: { "content-type": "application/json" },
64
- body: JSON.stringify(body ?? {})
65
- }).then((response) => response.json());
66
- }
67
61
  const messageOf = (error) => String(error?.message ?? error);
68
62
  /**
69
63
  * The page's own web-notification permission, read-only. Delivery itself
@@ -123,14 +117,14 @@ window.__ModuleLoader__.load({
123
117
  // Resolves the fresh host status, or null when it could not be read.
124
118
  const load = () => {
125
119
  setChecking(true);
126
- return post(STATUS_PATH).then((payload) => {
120
+ return props.channel(STATUS_PATH, {}).then((result) => {
127
121
  setChecking(false);
128
- if (payload?.ok && payload.value?.supported) {
129
- setHost(payload.value);
122
+ if (result.ok === true && result.value?.supported) {
123
+ setHost(result.value);
130
124
  setError(null);
131
- return payload.value;
125
+ return result.value;
132
126
  }
133
- setError(payload?.error?.message ?? "unknown");
127
+ setError(result.error?.message ?? "unknown");
134
128
  return null;
135
129
  }).catch((failure) => {
136
130
  setChecking(false);
@@ -139,8 +133,8 @@ window.__ModuleLoader__.load({
139
133
  });
140
134
  };
141
135
  react.useEffect(() => {
142
- post(STATUS_PATH).then(
143
- (payload) => setHost(payload?.ok && payload.value?.supported ? payload.value : { supported: false }),
136
+ props.channel(STATUS_PATH, {}).then(
137
+ (result) => setHost(result.ok === true && result.value?.supported ? result.value : { supported: false }),
144
138
  () => setHost({ supported: false })
145
139
  );
146
140
  return stopPolling;
@@ -183,10 +177,10 @@ window.__ModuleLoader__.load({
183
177
  setError(null);
184
178
  // Ask for the page permission inside this click (the only place a browser shows the prompt).
185
179
  const ready = action === "test" ? requestWebPermission(globalThis).then(() => setPermTick((tick) => tick + 1)) : Promise.resolve();
186
- ready.then(() => post(ACTION_PATH, { action })).then((payload) => {
180
+ ready.then(() => props.channel(ACTION_PATH, { action })).then((result) => {
187
181
  setBusy(null);
188
- if (!payload?.ok) {
189
- setError(payload?.error?.message ?? "unknown");
182
+ if (result.ok !== true) {
183
+ setError(result.error?.message ?? "unknown");
190
184
  return;
191
185
  }
192
186
  setFlow((current) => action === "test"
@@ -295,6 +289,104 @@ window.__ModuleLoader__.load({
295
289
  function NotifyPermissionsField(props) {
296
290
  return react_jsx_runtime.jsx(NotifyPermissionsBoundary, { t: props.t, children: react_jsx_runtime.jsx(NotifyPermissionsPanel, props) });
297
291
  }
292
+ // Locale data face (pure data, NO ctx calls — a chunk factory re-executes
293
+ // under HMR, so a chunk-local register would throw on the duplicate
294
+ // (namespace, locale) pair): the entry registers these under "settings.weir.page"
295
+ // inside a ctx.effect when this chunk arrives (lib/client.js
296
+ // registerChunkDictionaries). Pinned by test/client.test.js's dictionary
297
+ // union pin.
298
+ // (The entry merges every settings-batch slice into the single "settings.weir.page"
299
+ // registration — the whole settings card tree shares one t.)
300
+ const dictionaries = {
301
+ en: {
302
+ notifyPermissions: "Notification permission",
303
+ notifyPermissionsHint: "Check whether macOS lets these notifications through, and walk through allowing them.",
304
+ notifyPermissionsOpen: "Manage",
305
+ notifyPermTitle: "Notification permission",
306
+ notifyPermDescription: "Notifications are shown by DeepSeek Harness itself, so macOS has to let DeepSeek Harness notify you.",
307
+ notifyPermClose: "Close",
308
+ notifyPermChecking: "Checking…",
309
+ notifyPermRecheck: "Check again",
310
+ notifyPermBestEffort: "This check reads macOS settings that Apple does not document, so treat it as a hint. Seeing the test notification is the real proof.",
311
+ notifyPermFocusNote: "Focus / Do Not Disturb can still hold notifications back, and it cannot be checked from here.",
312
+ notifyPermSendTest: "Send a test notification",
313
+ notifyPermSending: "Sending…",
314
+ notifyPermOpenSettings: "Open notification settings",
315
+ notifyPermOpening: "Opening…",
316
+ notifyPermOpenHint: "In the list, find “DeepSeek Harness”, turn on “Allow notifications”, and pick the Banners or Alerts style.",
317
+ notifyPermWaiting: "Waiting for you to allow notifications… checking every 2 seconds.",
318
+ notifyPermConfirm: "Did you see the test notification?",
319
+ notifyPermSeenYes: "Yes, I saw it",
320
+ notifyPermSeenNo: "No",
321
+ notifyPermSeen: "All set — notifications will reach you.",
322
+ notifyPermNotSeen: "Check that Focus / Do Not Disturb is off, that DeepSeek Harness's style is Banners or Alerts, and — if the window is in front — that \"When the window is in front\" is set to Always notify. Then try again.",
323
+ notifyPermRetry: "Start over",
324
+ notifyPermSendAgain: "Send another test",
325
+ notifyPermError: "Something went wrong:",
326
+ notifyPermManualPath: "You can do this by hand: System Settings → Notifications → DeepSeek Harness.",
327
+ notifyPermDetails: "Technical details",
328
+ notifyPermFailed: "The permission panel failed; check the runtime log.",
329
+ "notifyPermState_granted": "Allowed",
330
+ "notifyPermState_denied": "Not allowed",
331
+ "notifyPermState_unknown": "Can't tell",
332
+ "notifyPermReason_alerts-allowed": "Alerts are allowed for DeepSeek Harness.",
333
+ "notifyPermReason_not-allowed": "Notifications are turned off for DeepSeek Harness.",
334
+ "notifyPermReason_no-record": "macOS has no record of DeepSeek Harness yet. Send a test notification so it shows up in the list.",
335
+ "notifyPermReason_default": "macOS has no explicit setting for DeepSeek Harness (it uses the system default), so this can't be told from here.",
336
+ "notifyPermReason_unreadable": "The notification settings could not be read.",
337
+ notifyWebUnsupported: "This page has no web Notification support, so DeepSeek Harness cannot notify directly.",
338
+ notifyWebPermission: "Web notification permission",
339
+ notifyWebDeniedHint: "This page was blocked from showing notifications and cannot ask again. Open System Settings → Notifications → DeepSeek Harness and turn notifications on.",
340
+ "notifyWebPerm_granted": "Allowed",
341
+ "notifyWebPerm_denied": "Blocked",
342
+ "notifyWebPerm_default": "Not asked yet",
343
+ },
344
+ zh: {
345
+ notifyPermissions: "通知权限",
346
+ notifyPermissionsHint: "检测 macOS 是否放行这些通知,并引导你完成授权。",
347
+ notifyPermissionsOpen: "管理",
348
+ notifyPermTitle: "通知权限",
349
+ notifyPermDescription: "通知由 DeepSeek Harness 自己弹出,所以需要在 macOS 里允许 DeepSeek Harness 发送通知。",
350
+ notifyPermClose: "关闭",
351
+ notifyPermChecking: "检测中…",
352
+ notifyPermRecheck: "重新检测",
353
+ notifyPermBestEffort: "该检测读取的是苹果未公开文档化的系统设置,仅供参考;亲眼看到测试通知才是最终证明。",
354
+ notifyPermFocusNote: "专注模式 / 勿扰仍可能拦截通知,且无法在这里检测。",
355
+ notifyPermSendTest: "发送测试通知",
356
+ notifyPermSending: "发送中…",
357
+ notifyPermOpenSettings: "打开系统通知设置",
358
+ notifyPermOpening: "打开中…",
359
+ notifyPermOpenHint: "在列表中找到「DeepSeek Harness」,打开「允许通知」,并选择「横幅」或「提醒」样式。",
360
+ notifyPermWaiting: "正在等你允许通知…每 2 秒自动检测一次。",
361
+ notifyPermConfirm: "你看到测试通知了吗?",
362
+ notifyPermSeenYes: "看到了",
363
+ notifyPermSeenNo: "没看到",
364
+ notifyPermSeen: "设置完成,通知会送达你。",
365
+ notifyPermNotSeen: "请确认专注模式 / 勿扰已关闭,「DeepSeek Harness」的样式是「横幅」或「提醒」,并且(若窗口在前台)「窗口在前台时」已设为「始终通知」,然后重试。",
366
+ notifyPermRetry: "重新开始",
367
+ notifyPermSendAgain: "再发一条测试",
368
+ notifyPermError: "出错了:",
369
+ notifyPermManualPath: "也可以手动操作:系统设置 → 通知 → DeepSeek Harness。",
370
+ notifyPermDetails: "技术细节",
371
+ notifyPermFailed: "权限面板出错,请查看运行日志。",
372
+ "notifyPermState_granted": "已允许",
373
+ "notifyPermState_denied": "未允许",
374
+ "notifyPermState_unknown": "无法判定",
375
+ "notifyPermReason_alerts-allowed": "已允许「DeepSeek Harness」弹出提醒。",
376
+ "notifyPermReason_not-allowed": "「DeepSeek Harness」的通知已被关闭。",
377
+ "notifyPermReason_no-record": "macOS 里还没有「DeepSeek Harness」的记录。先发一条测试通知,让它出现在列表里。",
378
+ "notifyPermReason_default": "macOS 里没有「DeepSeek Harness」的明确设置(沿用系统默认),无法在这里判定。",
379
+ "notifyPermReason_unreadable": "无法读取通知设置。",
380
+ notifyWebUnsupported: "当前页面没有网页通知能力,DeepSeek Harness 无法直接通知。",
381
+ notifyWebPermission: "网页通知权限",
382
+ notifyWebDeniedHint: "本页面被禁止显示通知,且无法再次询问。请打开「系统设置 → 通知 → DeepSeek Harness」并开启通知。",
383
+ "notifyWebPerm_granted": "已允许",
384
+ "notifyWebPerm_denied": "已禁止",
385
+ "notifyWebPerm_default": "尚未询问",
386
+ },
387
+ };
388
+
389
+ exports.dictionaries = dictionaries;
298
390
  exports.LABEL_KEYS = LABEL_KEYS;
299
391
  exports.webNotificationSupport = webNotificationSupport;
300
392
  exports.requestWebPermission = requestWebPermission;
@@ -87,10 +87,13 @@ window.__ModuleLoader__.load({
87
87
  }
88
88
  /**
89
89
  * Start the long-poll loop. Returns { stop, stats }. `env` is injectable:
90
- * { fetch, Notification, document, setTimeout, clearTimeout }.
90
+ * { channel, Notification, document, setTimeout, clearTimeout } — channel
91
+ * is the composition root's HTTP channel adapter ((endpoint, payload,
92
+ * { signal }) => Promise<{ ok, value | error }>, never rejecting; a
93
+ * carrier throw arrives folded as error.code "call-threw").
91
94
  */
92
95
  function startWebDelivery(env) {
93
- const doFetch = env.fetch ?? globalThis.fetch;
96
+ const channel = env.channel;
94
97
  const wait = env.setTimeout ?? globalThis.setTimeout;
95
98
  const unwait = env.clearTimeout ?? globalThis.clearTimeout;
96
99
  const open = new Map();
@@ -99,13 +102,7 @@ window.__ModuleLoader__.load({
99
102
  let failures = 0;
100
103
  let controller = null;
101
104
  let sleeper = null;
102
- const post = (path, body, signal) => doFetch(path, {
103
- method: "POST",
104
- credentials: "include",
105
- headers: { "content-type": "application/json" },
106
- body: JSON.stringify(body ?? {}),
107
- signal
108
- }).then((response) => response.json());
105
+ const post = (path, body, signal) => channel(path, body ?? {}, { signal });
109
106
  const sleep = (ms) => new Promise((resolve) => {
110
107
  sleeper = wait(resolve, ms);
111
108
  });
@@ -114,6 +111,10 @@ window.__ModuleLoader__.load({
114
111
  try {
115
112
  controller = typeof AbortController === "function" ? new AbortController() : null;
116
113
  const payload = await post(PULL_PATH, {}, controller?.signal);
114
+ // A folded carrier failure (call-threw) throws BEFORE stats.pulls++
115
+ // exactly like the old rejecting post did; an envelope failure
116
+ // counts as an answered pull, then throws.
117
+ if (payload?.error?.code === "call-threw") throw new Error(payload.error.message);
117
118
  stats.pulls++;
118
119
  if (!payload?.ok) throw new Error(payload?.error?.message ?? "pull failed");
119
120
  failures = 0;
@@ -122,8 +123,10 @@ window.__ModuleLoader__.load({
122
123
  const result = await showNote(env, pulled.note, open);
123
124
  if (result === "shown") stats.shown++;
124
125
  // The ACK must not be able to wedge the loop.
125
- post(ACK_PATH, { id: pulled.id, result: RESULTS.includes(result) ? result : "error" }).catch(() => {
126
- stats.failures++;
126
+ post(ACK_PATH, { id: pulled.id, result: RESULTS.includes(result) ? result : "error" }).then((ackResult) => {
127
+ // The folded call-threw stands in for the old rejection (the only
128
+ // shape the .catch counted); an envelope failure was ignored then.
129
+ if (ackResult?.error?.code === "call-threw") stats.failures++;
127
130
  });
128
131
  }
129
132
  } catch {
@@ -31,6 +31,32 @@ window.__ModuleLoader__.load({
31
31
  function stringListToJson(list) {
32
32
  return JSON.stringify((list ?? []).map((entry) => entry.trim()).filter((entry) => entry.length > 0));
33
33
  }
34
+ // Locale data face (pure data, NO ctx calls — a chunk factory re-executes
35
+ // under HMR, so a chunk-local register would throw on the duplicate
36
+ // (namespace, locale) pair): the entry registers these under "settings.weir.page"
37
+ // inside a ctx.effect when this chunk arrives (lib/client.js
38
+ // registerChunkDictionaries). Pinned by test/client.test.js's dictionary
39
+ // union pin.
40
+ // (The entry merges every settings-batch slice into the single "settings.weir.page"
41
+ // registration — the whole settings card tree shares one t.)
42
+ const dictionaries = {
43
+ en: {
44
+ robashListEntries: "entries",
45
+ robashListAdd: "Add entry",
46
+ robashListInvalid: "The stored value is not a valid command list; the editor opened empty — saving replaces it.",
47
+ robashListPanelHint: "One command name per row; entries are added to the built-in list, which always applies — an empty list adds nothing.",
48
+ robashListEntryPlaceholder: "command name",
49
+ },
50
+ zh: {
51
+ robashListEntries: "条目",
52
+ robashListAdd: "添加条目",
53
+ robashListInvalid: "已保存的值不是有效的命令清单;编辑器已以空列表打开,保存将覆盖该值。",
54
+ robashListPanelHint: "每行一个命令名;条目追加到内置清单,内置清单始终生效——空列表即不追加。",
55
+ robashListEntryPlaceholder: "命令名",
56
+ },
57
+ };
58
+
59
+ exports.dictionaries = dictionaries;
34
60
  exports.jsonToStringList = jsonToStringList;
35
61
  exports.robashEditorOpenState = robashEditorOpenState;
36
62
  exports.stringListToJson = stringListToJson;