kiokuko-dsh 0.2.1 → 0.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 (286) hide show
  1. package/PERMISSIONS.md +21 -28
  2. package/README.ja.md +5 -2
  3. package/README.md +8 -2
  4. package/THIRD_PARTY_NOTICES.md +8 -0
  5. package/dist/client.cjs +2248 -1383
  6. package/dist/client.d.ts +3 -1
  7. package/dist/client.d.ts.map +1 -1
  8. package/dist/deep-thinker/configuration.d.ts.map +1 -1
  9. package/dist/deep-thinker/configuration.js +8 -5
  10. package/dist/deep-thinker/configuration.js.map +1 -1
  11. package/dist/deep-thinker/core/contracts.d.ts +107 -13
  12. package/dist/deep-thinker/core/contracts.d.ts.map +1 -1
  13. package/dist/deep-thinker/core/contracts.js +4 -2
  14. package/dist/deep-thinker/core/contracts.js.map +1 -1
  15. package/dist/deep-thinker/memory-finalizer.d.ts +10 -10
  16. package/dist/deep-thinker/memory-finalizer.js +2 -2
  17. package/dist/deep-thinker/memory-finalizer.js.map +1 -1
  18. package/dist/deep-thinker/report.d.ts +1 -1
  19. package/dist/deep-thinker/store.d.ts +10 -10
  20. package/dist/deep-thinker/store.d.ts.map +1 -1
  21. package/dist/deep-thinker/store.js +2 -2
  22. package/dist/deep-thinker/store.js.map +1 -1
  23. package/dist/dsh/completion-verifiers.d.ts +26 -0
  24. package/dist/dsh/completion-verifiers.d.ts.map +1 -0
  25. package/dist/dsh/completion-verifiers.js +9 -0
  26. package/dist/dsh/completion-verifiers.js.map +1 -0
  27. package/dist/dsh/config.d.ts +5 -1
  28. package/dist/dsh/config.d.ts.map +1 -1
  29. package/dist/dsh/core/host.d.ts +1 -1
  30. package/dist/dsh/core/host.d.ts.map +1 -1
  31. package/dist/dsh/core/host.js +49 -4
  32. package/dist/dsh/core/host.js.map +1 -1
  33. package/dist/dsh/core/index.d.ts +4 -4
  34. package/dist/dsh/core/tasks.d.ts.map +1 -1
  35. package/dist/dsh/core/tasks.js +7 -0
  36. package/dist/dsh/core/tasks.js.map +1 -1
  37. package/dist/dsh/core-runtime.d.ts +1 -0
  38. package/dist/dsh/core-runtime.d.ts.map +1 -1
  39. package/dist/dsh/core-runtime.js +4 -1
  40. package/dist/dsh/core-runtime.js.map +1 -1
  41. package/dist/dsh/decisions/config.d.ts +2 -2
  42. package/dist/dsh/enno-delegation.js +2 -2
  43. package/dist/dsh/enno-delegation.js.map +1 -1
  44. package/dist/dsh/execution-command.d.ts +4 -0
  45. package/dist/dsh/execution-command.d.ts.map +1 -0
  46. package/dist/dsh/execution-command.js +52 -0
  47. package/dist/dsh/execution-command.js.map +1 -0
  48. package/dist/dsh/execution-journal.d.ts +17 -0
  49. package/dist/dsh/execution-journal.d.ts.map +1 -0
  50. package/dist/dsh/execution-journal.js +69 -0
  51. package/dist/dsh/execution-journal.js.map +1 -0
  52. package/dist/dsh/execution-result.d.ts +20 -0
  53. package/dist/dsh/execution-result.d.ts.map +1 -0
  54. package/dist/dsh/execution-result.js +7 -0
  55. package/dist/dsh/execution-result.js.map +1 -0
  56. package/dist/dsh/execution-selection.d.ts +149 -22
  57. package/dist/dsh/execution-selection.d.ts.map +1 -1
  58. package/dist/dsh/execution-selection.js +18 -6
  59. package/dist/dsh/execution-selection.js.map +1 -1
  60. package/dist/dsh/host-adapter/admission.d.ts.map +1 -1
  61. package/dist/dsh/host-adapter/admission.js +20 -0
  62. package/dist/dsh/host-adapter/admission.js.map +1 -1
  63. package/dist/dsh/host-adapter/turn-state.js +1 -1
  64. package/dist/dsh/host-adapter/turn-state.js.map +1 -1
  65. package/dist/dsh/host-adapter.d.ts.map +1 -1
  66. package/dist/dsh/host-adapter.js +26 -13
  67. package/dist/dsh/host-adapter.js.map +1 -1
  68. package/dist/dsh/index.d.ts +4 -2
  69. package/dist/dsh/index.d.ts.map +1 -1
  70. package/dist/dsh/index.js +7 -0
  71. package/dist/dsh/index.js.map +1 -1
  72. package/dist/dsh/intake-gate.d.ts +1 -0
  73. package/dist/dsh/intake-gate.d.ts.map +1 -1
  74. package/dist/dsh/intake-gate.js +9 -1
  75. package/dist/dsh/intake-gate.js.map +1 -1
  76. package/dist/dsh/lisp/approval.d.ts +2 -1
  77. package/dist/dsh/lisp/approval.d.ts.map +1 -1
  78. package/dist/dsh/lisp/approval.js +4 -3
  79. package/dist/dsh/lisp/approval.js.map +1 -1
  80. package/dist/dsh/lisp/candidate.d.ts +3 -3
  81. package/dist/dsh/lisp/code-intelligence-contracts.d.ts +78 -0
  82. package/dist/dsh/lisp/code-intelligence-contracts.d.ts.map +1 -0
  83. package/dist/dsh/lisp/code-intelligence-contracts.js +54 -0
  84. package/dist/dsh/lisp/code-intelligence-contracts.js.map +1 -0
  85. package/dist/dsh/lisp/code-intelligence-provider.d.ts +116 -0
  86. package/dist/dsh/lisp/code-intelligence.d.ts +13 -0
  87. package/dist/dsh/lisp/code-intelligence.d.ts.map +1 -0
  88. package/dist/dsh/lisp/code-intelligence.js +181 -0
  89. package/dist/dsh/lisp/code-intelligence.js.map +1 -0
  90. package/dist/dsh/lisp/coding-choice.d.ts +1 -0
  91. package/dist/dsh/lisp/coding-choice.d.ts.map +1 -1
  92. package/dist/dsh/lisp/coding-choice.js +17 -6
  93. package/dist/dsh/lisp/coding-choice.js.map +1 -1
  94. package/dist/dsh/lisp/compiled-cache.d.ts.map +1 -1
  95. package/dist/dsh/lisp/compiled-cache.js +3 -2
  96. package/dist/dsh/lisp/compiled-cache.js.map +1 -1
  97. package/dist/dsh/lisp/contracts.d.ts +4 -0
  98. package/dist/dsh/lisp/contracts.d.ts.map +1 -1
  99. package/dist/dsh/lisp/contracts.js +1 -0
  100. package/dist/dsh/lisp/contracts.js.map +1 -1
  101. package/dist/dsh/lisp/manager.d.ts +8 -1
  102. package/dist/dsh/lisp/manager.d.ts.map +1 -1
  103. package/dist/dsh/lisp/manager.js +78 -24
  104. package/dist/dsh/lisp/manager.js.map +1 -1
  105. package/dist/dsh/lisp/model-result.d.ts.map +1 -1
  106. package/dist/dsh/lisp/model-result.js +35 -1
  107. package/dist/dsh/lisp/model-result.js.map +1 -1
  108. package/dist/dsh/lisp/proposal-batch.d.ts +2 -0
  109. package/dist/dsh/lisp/proposal-batch.d.ts.map +1 -1
  110. package/dist/dsh/lisp/proposal-batch.js +32 -2
  111. package/dist/dsh/lisp/proposal-batch.js.map +1 -1
  112. package/dist/dsh/lisp/sandbox.d.ts +3 -0
  113. package/dist/dsh/lisp/sandbox.d.ts.map +1 -1
  114. package/dist/dsh/lisp/sandbox.js +13 -6
  115. package/dist/dsh/lisp/sandbox.js.map +1 -1
  116. package/dist/dsh/lisp/surface.d.ts +1 -1
  117. package/dist/dsh/lisp/surface.d.ts.map +1 -1
  118. package/dist/dsh/lisp/surface.js +74 -20
  119. package/dist/dsh/lisp/surface.js.map +1 -1
  120. package/dist/dsh/lisp/worker.d.ts +2 -0
  121. package/dist/dsh/lisp/worker.d.ts.map +1 -1
  122. package/dist/dsh/lisp/worker.js +7 -5
  123. package/dist/dsh/lisp/worker.js.map +1 -1
  124. package/dist/dsh/memory-application.d.ts +1 -1
  125. package/dist/dsh/memory-application.d.ts.map +1 -1
  126. package/dist/dsh/memory-application.js +4 -3
  127. package/dist/dsh/memory-application.js.map +1 -1
  128. package/dist/dsh/model-auto/candidates.d.ts.map +1 -1
  129. package/dist/dsh/model-auto/candidates.js +31 -22
  130. package/dist/dsh/model-auto/candidates.js.map +1 -1
  131. package/dist/dsh/model-configuration.d.ts +145 -2
  132. package/dist/dsh/model-configuration.d.ts.map +1 -1
  133. package/dist/dsh/model-configuration.js +10 -3
  134. package/dist/dsh/model-configuration.js.map +1 -1
  135. package/dist/dsh/model-selection-ui.d.ts.map +1 -1
  136. package/dist/dsh/model-selection-ui.js +7 -2
  137. package/dist/dsh/model-selection-ui.js.map +1 -1
  138. package/dist/dsh/models/config-store.d.ts +8 -0
  139. package/dist/dsh/models/config-store.d.ts.map +1 -0
  140. package/dist/dsh/models/config-store.js +49 -0
  141. package/dist/dsh/models/config-store.js.map +1 -0
  142. package/dist/dsh/models/contracts.d.ts +107 -0
  143. package/dist/dsh/models/contracts.d.ts.map +1 -0
  144. package/dist/dsh/models/contracts.js +104 -0
  145. package/dist/dsh/models/contracts.js.map +1 -0
  146. package/dist/dsh/models/metadata.d.ts +7 -0
  147. package/dist/dsh/models/metadata.d.ts.map +1 -0
  148. package/dist/dsh/models/metadata.js +13 -0
  149. package/dist/dsh/models/metadata.js.map +1 -0
  150. package/dist/dsh/models/profiles.d.ts +10 -0
  151. package/dist/dsh/models/profiles.d.ts.map +1 -0
  152. package/dist/dsh/models/profiles.js +176 -0
  153. package/dist/dsh/models/profiles.js.map +1 -0
  154. package/dist/dsh/models/service.d.ts +97 -0
  155. package/dist/dsh/models/service.d.ts.map +1 -0
  156. package/dist/dsh/models/service.js +518 -0
  157. package/dist/dsh/models/service.js.map +1 -0
  158. package/dist/dsh/models/surface.d.ts +8 -0
  159. package/dist/dsh/models/surface.d.ts.map +1 -0
  160. package/dist/dsh/models/surface.js +162 -0
  161. package/dist/dsh/models/surface.js.map +1 -0
  162. package/dist/dsh/models/vendor/THIRD_PARTY_NOTICES.txt +134 -0
  163. package/dist/dsh/models/vendor/abortable.d.ts +3 -0
  164. package/dist/dsh/models/vendor/abortable.d.ts.map +1 -0
  165. package/dist/dsh/models/vendor/abortable.js +10 -0
  166. package/dist/dsh/models/vendor/abortable.js.map +1 -0
  167. package/dist/dsh/models/vendor/auth-contract.d.ts +67 -0
  168. package/dist/dsh/models/vendor/auth-contract.d.ts.map +1 -0
  169. package/dist/dsh/models/vendor/auth-contract.js +2 -0
  170. package/dist/dsh/models/vendor/auth-contract.js.map +1 -0
  171. package/dist/dsh/models/vendor/credentials.d.ts +58 -0
  172. package/dist/dsh/models/vendor/credentials.d.ts.map +1 -0
  173. package/dist/dsh/models/vendor/credentials.js +205 -0
  174. package/dist/dsh/models/vendor/credentials.js.map +1 -0
  175. package/dist/dsh/models/vendor/nous-oauth.d.ts +6 -0
  176. package/dist/dsh/models/vendor/nous-oauth.d.ts.map +1 -0
  177. package/dist/dsh/models/vendor/nous-oauth.js +113 -0
  178. package/dist/dsh/models/vendor/nous-oauth.js.map +1 -0
  179. package/dist/dsh/models/vendor/opencode-adapter.d.ts +38 -0
  180. package/dist/dsh/models/vendor/opencode-adapter.d.ts.map +1 -0
  181. package/dist/dsh/models/vendor/opencode-adapter.js +207 -0
  182. package/dist/dsh/models/vendor/opencode-adapter.js.map +1 -0
  183. package/dist/dsh/models/vendor/opencode-catalog.d.ts +73 -0
  184. package/dist/dsh/models/vendor/opencode-catalog.d.ts.map +1 -0
  185. package/dist/dsh/models/vendor/opencode-catalog.js +253 -0
  186. package/dist/dsh/models/vendor/opencode-catalog.js.map +1 -0
  187. package/dist/dsh/models/vendor/opencode-codec.d.ts +7 -0
  188. package/dist/dsh/models/vendor/opencode-codec.d.ts.map +1 -0
  189. package/dist/dsh/models/vendor/opencode-codec.js +107 -0
  190. package/dist/dsh/models/vendor/opencode-codec.js.map +1 -0
  191. package/dist/dsh/models/vendor/opencode-owned.generated.d.ts +114 -0
  192. package/dist/dsh/models/vendor/opencode-owned.generated.d.ts.map +1 -0
  193. package/dist/dsh/models/vendor/opencode-owned.generated.js +2867 -0
  194. package/dist/dsh/models/vendor/opencode-owned.generated.js.map +1 -0
  195. package/dist/dsh/models/vendor/opencode-replay.d.ts +17 -0
  196. package/dist/dsh/models/vendor/opencode-replay.d.ts.map +1 -0
  197. package/dist/dsh/models/vendor/opencode-replay.js +147 -0
  198. package/dist/dsh/models/vendor/opencode-replay.js.map +1 -0
  199. package/dist/dsh/models/vendor/opencode-sdk.d.ts +6 -0
  200. package/dist/dsh/models/vendor/opencode-sdk.d.ts.map +1 -0
  201. package/dist/dsh/models/vendor/opencode-sdk.js +49915 -0
  202. package/dist/dsh/models/vendor/opencode-sdk.js.map +7 -0
  203. package/dist/dsh/models/vendor/opencode-transport.d.ts +6 -0
  204. package/dist/dsh/models/vendor/opencode-transport.d.ts.map +1 -0
  205. package/dist/dsh/models/vendor/opencode-transport.js +70 -0
  206. package/dist/dsh/models/vendor/opencode-transport.js.map +1 -0
  207. package/dist/dsh/models/vendor/pi-ai.d.ts +23 -0
  208. package/dist/dsh/models/vendor/pi-ai.d.ts.map +1 -0
  209. package/dist/dsh/models/vendor/pi-ai.js +27 -0
  210. package/dist/dsh/models/vendor/pi-ai.js.map +1 -0
  211. package/dist/dsh/modules/lisp.d.ts +1 -1
  212. package/dist/dsh/modules/lisp.d.ts.map +1 -1
  213. package/dist/dsh/modules/lisp.js +1 -1
  214. package/dist/dsh/modules/lisp.js.map +1 -1
  215. package/dist/dsh/native-model-catalog.d.ts.map +1 -1
  216. package/dist/dsh/native-model-catalog.js +4 -3
  217. package/dist/dsh/native-model-catalog.js.map +1 -1
  218. package/dist/dsh/node-tap-summary.d.ts +6 -1
  219. package/dist/dsh/node-tap-summary.d.ts.map +1 -1
  220. package/dist/dsh/node-tap-summary.js +27 -2
  221. package/dist/dsh/node-tap-summary.js.map +1 -1
  222. package/dist/dsh/on-demand-intake.d.ts +1 -1
  223. package/dist/dsh/on-demand-intake.d.ts.map +1 -1
  224. package/dist/dsh/on-demand-intake.js +23 -11
  225. package/dist/dsh/on-demand-intake.js.map +1 -1
  226. package/dist/dsh/owned-evidence.d.ts +49 -0
  227. package/dist/dsh/owned-evidence.d.ts.map +1 -0
  228. package/dist/dsh/owned-evidence.js +69 -0
  229. package/dist/dsh/owned-evidence.js.map +1 -0
  230. package/dist/dsh/owned-execution.d.ts +98 -0
  231. package/dist/dsh/owned-execution.d.ts.map +1 -0
  232. package/dist/dsh/owned-execution.js +523 -0
  233. package/dist/dsh/owned-execution.js.map +1 -0
  234. package/dist/dsh/plan-transition.d.ts +5 -0
  235. package/dist/dsh/plan-transition.d.ts.map +1 -0
  236. package/dist/dsh/plan-transition.js +34 -0
  237. package/dist/dsh/plan-transition.js.map +1 -0
  238. package/dist/dsh/session-memory-finalizer.d.ts.map +1 -1
  239. package/dist/dsh/session-memory-finalizer.js +46 -3
  240. package/dist/dsh/session-memory-finalizer.js.map +1 -1
  241. package/dist/dsh/setup.d.ts +1 -1
  242. package/dist/dsh/setup.d.ts.map +1 -1
  243. package/dist/dsh/setup.js +2 -0
  244. package/dist/dsh/setup.js.map +1 -1
  245. package/dist/dsh/skill-prompts.json +1 -1
  246. package/dist/dsh/task-completion.d.ts +1 -0
  247. package/dist/dsh/task-completion.d.ts.map +1 -1
  248. package/dist/dsh/task-completion.js +19 -19
  249. package/dist/dsh/task-completion.js.map +1 -1
  250. package/dist/enno-oduno/schemas.d.ts +9 -9
  251. package/dist/enno-oduno/store.d.ts.map +1 -1
  252. package/dist/enno-oduno/store.js +12 -0
  253. package/dist/enno-oduno/store.js.map +1 -1
  254. package/dist/locale/en.json +1 -1
  255. package/dist/locale/ja.json +1 -1
  256. package/dist/locale/ko.json +1 -1
  257. package/dist/locale/zh-CN.json +1 -1
  258. package/dist/memory/application.d.ts +2 -1
  259. package/dist/memory/application.d.ts.map +1 -1
  260. package/dist/memory/application.js +26 -12
  261. package/dist/memory/application.js.map +1 -1
  262. package/dist/memory/evidence.d.ts +2 -0
  263. package/dist/memory/evidence.d.ts.map +1 -1
  264. package/dist/memory/evidence.js +3 -1
  265. package/dist/memory/evidence.js.map +1 -1
  266. package/dist/model-tools/contracts.d.ts +4 -4
  267. package/dist/models-client.cjs +635 -0
  268. package/dist/models-client.d.ts +26 -0
  269. package/dist/models-client.d.ts.map +1 -0
  270. package/dist/models-client.js +642 -0
  271. package/dist/models-client.js.map +1 -0
  272. package/docs/code-intelligence.md +149 -0
  273. package/docs/dsh-auth-LICENSE.txt +22 -0
  274. package/docs/dsh-auth-provenance.md +25 -0
  275. package/docs/dsh-plugin.md +19 -0
  276. package/docs/kiokuko-models.md +66 -0
  277. package/docs/lisp.md +107 -56
  278. package/docs/model-selection.ja.md +7 -9
  279. package/docs/model-selection.md +14 -12
  280. package/docs/owned-execution.md +99 -0
  281. package/lisp/supervisor.mjs +8 -2
  282. package/lisp/tools.lisp +31 -3
  283. package/migrations/035_owned_execution.sql +19 -0
  284. package/package.json +46 -11
  285. package/skills/kiokuko-lisp/SKILL.md +49 -67
  286. package/skills/kiokuko-soul/SKILL.md +7 -28
package/dist/client.cjs CHANGED
@@ -7,888 +7,1575 @@ window.__ModuleLoader__.load({
7
7
  const { jsx, jsxs, Fragment } = require("react/jsx-runtime");
8
8
  const { useState, useRef, useEffect } = require("react");
9
9
  const { Modal, Button, IconDownloadOutline16, MarkdownText } = require("@deepseek-ai/dsh-client-ui-primitives");
10
- function LispApprovalControl(props) {
11
- const scope = props.approvalScope;
12
- const [state, setState] = useState(() => scope.getSnapshot());
13
- const [busy, setBusy] = useState(false);
14
- const [error, setError] = useState('');
15
- useEffect(() => scope.subscribe(() => setState(scope.getSnapshot())), [scope]);
16
- const modern = props.modern === true;
17
- const field = modern ? 'lisp' : Object.keys(state.base ?? {}).find(key => key === 'approvalMode' || key.startsWith('approvalMode-'));
18
- const mode = modern ? state.value?.lisp?.approvalMode : field ? state.value?.[field] : undefined;
19
- const update = async (mode) => {
20
- if (busy || !field)
21
- return;
22
- setBusy(true);
23
- setError('');
24
- try {
25
- const saved = modern ? await scope.mutate([{ op: 'set', path: ['lisp', 'approvalMode'], value: mode }]) : await scope.set(field, mode);
26
- if (saved === false)
27
- throw new Error('The host did not save the approval setting. Reload the current value and try again.');
28
- }
29
- catch (error) {
30
- setError(messageOf(error));
31
- }
32
- finally {
33
- setBusy(false);
10
+ "use strict";
11
+ var __defProp = Object.defineProperty;
12
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
13
+ var __getOwnPropNames = Object.getOwnPropertyNames;
14
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
15
+ var __export = (target, all) => {
16
+ for (var name in all)
17
+ __defProp(target, name, { get: all[name], enumerable: true });
18
+ };
19
+ var __copyProps = (to, from, except, desc) => {
20
+ if (from && typeof from === "object" || typeof from === "function") {
21
+ for (let key of __getOwnPropNames(from))
22
+ if (!__hasOwnProp.call(to, key) && key !== except)
23
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
24
+ }
25
+ return to;
26
+ };
27
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
28
+
29
+ // dist/client.js
30
+ var client_exports = {};
31
+ __export(client_exports, {
32
+ apply: () => apply,
33
+ downloadDshSessionLog: () => downloadDshSessionLog,
34
+ inject: () => inject
35
+ });
36
+ module.exports = __toCommonJS(client_exports);
37
+
38
+ // dist/models-client.js
39
+ function messageOf(error) {
40
+ return error instanceof Error ? error.message : String(error);
41
+ }
42
+ var MODELS_NS = "kiokuko-models";
43
+ var MODEL_CONNECTION_DEFAULTS = {
44
+ openai: ["OpenAI API", "https://api.openai.com/v1", "responses"],
45
+ codex: ["Codex", "https://chatgpt.com/backend-api/codex", "responses"],
46
+ claude: ["Claude", "https://api.anthropic.com", "messages"],
47
+ xai: ["xAI", "https://api.x.ai/v1", "chat-completions"],
48
+ deepseek: ["DeepSeek", "https://api.deepseek.com/v1", "chat-completions"],
49
+ opencode: ["OpenCode Zen", "https://opencode.ai/zen/v1", "chat-completions"],
50
+ "opencode-go": [
51
+ "OpenCode Go",
52
+ "https://opencode.ai/zen/go/v1",
53
+ "chat-completions"
54
+ ],
55
+ openrouter: [
56
+ "OpenRouter",
57
+ "https://openrouter.ai/api/v1",
58
+ "chat-completions"
59
+ ],
60
+ nous: [
61
+ "Nous / Hermes",
62
+ "https://inference-api.nousresearch.com/v1",
63
+ "chat-completions"
64
+ ],
65
+ infron: ["Infron", "https://llm.onerouter.pro/v1", "chat-completions"],
66
+ ollama: ["Ollama", "http://localhost:11434/v1", "chat-completions"],
67
+ custom: ["Custom connection", "http://localhost:8080/v1", "chat-completions"]
68
+ };
69
+ async function modelsApi(body, query = "") {
70
+ const response = await fetch("/api/kiokuko.models" + query, {
71
+ ...body ? {
72
+ method: "POST",
73
+ headers: { "content-type": "application/json" },
74
+ body: JSON.stringify(body)
75
+ } : {},
76
+ cache: "no-store"
77
+ });
78
+ const value = await response.json();
79
+ if (!response.ok)
80
+ throw new Error(value.error ?? "Kiokuko Models request failed");
81
+ return value;
82
+ }
83
+ function KiokukoModelsPanel(props) {
84
+ const [state, setState] = useState({ revision: 0, connections: [] }), [connection, setConnection] = useState(null), [models, setModels] = useState([]), [filter, setFilter] = useState("");
85
+ const [busy, setBusy] = useState(false), [error, setError] = useState(""), [notice, setNotice] = useState(""), [key, setKey] = useState(""), [auth, setAuth] = useState(null), [answer, setAnswer] = useState("");
86
+ const [manual, setManual] = useState({
87
+ id: "",
88
+ contextWindow: 262144,
89
+ maxTokens: 32768,
90
+ reasoning: false,
91
+ image: false
92
+ });
93
+ const operation = useRef(void 0), mounted = useRef(true), working = useRef(false);
94
+ const sessionId = props.sessionId;
95
+ const load = async () => {
96
+ const value = await modelsApi();
97
+ if (mounted.current)
98
+ setState(value);
99
+ return value;
100
+ };
101
+ useEffect(() => {
102
+ mounted.current = true;
103
+ void load().catch((e) => setError(messageOf(e)));
104
+ return () => {
105
+ mounted.current = false;
106
+ if (operation.current)
107
+ void modelsApi({ op: "cancel", id: operation.current }).catch(() => void 0);
108
+ };
109
+ }, []);
110
+ const run = async (action) => {
111
+ if (working.current)
112
+ return;
113
+ working.current = true;
114
+ setBusy(true);
115
+ setError("");
116
+ setNotice("");
117
+ try {
118
+ await action();
119
+ } catch (e) {
120
+ if (mounted.current)
121
+ setError(messageOf(e));
122
+ } finally {
123
+ working.current = false;
124
+ if (mounted.current)
125
+ setBusy(false);
126
+ }
127
+ };
128
+ const choose = async (c) => {
129
+ setConnection({ ...c });
130
+ setKey("");
131
+ setError("");
132
+ setModels([]);
133
+ try {
134
+ setModels(await modelsApi(void 0, "?op=models&id=" + encodeURIComponent(c.id)));
135
+ } catch (e) {
136
+ setError(messageOf(e));
137
+ }
138
+ };
139
+ const save = async () => {
140
+ await modelsApi({
141
+ op: "save",
142
+ connection: {
143
+ id: connection.id,
144
+ kind: connection.kind,
145
+ name: connection.name,
146
+ baseURL: connection.baseURL,
147
+ protocol: connection.protocol,
148
+ serviceTier: connection.serviceTier,
149
+ models: connection.models
150
+ },
151
+ revision: state.revision
152
+ });
153
+ const next = await load();
154
+ await choose(next.connections.find((c) => c.id === connection.id));
155
+ setNotice("Connection saved");
156
+ };
157
+ const newConnection = (kind) => {
158
+ const spec = MODEL_CONNECTION_DEFAULTS[kind];
159
+ setConnection({
160
+ id: "kiokuko-" + kind + "-" + crypto.randomUUID().slice(0, 8),
161
+ kind,
162
+ name: spec[0],
163
+ baseURL: spec[1],
164
+ protocol: spec[2],
165
+ serviceTier: "standard",
166
+ models: []
167
+ });
168
+ setModels([]);
169
+ setKey("");
170
+ setError("");
171
+ };
172
+ const update = (field, value) => setConnection({ ...connection, [field]: value });
173
+ const startLogin = async () => {
174
+ await save();
175
+ const result = await modelsApi({ op: "login", id: connection.id });
176
+ operation.current = result.operationId;
177
+ setAuth({ id: result.operationId, status: "pending", events: [] });
178
+ };
179
+ useEffect(() => {
180
+ if (!auth || auth.status !== "pending")
181
+ return;
182
+ let stopped = false;
183
+ const timer = setInterval(() => {
184
+ void modelsApi(void 0, "?op=auth&id=" + encodeURIComponent(auth.id)).then((next) => {
185
+ if (stopped)
186
+ return;
187
+ setAuth(next);
188
+ if (next.status !== "pending") {
189
+ operation.current = void 0;
190
+ void load().then((value) => {
191
+ if (next.status === "success")
192
+ return choose(value.connections.find((c) => c.id === connection.id));
193
+ setError(next.error ?? "Authentication cancelled");
194
+ }).catch((e) => setError(messageOf(e)));
34
195
  }
196
+ }, (e) => {
197
+ if (!stopped)
198
+ setError(messageOf(e));
199
+ });
200
+ }, 500);
201
+ return () => {
202
+ stopped = true;
203
+ clearInterval(timer);
204
+ };
205
+ }, [auth?.id, auth?.status]);
206
+ const input = (label, value, onChange, type = "text") => jsx("label", {
207
+ children: jsxs(Fragment, {
208
+ children: [
209
+ jsx("span", { children: label }),
210
+ jsx("input", {
211
+ type,
212
+ value,
213
+ "aria-label": label,
214
+ disabled: busy || label === "Base URL" && ["codex", "opencode", "opencode-go"].includes(connection?.kind),
215
+ onChange: (e) => onChange(e.target.value),
216
+ autoComplete: type === "password" ? "new-password" : "off"
217
+ })
218
+ ]
219
+ })
220
+ });
221
+ const button = (label, action, disabled = false) => jsx("button", {
222
+ type: "button",
223
+ disabled: busy || disabled,
224
+ onClick: action,
225
+ children: label
226
+ });
227
+ return jsxs("section", {
228
+ className: "kiokuko-models-panel",
229
+ "aria-label": "Kiokuko Models",
230
+ "aria-busy": busy,
231
+ children: [
232
+ jsx("h2", { children: "Kiokuko Models" }),
233
+ jsx("p", {
234
+ children: "Connect directly for chat, Enno and Deep. Credentials are shared with dsh-cli. Signing out also signs dsh-cli out."
235
+ }),
236
+ ...error ? [jsx("p", { role: "alert", children: error })] : [],
237
+ ...notice ? [jsx("p", { role: "status", children: notice })] : [],
238
+ ...busy ? [jsx("p", { role: "status", children: "Working\u2026" })] : [],
239
+ button("Reload", () => void run(load)),
240
+ jsx("label", {
241
+ children: jsxs(Fragment, {
242
+ children: [
243
+ "Add connection",
244
+ jsxs("select", {
245
+ "aria-label": "Add connection",
246
+ value: "",
247
+ disabled: busy,
248
+ onChange: (e) => newConnection(e.target.value),
249
+ children: [
250
+ jsx("option", { value: "", children: "Choose provider\u2026" }),
251
+ ...Object.entries(MODEL_CONNECTION_DEFAULTS).map(([id, spec]) => jsx("option", { value: id, children: spec[0] }, id))
252
+ ]
253
+ })
254
+ ]
255
+ })
256
+ }),
257
+ jsx("ul", {
258
+ children: state.connections.map((c) => jsx("li", {
259
+ children: button(c.name + " \xB7 " + c.authState, () => void choose(c))
260
+ }, c.id))
261
+ }),
262
+ ...connection ? [
263
+ jsxs("div", {
264
+ className: "kiokuko-models-editor",
265
+ children: [
266
+ input("Connection name", connection.name, (value) => update("name", value)),
267
+ input("Base URL", connection.baseURL, (value) => update("baseURL", value)),
268
+ jsx("label", {
269
+ children: jsxs(Fragment, {
270
+ children: [
271
+ "Protocol",
272
+ jsx("select", {
273
+ "aria-label": "Protocol",
274
+ value: connection.protocol,
275
+ disabled: busy || [
276
+ "codex",
277
+ "infron",
278
+ "opencode",
279
+ "opencode-go"
280
+ ].includes(connection.kind),
281
+ onChange: (e) => update("protocol", e.target.value),
282
+ children: [
283
+ ["chat-completions", "Chat Completions"],
284
+ ["responses", "Responses"],
285
+ ["messages", "Messages"]
286
+ ].map(([id, name]) => jsx("option", { value: id, children: name }, id))
287
+ })
288
+ ]
289
+ })
290
+ }),
291
+ ...connection.kind === "infron" ? [
292
+ jsx("label", {
293
+ children: jsxs(Fragment, {
294
+ children: [
295
+ "Service tier",
296
+ jsx("select", {
297
+ "aria-label": "Service tier",
298
+ value: connection.serviceTier,
299
+ disabled: busy,
300
+ onChange: (e) => update("serviceTier", e.target.value),
301
+ children: [
302
+ jsx("option", {
303
+ value: "standard",
304
+ children: "Standard"
305
+ }),
306
+ jsx("option", {
307
+ value: "flex",
308
+ children: "Flex"
309
+ })
310
+ ]
311
+ })
312
+ ]
313
+ })
314
+ }),
315
+ jsx("p", {
316
+ children: "Requested tier: " + connection.serviceTier + ". Last request: " + (connection.lastRequestedTier ?? "none") + ". Actual served tier: " + (connection.actualTier ?? "unknown") + ". Infron may serve Standard for a Flex request."
317
+ })
318
+ ] : [],
319
+ button("Save connection", () => void run(save)),
320
+ ...connection.kind !== "codex" && connection.kind !== "ollama" ? [
321
+ input("API key / bearer token", key, setKey, "password"),
322
+ button("Save key", () => void run(async () => {
323
+ await save();
324
+ await modelsApi({
325
+ op: "key",
326
+ id: connection.id,
327
+ key
328
+ });
329
+ setKey("");
330
+ await load();
331
+ await choose(connection);
332
+ setNotice("Key saved");
333
+ }), !key)
334
+ ] : [],
335
+ ...[
336
+ "codex",
337
+ "claude",
338
+ "xai",
339
+ "openai",
340
+ "openrouter",
341
+ "nous"
342
+ ].includes(connection.kind) ? [
343
+ button("Log in", () => void run(startLogin), auth?.status === "pending")
344
+ ] : [],
345
+ button("Log out (also dsh-cli)", () => void run(async () => {
346
+ await modelsApi({ op: "logout", id: connection.id });
347
+ setKey("");
348
+ setModels([]);
349
+ await load();
350
+ setNotice("Signed out");
351
+ })),
352
+ button("Refresh models", () => void run(async () => {
353
+ await modelsApi({ op: "refresh", id: connection.id });
354
+ const next = await load();
355
+ await choose(next.connections.find((c) => c.id === connection.id));
356
+ setNotice("Models refreshed");
357
+ })),
358
+ ...auth?.status === "pending" ? [
359
+ jsxs("div", {
360
+ "aria-label": "Authentication",
361
+ children: [
362
+ jsx("p", {
363
+ role: "status",
364
+ children: "Waiting for authentication"
365
+ }),
366
+ ...auth.events.map((event, index) => jsx("div", {
367
+ children: event.type === "auth_url" ? jsxs(Fragment, {
368
+ children: [
369
+ jsx("a", {
370
+ href: event.url,
371
+ target: "_blank",
372
+ rel: "noopener noreferrer",
373
+ children: "Open authentication link"
374
+ }),
375
+ jsx("p", {
376
+ children: event.instructions
377
+ })
378
+ ]
379
+ }) : event.type === "device_code" ? jsxs(Fragment, {
380
+ children: [
381
+ jsx("a", {
382
+ href: event.verificationUri,
383
+ target: "_blank",
384
+ rel: "noopener noreferrer",
385
+ children: "Open sign-in page"
386
+ }),
387
+ jsx("p", {
388
+ children: "Code: " + event.userCode
389
+ })
390
+ ]
391
+ }) : jsxs(Fragment, {
392
+ children: [
393
+ jsx("p", {
394
+ children: event.message
395
+ }),
396
+ ...(event.links ?? []).map((link) => jsx("a", {
397
+ href: link.url,
398
+ target: "_blank",
399
+ rel: "noopener noreferrer",
400
+ children: link.label ?? "Open link"
401
+ }, link.url))
402
+ ]
403
+ })
404
+ }, index)),
405
+ ...auth.prompt ? [
406
+ auth.prompt.type === "select" ? jsx("label", {
407
+ children: jsxs(Fragment, {
408
+ children: [
409
+ auth.prompt.message,
410
+ jsxs("select", {
411
+ "aria-label": auth.prompt.message,
412
+ value: answer,
413
+ onChange: (e) => setAnswer(e.target.value),
414
+ children: [
415
+ jsx("option", {
416
+ value: "",
417
+ children: "Choose\u2026"
418
+ }),
419
+ ...auth.prompt.options.map((option) => jsx("option", {
420
+ value: option.id,
421
+ children: option.label
422
+ }, option.id))
423
+ ]
424
+ })
425
+ ]
426
+ })
427
+ }) : input(auth.prompt.message, answer, setAnswer, auth.prompt.type === "secret" ? "password" : "text"),
428
+ button("Submit authentication answer", () => void run(async () => {
429
+ await modelsApi({
430
+ op: "answer",
431
+ id: auth.id,
432
+ answer
433
+ });
434
+ setAnswer("");
435
+ }))
436
+ ] : [],
437
+ button("Cancel authentication", () => void run(async () => {
438
+ await modelsApi({ op: "cancel", id: auth.id });
439
+ operation.current = void 0;
440
+ setAuth(null);
441
+ setNotice("Authentication cancelled");
442
+ }))
443
+ ]
444
+ })
445
+ ] : [],
446
+ ...!["opencode", "opencode-go"].includes(connection.kind) ? [
447
+ jsx("details", {
448
+ children: jsxs(Fragment, {
449
+ children: [
450
+ jsx("summary", {
451
+ children: "Register model manually"
452
+ }),
453
+ input("Model ID", manual.id, (value) => setManual({ ...manual, id: value })),
454
+ input("Context window", manual.contextWindow, (value) => setManual({
455
+ ...manual,
456
+ contextWindow: Number(value)
457
+ }), "number"),
458
+ input("Maximum output tokens", manual.maxTokens, (value) => setManual({
459
+ ...manual,
460
+ maxTokens: Number(value)
461
+ }), "number"),
462
+ jsx("label", {
463
+ children: jsxs(Fragment, {
464
+ children: [
465
+ jsx("input", {
466
+ type: "checkbox",
467
+ checked: manual.reasoning,
468
+ onChange: (e) => setManual({
469
+ ...manual,
470
+ reasoning: e.target.checked
471
+ })
472
+ }),
473
+ "Reasoning supported"
474
+ ]
475
+ })
476
+ }),
477
+ jsx("label", {
478
+ children: jsxs(Fragment, {
479
+ children: [
480
+ jsx("input", {
481
+ type: "checkbox",
482
+ checked: manual.image,
483
+ onChange: (e) => setManual({
484
+ ...manual,
485
+ image: e.target.checked
486
+ })
487
+ }),
488
+ "Image input supported"
489
+ ]
490
+ })
491
+ }),
492
+ button("Add manual model", () => {
493
+ update("models", [
494
+ ...connection.models.filter((m) => m.id !== manual.id),
495
+ { ...manual, name: manual.id }
496
+ ]);
497
+ setNotice("Model added to draft. Save connection to register it.");
498
+ }, !manual.id)
499
+ ]
500
+ })
501
+ })
502
+ ] : [],
503
+ input("Search models", filter, setFilter),
504
+ jsx("ul", {
505
+ children: models.filter((m) => (m.id + " " + m.name).toLowerCase().includes(filter.toLowerCase())).map((m) => jsx("li", {
506
+ children: jsxs(Fragment, {
507
+ children: [
508
+ jsx("span", { children: m.name + " \xB7 " + m.id }),
509
+ button("Use in current chat", () => void run(async () => {
510
+ await modelsApi({
511
+ op: "select",
512
+ id: connection.id,
513
+ model: m.id,
514
+ scope: "current",
515
+ sessionId
516
+ });
517
+ setNotice("Model selected for this chat");
518
+ }), !sessionId),
519
+ button("Default for new chats", () => void run(async () => {
520
+ await modelsApi({
521
+ op: "select",
522
+ id: connection.id,
523
+ model: m.id,
524
+ scope: "default"
525
+ });
526
+ setNotice("Default model saved for new chats");
527
+ }))
528
+ ]
529
+ })
530
+ }, m.id))
531
+ })
532
+ ]
533
+ })
534
+ ] : []
535
+ ]
536
+ });
537
+ }
538
+ function KiokukoModelsDialog(props) {
539
+ const useModels = props.useKiokukoModels;
540
+ const state = useModels((s) => s), close = props.closeModels;
541
+ return jsx(Modal, {
542
+ open: state.open,
543
+ className: "kiokuko-models-modal",
544
+ title: "Kiokuko Models",
545
+ closeLabel: "Close",
546
+ onClose: close,
547
+ children: state.open ? jsx(KiokukoModelsPanel, { sessionId: state.sessionId }) : null
548
+ });
549
+ }
550
+ function KiokukoModelsAnchor(props) {
551
+ const active = props.activeModelsSession;
552
+ useEffect(() => {
553
+ active.current = props.sessionId;
554
+ return () => {
555
+ if (active.current === props.sessionId)
556
+ active.current = void 0;
35
557
  };
36
- return jsxs('div', { children: [
37
- jsx('label', { children: jsxs(Fragment, { children: [
38
- 'Lisp approvals · Entire profile ',
39
- jsxs('select', { 'aria-label': 'Lisp approvals', value: mode ?? 'ask', disabled: busy || !field || state.status !== 'ready' || !state.writable,
40
- onChange: (event) => void update(event.target.value),
41
- children: [jsx('option', { value: 'ask', children: 'Ask' }), jsx('option', { value: 'auto', children: 'Auto-approve all' })] }),
42
- ] }) }),
43
- ...(state.status === 'ready' && state.writable ? [] : [jsx('span', { children: ' Profile settings are unavailable or read-only.' })]),
44
- ...(error ? [jsx('span', { role: 'alert', children: error })] : []),
45
- ...(busy ? [jsx('span', { role: 'status', children: 'Saving…' })] : []),
46
- ] });
558
+ }, [props.sessionId]);
559
+ return null;
47
560
  }
48
- const SESSION_EXPORT_PATH = '/api/session.export';
49
- const LOCALE_NAMESPACE = 'kiokuko-session-log-download';
50
- const INITIAL_DOWNLOAD_STATE = { bySession: {} };
51
- const en = {
52
- 'header.action': 'Session log',
53
- 'dialog.preparingTitle': 'Exporting Session',
54
- 'dialog.preparingDescription': 'Streaming this Session, its sub-Sessions, and attachments to a ZIP file.',
55
- 'dialog.successTitle': 'Session export complete',
56
- 'dialog.successDescription': 'The Session ZIP has been saved or handed to the browser download manager.',
57
- 'dialog.errorTitle': 'Session export failed',
58
- 'dialog.close': 'Close',
59
- 'dialog.commandFailed': 'Could not export the Session log.',
60
- 'review.discuss': 'Chat about it',
61
- 'review.copy': 'Copy',
62
- 'review.copied': 'Copied',
63
- 'review.footnotes': 'Footnotes',
64
- 'review.title': 'Diff review',
65
- 'review.description': 'Review changes with repository facts and Kiokuko context.',
561
+ function mountModelsClient(ctx) {
562
+ const store = createSnapshotStore({
563
+ open: false
564
+ }), active = { current: void 0 };
565
+ let focusFrame;
566
+ const close = () => {
567
+ store.update((s) => {
568
+ s.open = false;
569
+ });
570
+ if (focusFrame !== void 0)
571
+ cancelAnimationFrame(focusFrame);
572
+ focusFrame = requestAnimationFrame(() => {
573
+ focusFrame = void 0;
574
+ const sessionId = active.current;
575
+ if (!sessionId)
576
+ return;
577
+ const sessions = ctx.get?.("sessions", false);
578
+ const scope = sessions?.scope(sessionId);
579
+ if (!scope)
580
+ return;
581
+ const conversation = scope.get("conversation", false);
582
+ conversation?.input.for(scope).focus();
583
+ });
584
+ };
585
+ ctx.effect(() => () => {
586
+ if (focusFrame !== void 0)
587
+ cancelAnimationFrame(focusFrame);
588
+ }, "kiokuko models focus lifecycle");
589
+ ctx.effect(() => {
590
+ const style = document.createElement("style");
591
+ style.textContent = ".kiokuko-models-modal{width:min(900px,calc(100vw - 32px))}.kiokuko-models-panel{box-sizing:border-box;width:100%;max-width:900px;padding:16px;overflow:auto;max-height:75vh}.kiokuko-models-panel label{display:flex;gap:10px;align-items:center;flex-wrap:wrap;margin:12px 0}.kiokuko-models-panel input,.kiokuko-models-panel select{max-width:100%;box-sizing:border-box;border:1px solid #888;border-radius:6px;padding:8px;color:inherit;background:transparent}.kiokuko-models-panel button{border:1px solid #888;border-radius:6px;padding:8px;margin:4px;color:inherit}.kiokuko-models-panel button:disabled{opacity:.5}.kiokuko-models-panel li{margin:8px 0}.kiokuko-models-panel :focus-visible{outline:2px solid #3286ff;outline-offset:3px}";
592
+ document.head.append(style);
593
+ return () => style.remove();
594
+ }, "kiokuko models style");
595
+ ctx.slots.inject("settings.section", () => ctx.slots.register({
596
+ name: "settings.section",
597
+ id: MODELS_NS,
598
+ order: 1e3,
599
+ label: "Kiokuko Models",
600
+ locale: MODELS_NS
601
+ }, () => jsx(KiokukoModelsPanel, { sessionId: active.current })));
602
+ ctx.slots.inject("conversation.input.left", () => ctx.slots.register({
603
+ name: "conversation.input.left",
604
+ id: "kiokuko-models-dialog",
605
+ locale: MODELS_NS,
606
+ inject: () => ({
607
+ hooks: { kiokukoModels: store },
608
+ closeModels: close,
609
+ activeModelsSession: active
610
+ })
611
+ }, KiokukoModelsAnchor));
612
+ ctx.slots.inject("shell.overlay", () => ctx.slots.register({
613
+ name: "shell.overlay",
614
+ id: "kiokuko-models-overlay",
615
+ locale: MODELS_NS,
616
+ inject: () => ({ hooks: { kiokukoModels: store }, closeModels: close })
617
+ }, KiokukoModelsDialog));
618
+ ctx.on("command/executed", (sessionId, name, result) => {
619
+ if (name === "kiokuko" && result.kind === "success")
620
+ store.update((s) => {
621
+ s.open = true;
622
+ s.sessionId = sessionId;
623
+ });
624
+ });
625
+ ctx.effect(() => ctx.locale.register(MODELS_NS, {
626
+ en: { "meta.title": "Kiokuko Models" },
627
+ ja: { "meta.title": "Kiokuko Models" }
628
+ }), "kiokuko models labels");
629
+ }
630
+
631
+ // dist/client.js
632
+ function LispApprovalControl(props) {
633
+ const scope = props.approvalScope;
634
+ const [state, setState] = useState(() => scope.getSnapshot());
635
+ const [busy, setBusy] = useState(false);
636
+ const [error, setError] = useState("");
637
+ useEffect(() => scope.subscribe(() => setState(scope.getSnapshot())), [scope]);
638
+ const modern = props.modern === true;
639
+ const field = modern ? "lisp" : Object.keys(state.base ?? {}).find((key) => key === "approvalMode" || key.startsWith("approvalMode-"));
640
+ const mode = modern ? state.value?.lisp?.approvalMode : field ? state.value?.[field] : void 0;
641
+ const update = async (mode2) => {
642
+ if (busy || !field)
643
+ return;
644
+ setBusy(true);
645
+ setError("");
646
+ try {
647
+ const saved = modern ? await scope.mutate([{ op: "set", path: ["lisp", "approvalMode"], value: mode2 }]) : await scope.set(field, mode2);
648
+ if (saved === false)
649
+ throw new Error("The host did not save the approval setting. Reload the current value and try again.");
650
+ } catch (error2) {
651
+ setError(messageOf2(error2));
652
+ } finally {
653
+ setBusy(false);
654
+ }
655
+ };
656
+ return jsxs("div", { children: [
657
+ jsx("label", { children: jsxs(Fragment, { children: [
658
+ "Lisp approvals \xB7 Entire profile ",
659
+ jsxs("select", {
660
+ "aria-label": "Lisp approvals",
661
+ value: mode ?? "ask",
662
+ disabled: busy || !field || state.status !== "ready" || !state.writable,
663
+ onChange: (event) => void update(event.target.value),
664
+ children: [jsx("option", { value: "ask", children: "Ask" }), jsx("option", { value: "auto", children: "Auto-approve all" })]
665
+ })
666
+ ] }) }),
667
+ ...state.status === "ready" && state.writable ? [] : [jsx("span", { children: " Profile settings are unavailable or read-only." })],
668
+ ...error ? [jsx("span", { role: "alert", children: error })] : [],
669
+ ...busy ? [jsx("span", { role: "status", children: "Saving\u2026" })] : []
670
+ ] });
671
+ }
672
+ var SESSION_EXPORT_PATH = "/api/session.export";
673
+ var LOCALE_NAMESPACE = "kiokuko-session-log-download";
674
+ var INITIAL_DOWNLOAD_STATE = { bySession: {} };
675
+ var en = {
676
+ "header.action": "Session log",
677
+ "dialog.preparingTitle": "Exporting Session",
678
+ "dialog.preparingDescription": "Streaming this Session, its sub-Sessions, and attachments to a ZIP file.",
679
+ "dialog.successTitle": "Session export complete",
680
+ "dialog.successDescription": "The Session ZIP has been saved or handed to the browser download manager.",
681
+ "dialog.errorTitle": "Session export failed",
682
+ "dialog.close": "Close",
683
+ "dialog.commandFailed": "Could not export the Session log.",
684
+ "review.discuss": "Chat about it",
685
+ "review.copy": "Copy",
686
+ "review.copied": "Copied",
687
+ "review.footnotes": "Footnotes",
688
+ "review.title": "Diff review",
689
+ "review.description": "Review changes with repository facts and Kiokuko context."
66
690
  };
67
- const ja = {
68
- 'header.action': 'Session log',
69
- 'dialog.preparingTitle': 'Sessionをエクスポート中',
70
- 'dialog.preparingDescription': 'このSession、子Session、添付ファイルをZIPへストリーミングしています。',
71
- 'dialog.successTitle': 'Sessionのエクスポート完了',
72
- 'dialog.successDescription': 'Session ZIPを保存、またはブラウザのダウンロード処理へ渡しました。',
73
- 'dialog.errorTitle': 'Sessionのエクスポートに失敗',
74
- 'dialog.close': '閉じる',
75
- 'dialog.commandFailed': 'Session logをエクスポートできませんでした。',
76
- 'review.discuss': '相談に戻る',
77
- 'review.copy': 'コピー',
78
- 'review.copied': 'コピーしました',
79
- 'review.footnotes': '脚注',
80
- 'review.title': 'Diff レビュー',
81
- 'review.description': '差分をリポジトリの事実と Kiokuko の文脈で確認します。',
691
+ var ja = {
692
+ "header.action": "Session log",
693
+ "dialog.preparingTitle": "Session\u3092\u30A8\u30AF\u30B9\u30DD\u30FC\u30C8\u4E2D",
694
+ "dialog.preparingDescription": "\u3053\u306ESession\u3001\u5B50Session\u3001\u6DFB\u4ED8\u30D5\u30A1\u30A4\u30EB\u3092ZIP\u3078\u30B9\u30C8\u30EA\u30FC\u30DF\u30F3\u30B0\u3057\u3066\u3044\u307E\u3059\u3002",
695
+ "dialog.successTitle": "Session\u306E\u30A8\u30AF\u30B9\u30DD\u30FC\u30C8\u5B8C\u4E86",
696
+ "dialog.successDescription": "Session ZIP\u3092\u4FDD\u5B58\u3001\u307E\u305F\u306F\u30D6\u30E9\u30A6\u30B6\u306E\u30C0\u30A6\u30F3\u30ED\u30FC\u30C9\u51E6\u7406\u3078\u6E21\u3057\u307E\u3057\u305F\u3002",
697
+ "dialog.errorTitle": "Session\u306E\u30A8\u30AF\u30B9\u30DD\u30FC\u30C8\u306B\u5931\u6557",
698
+ "dialog.close": "\u9589\u3058\u308B",
699
+ "dialog.commandFailed": "Session log\u3092\u30A8\u30AF\u30B9\u30DD\u30FC\u30C8\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F\u3002",
700
+ "review.discuss": "\u76F8\u8AC7\u306B\u623B\u308B",
701
+ "review.copy": "\u30B3\u30D4\u30FC",
702
+ "review.copied": "\u30B3\u30D4\u30FC\u3057\u307E\u3057\u305F",
703
+ "review.footnotes": "\u811A\u6CE8",
704
+ "review.title": "Diff \u30EC\u30D3\u30E5\u30FC",
705
+ "review.description": "\u5DEE\u5206\u3092\u30EA\u30DD\u30B8\u30C8\u30EA\u306E\u4E8B\u5B9F\u3068 Kiokuko \u306E\u6587\u8108\u3067\u78BA\u8A8D\u3057\u307E\u3059\u3002"
82
706
  };
83
- const zh = {
84
- 'header.action': 'Session 日志',
85
- 'dialog.preparingTitle': '正在导出 Session',
86
- 'dialog.preparingDescription': '正在将当前 Session、子 Session 和附件流式写入 ZIP 文件。',
87
- 'dialog.successTitle': 'Session 导出完成',
88
- 'dialog.successDescription': 'Session ZIP 已保存或交给浏览器下载管理器。',
89
- 'dialog.errorTitle': 'Session 导出失败',
90
- 'dialog.close': '关闭',
91
- 'dialog.commandFailed': '无法导出 Session 日志。',
92
- 'review.discuss': '继续讨论',
93
- 'review.copy': '复制',
94
- 'review.copied': '已复制',
95
- 'review.footnotes': '脚注',
96
- 'review.title': 'Diff 审查',
97
- 'review.description': '结合仓库事实和 Kiokuko 上下文查看更改。',
707
+ var zh = {
708
+ "header.action": "Session \u65E5\u5FD7",
709
+ "dialog.preparingTitle": "\u6B63\u5728\u5BFC\u51FA Session",
710
+ "dialog.preparingDescription": "\u6B63\u5728\u5C06\u5F53\u524D Session\u3001\u5B50 Session \u548C\u9644\u4EF6\u6D41\u5F0F\u5199\u5165 ZIP \u6587\u4EF6\u3002",
711
+ "dialog.successTitle": "Session \u5BFC\u51FA\u5B8C\u6210",
712
+ "dialog.successDescription": "Session ZIP \u5DF2\u4FDD\u5B58\u6216\u4EA4\u7ED9\u6D4F\u89C8\u5668\u4E0B\u8F7D\u7BA1\u7406\u5668\u3002",
713
+ "dialog.errorTitle": "Session \u5BFC\u51FA\u5931\u8D25",
714
+ "dialog.close": "\u5173\u95ED",
715
+ "dialog.commandFailed": "\u65E0\u6CD5\u5BFC\u51FA Session \u65E5\u5FD7\u3002",
716
+ "review.discuss": "\u7EE7\u7EED\u8BA8\u8BBA",
717
+ "review.copy": "\u590D\u5236",
718
+ "review.copied": "\u5DF2\u590D\u5236",
719
+ "review.footnotes": "\u811A\u6CE8",
720
+ "review.title": "Diff \u5BA1\u67E5",
721
+ "review.description": "\u7ED3\u5408\u4ED3\u5E93\u4E8B\u5B9E\u548C Kiokuko \u4E0A\u4E0B\u6587\u67E5\u770B\u66F4\u6539\u3002"
98
722
  };
99
723
  function hostBase() {
100
- const origin = globalThis.location?.origin;
101
- return origin !== undefined && origin !== 'null' ? origin : 'http://dsh.internal';
724
+ const origin = globalThis.location?.origin;
725
+ return origin !== void 0 && origin !== "null" ? origin : "http://dsh.internal";
102
726
  }
103
- function messageOf(error) {
104
- return error instanceof Error ? error.message : String(error);
727
+ function messageOf2(error) {
728
+ return error instanceof Error ? error.message : String(error);
105
729
  }
106
730
  function isAbortError(error) {
107
- return typeof DOMException !== 'undefined' && error instanceof DOMException && error.name === 'AbortError';
731
+ return typeof DOMException !== "undefined" && error instanceof DOMException && error.name === "AbortError";
108
732
  }
109
733
  function sessionLogZipFilename(sessionId) {
110
- return `dsh-session-${String(sessionId).replace(/[^A-Za-z0-9_-]/gu, '_')}.zip`;
734
+ return `dsh-session-${String(sessionId).replace(/[^A-Za-z0-9_-]/gu, "_")}.zip`;
111
735
  }
112
736
  async function responseFailure(response) {
113
- const detail = await response.text().catch(() => '');
114
- return new Error(`Session export failed: HTTP ${response.status}${detail === '' ? '' : ` ${detail}`}`);
737
+ const detail = await response.text().catch(() => "");
738
+ return new Error(`Session export failed: HTTP ${response.status}${detail === "" ? "" : ` ${detail}`}`);
115
739
  }
116
- /** Browser helper that never builds a whole-log Blob in application memory. */
117
740
  async function downloadDshSessionLog(options) {
118
- const url = new URL(options.endpoint);
119
- url.searchParams.set('sessionId', options.sessionId);
120
- url.searchParams.set('includeDescendants', 'true');
121
- const browser = options.window ?? globalThis.window;
122
- if (browser?.showSaveFilePicker === undefined) {
123
- if (browser === undefined)
124
- throw new Error('browser download surface is unavailable');
125
- url.searchParams.set('download', '1');
126
- browser.location.assign(url.toString());
127
- return 'navigated';
128
- }
129
- const handle = await browser.showSaveFilePicker({
130
- suggestedName: sessionLogZipFilename(options.sessionId),
131
- types: [{ description: 'ZIP archive', accept: { 'application/zip': ['.zip'] } }],
132
- });
133
- const response = await (options.fetch ?? globalThis.fetch)(url, options.signal === undefined ? {} : { signal: options.signal });
134
- if (!response.ok)
135
- throw await responseFailure(response);
136
- if (response.body === null)
137
- throw new Error('Session export returned no response stream');
138
- const writable = await handle.createWritable();
139
- await response.body.pipeTo(writable);
140
- return 'streamed';
741
+ const url = new URL(options.endpoint);
742
+ url.searchParams.set("sessionId", options.sessionId);
743
+ url.searchParams.set("includeDescendants", "true");
744
+ const browser = options.window ?? globalThis.window;
745
+ if (browser?.showSaveFilePicker === void 0) {
746
+ if (browser === void 0)
747
+ throw new Error("browser download surface is unavailable");
748
+ url.searchParams.set("download", "1");
749
+ browser.location.assign(url.toString());
750
+ return "navigated";
751
+ }
752
+ const handle = await browser.showSaveFilePicker({
753
+ suggestedName: sessionLogZipFilename(options.sessionId),
754
+ types: [{ description: "ZIP archive", accept: { "application/zip": [".zip"] } }]
755
+ });
756
+ const response = await (options.fetch ?? globalThis.fetch)(url, options.signal === void 0 ? {} : { signal: options.signal });
757
+ if (!response.ok)
758
+ throw await responseFailure(response);
759
+ if (response.body === null)
760
+ throw new Error("Session export returned no response stream");
761
+ const writable = await handle.createWritable();
762
+ await response.body.pipeTo(writable);
763
+ return "streamed";
141
764
  }
142
- class SessionLogDownloadController {
143
- store = createSnapshotStore({ ...INITIAL_DOWNLOAD_STATE, bySession: {} });
144
- active = new Map();
145
- disposed = false;
146
- download(sessionId) {
147
- const existing = this.active.get(sessionId);
148
- if (existing !== undefined)
149
- return existing.done;
150
- if (this.disposed)
151
- return Promise.resolve();
152
- const abort = new AbortController();
153
- const done = this.run(sessionId, abort.signal).finally(() => this.active.delete(sessionId));
154
- this.active.set(sessionId, { abort, done });
155
- return done;
156
- }
157
- dismiss(sessionId) {
158
- const current = this.store.getSnapshot().bySession[sessionId];
159
- if (current === undefined || !current.open)
160
- return;
161
- this.publish(sessionId, { ...current, open: false });
162
- }
163
- async dispose() {
164
- this.disposed = true;
165
- const active = [...this.active.values()];
166
- for (const operation of active)
167
- operation.abort.abort();
168
- await Promise.allSettled(active.map(operation => operation.done));
169
- }
170
- async run(sessionId, signal) {
171
- this.publish(sessionId, { open: true, status: 'downloading', error: null });
172
- try {
173
- const endpoint = new URL(SESSION_EXPORT_PATH, hostBase()).toString();
174
- const browser = globalThis.window;
175
- if (browser?.showSaveFilePicker === undefined) {
176
- const probe = await fetch(`${endpoint}?sessionId=${encodeURIComponent(sessionId)}&includeDescendants=true`, {
177
- method: 'HEAD',
178
- signal,
179
- });
180
- if (!probe.ok)
181
- throw await responseFailure(probe);
182
- }
183
- await downloadDshSessionLog({
184
- endpoint,
185
- sessionId,
186
- signal,
187
- ...(browser === undefined ? {} : { window: browser }),
188
- });
189
- const open = this.store.getSnapshot().bySession[sessionId]?.open ?? true;
190
- this.publish(sessionId, { open, status: 'success', error: null });
191
- }
192
- catch (error) {
193
- if (signal.aborted || isAbortError(error))
194
- return;
195
- const open = this.store.getSnapshot().bySession[sessionId]?.open ?? true;
196
- this.publish(sessionId, { open, status: 'error', error: messageOf(error) });
197
- }
198
- }
199
- publish(sessionId, entry) {
200
- this.store.update(state => {
201
- state.bySession = { ...state.bySession, [sessionId]: entry };
765
+ var SessionLogDownloadController = class {
766
+ store = createSnapshotStore({ ...INITIAL_DOWNLOAD_STATE, bySession: {} });
767
+ active = /* @__PURE__ */ new Map();
768
+ disposed = false;
769
+ download(sessionId) {
770
+ const existing = this.active.get(sessionId);
771
+ if (existing !== void 0)
772
+ return existing.done;
773
+ if (this.disposed)
774
+ return Promise.resolve();
775
+ const abort = new AbortController();
776
+ const done = this.run(sessionId, abort.signal).finally(() => this.active.delete(sessionId));
777
+ this.active.set(sessionId, { abort, done });
778
+ return done;
779
+ }
780
+ dismiss(sessionId) {
781
+ const current = this.store.getSnapshot().bySession[sessionId];
782
+ if (current === void 0 || !current.open)
783
+ return;
784
+ this.publish(sessionId, { ...current, open: false });
785
+ }
786
+ async dispose() {
787
+ this.disposed = true;
788
+ const active = [...this.active.values()];
789
+ for (const operation of active)
790
+ operation.abort.abort();
791
+ await Promise.allSettled(active.map((operation) => operation.done));
792
+ }
793
+ async run(sessionId, signal) {
794
+ this.publish(sessionId, { open: true, status: "downloading", error: null });
795
+ try {
796
+ const endpoint = new URL(SESSION_EXPORT_PATH, hostBase()).toString();
797
+ const browser = globalThis.window;
798
+ if (browser?.showSaveFilePicker === void 0) {
799
+ const probe = await fetch(`${endpoint}?sessionId=${encodeURIComponent(sessionId)}&includeDescendants=true`, {
800
+ method: "HEAD",
801
+ signal
202
802
  });
803
+ if (!probe.ok)
804
+ throw await responseFailure(probe);
805
+ }
806
+ await downloadDshSessionLog({
807
+ endpoint,
808
+ sessionId,
809
+ signal,
810
+ ...browser === void 0 ? {} : { window: browser }
811
+ });
812
+ const open = this.store.getSnapshot().bySession[sessionId]?.open ?? true;
813
+ this.publish(sessionId, { open, status: "success", error: null });
814
+ } catch (error) {
815
+ if (signal.aborted || isAbortError(error))
816
+ return;
817
+ const open = this.store.getSnapshot().bySession[sessionId]?.open ?? true;
818
+ this.publish(sessionId, { open, status: "error", error: messageOf2(error) });
203
819
  }
204
- }
205
- function SessionLogDownloadDialog(props) {
206
- const sessionId = String(props.sessionId);
207
- const useSessionLogDownload = props.useSessionLogDownload;
208
- const dismiss = props.dismiss;
209
- const t = props.t;
210
- const entry = useSessionLogDownload(state => state.bySession[sessionId]);
211
- const status = entry?.status;
212
- const error = status === 'error' ? entry?.error || t('dialog.commandFailed') : null;
213
- return jsx(Modal, {
214
- open: entry?.open === true,
215
- onClose: () => dismiss(sessionId),
216
- title: status === 'downloading'
217
- ? t('dialog.preparingTitle')
218
- : status === 'success' ? t('dialog.successTitle') : t('dialog.errorTitle'),
219
- description: status === 'downloading'
220
- ? t('dialog.preparingDescription')
221
- : status === 'success' ? t('dialog.successDescription') : error ?? t('dialog.commandFailed'),
222
- closeLabel: t('dialog.close'),
223
- footer: jsx(Button, { variant: 'primary', onClick: () => dismiss(sessionId), children: t('dialog.close') }),
820
+ }
821
+ publish(sessionId, entry) {
822
+ this.store.update((state) => {
823
+ state.bySession = { ...state.bySession, [sessionId]: entry };
224
824
  });
825
+ }
826
+ };
827
+ function SessionLogDownloadDialog(props) {
828
+ const sessionId = String(props.sessionId);
829
+ const useSessionLogDownload = props.useSessionLogDownload;
830
+ const dismiss = props.dismiss;
831
+ const t = props.t;
832
+ const entry = useSessionLogDownload((state) => state.bySession[sessionId]);
833
+ const status = entry?.status;
834
+ const error = status === "error" ? entry?.error || t("dialog.commandFailed") : null;
835
+ return jsx(Modal, {
836
+ open: entry?.open === true,
837
+ onClose: () => dismiss(sessionId),
838
+ title: status === "downloading" ? t("dialog.preparingTitle") : status === "success" ? t("dialog.successTitle") : t("dialog.errorTitle"),
839
+ description: status === "downloading" ? t("dialog.preparingDescription") : status === "success" ? t("dialog.successDescription") : error ?? t("dialog.commandFailed"),
840
+ closeLabel: t("dialog.close"),
841
+ footer: jsx(Button, { variant: "primary", onClick: () => dismiss(sessionId), children: t("dialog.close") })
842
+ });
225
843
  }
226
844
  function SessionLogDownloadHeaderAction(props) {
227
- const sessionId = String(props.sessionId);
228
- const useSessionLogDownload = props.useSessionLogDownload;
229
- const request = props.request;
230
- const t = props.t;
231
- const busy = useSessionLogDownload(state => state.bySession[sessionId])?.status === 'downloading';
232
- return jsxs(Fragment, {
233
- children: [
234
- jsxs('button', {
235
- type: 'button',
236
- className: 'kiokuko-session-log-button',
237
- disabled: busy,
238
- 'aria-busy': busy,
239
- onClick: () => request(sessionId),
240
- children: [jsx('span', { children: t('header.action') }), jsx(IconDownloadOutline16, { size: 12 })],
241
- }),
242
- jsx(SessionLogDownloadDialog, props),
243
- ],
244
- });
845
+ const sessionId = String(props.sessionId);
846
+ const useSessionLogDownload = props.useSessionLogDownload;
847
+ const request = props.request;
848
+ const t = props.t;
849
+ const busy = useSessionLogDownload((state) => state.bySession[sessionId])?.status === "downloading";
850
+ return jsxs(Fragment, {
851
+ children: [
852
+ jsxs("button", {
853
+ type: "button",
854
+ className: "kiokuko-session-log-button",
855
+ disabled: busy,
856
+ "aria-busy": busy,
857
+ onClick: () => request(sessionId),
858
+ children: [jsx("span", { children: t("header.action") }), jsx(IconDownloadOutline16, { size: 12 })]
859
+ }),
860
+ jsx(SessionLogDownloadDialog, props)
861
+ ]
862
+ });
245
863
  }
246
864
  function installStyle() {
247
- const styleId = 'kiokuko-session-log-download-style';
248
- const existing = document.querySelector(`style[data-plugin-css="${styleId}"]`);
249
- if (existing !== null)
250
- return () => undefined;
251
- const style = document.createElement('style');
252
- style.dataset.plugin = 'kiokuko-dsh';
253
- style.dataset.pluginCss = styleId;
254
- style.textContent = '.kiokuko-session-log-button{border:.5px solid var(--dsw-alias-border-l4);min-width:111px;height:32px;color:var(--dsw-alias-label-primary);font-family:var(--dsw-font-family);cursor:pointer;background:transparent;border-radius:18px;justify-content:center;align-items:center;gap:4px;padding:6px 12px;font-size:13px;font-weight:400;line-height:20px;display:inline-flex}.kiokuko-session-log-button:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}.kiokuko-session-log-button:disabled{color:var(--dsw-alias-label-dimmed);cursor:wait}.kiokuko-session-log-button span,.kiokuko-session-log-button svg{flex:none}.kiokuko-session-log-button span{white-space:nowrap}';
255
- document.head.appendChild(style);
256
- return () => style.remove();
865
+ const styleId = "kiokuko-session-log-download-style";
866
+ const existing = document.querySelector(`style[data-plugin-css="${styleId}"]`);
867
+ if (existing !== null)
868
+ return () => void 0;
869
+ const style = document.createElement("style");
870
+ style.dataset.plugin = "kiokuko-dsh";
871
+ style.dataset.pluginCss = styleId;
872
+ style.textContent = ".kiokuko-session-log-button{border:.5px solid var(--dsw-alias-border-l4);min-width:111px;height:32px;color:var(--dsw-alias-label-primary);font-family:var(--dsw-font-family);cursor:pointer;background:transparent;border-radius:18px;justify-content:center;align-items:center;gap:4px;padding:6px 12px;font-size:13px;font-weight:400;line-height:20px;display:inline-flex}.kiokuko-session-log-button:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}.kiokuko-session-log-button:disabled{color:var(--dsw-alias-label-dimmed);cursor:wait}.kiokuko-session-log-button span,.kiokuko-session-log-button svg{flex:none}.kiokuko-session-log-button span{white-space:nowrap}";
873
+ document.head.appendChild(style);
874
+ return () => style.remove();
257
875
  }
258
- const inject = ['slots', 'locale', 'uiConversation'];
259
- /** A plugin-owned result in chat, never forged as a model assistant message. */
260
- const completionReportDefinition = {
261
- kind: 'kiokuko-completion-report', target: 'chat',
262
- match: (event) => ['kiokuko/completion-report', 'kiokuko/execution-status', 'kiokuko/deep-report', 'kiokuko/deep-status'].includes(event.type) && typeof event.data.reportId === 'string' && typeof event.data.text === 'string'
263
- ? { id: event.data.reportId, role: 'start' } : null,
264
- start: (_context, match) => ({ seq: match.event.seq, text: match.event.data.text, status: match.event.type.endsWith('status') }),
265
- update: (context) => context.state,
266
- buildViewNode: (context) => context.state === undefined ? null : {
267
- key: context.key, id: context.id, kind: 'kiokuko-completion-report', target: 'chat',
268
- anchorSeq: context.state.seq, location: context.start?.location ?? { kind: 'session' },
269
- visibility: 'visible', data: { text: context.state.text, status: context.state.status },
270
- },
876
+ var inject = ["slots", "locale", "uiConversation"];
877
+ var completionReportDefinition = {
878
+ kind: "kiokuko-completion-report",
879
+ target: "chat",
880
+ match: (event) => ["kiokuko/completion-report", "kiokuko/execution-status", "kiokuko/deep-report", "kiokuko/deep-status"].includes(event.type) && typeof event.data.reportId === "string" && typeof event.data.text === "string" ? { id: event.data.reportId, role: "start" } : null,
881
+ start: (_context, match) => ({ seq: match.event.seq, text: match.event.data.text, status: match.event.type.endsWith("status") }),
882
+ update: (context) => context.state,
883
+ buildViewNode: (context) => context.state === void 0 ? null : {
884
+ key: context.key,
885
+ id: context.id,
886
+ kind: "kiokuko-completion-report",
887
+ target: "chat",
888
+ anchorSeq: context.state.seq,
889
+ location: context.start?.location ?? { kind: "session" },
890
+ visibility: "visible",
891
+ data: { text: context.state.text, status: context.state.status }
892
+ }
271
893
  };
272
894
  function CompletionReport(props) {
273
- const node = props.node;
274
- return jsx('section', { ...(node.data.status ? { role: 'status', 'aria-live': 'polite', 'aria-atomic': true } : { 'aria-label': '保存済みの回答', tabIndex: 0 }),
275
- style: { whiteSpace: 'pre-wrap', overflowWrap: 'anywhere', maxWidth: '100%', minWidth: 0, padding: '12px 0', lineHeight: 1.65 }, children: node.data.text });
895
+ const node = props.node;
896
+ return jsx("section", {
897
+ ...node.data.status ? { role: "status", "aria-live": "polite", "aria-atomic": true } : { "aria-label": "\u4FDD\u5B58\u6E08\u307F\u306E\u56DE\u7B54", tabIndex: 0 },
898
+ style: { whiteSpace: "pre-wrap", overflowWrap: "anywhere", maxWidth: "100%", minWidth: 0, padding: "12px 0", lineHeight: 1.65 },
899
+ children: node.data.text
900
+ });
276
901
  }
277
- /** Recovery stays available even when the model loop is stopped. */
278
902
  function LispSessionStatus(props) {
279
- const sessionId = String(props.sessionId);
280
- const [status, setStatus] = useState(null);
281
- const [open, setOpen] = useState(false);
282
- const [error, setError] = useState('');
283
- const [busy, setBusy] = useState(false);
284
- const [details, setDetails] = useState('');
285
- const dialog = useRef(null);
286
- const trigger = useRef(null);
287
- const current = useRef(sessionId);
288
- current.current = sessionId;
289
- const request = async (action) => {
290
- const url = new URL('/api/kiokuko.lisp', hostBase());
291
- url.searchParams.set('sessionId', sessionId);
292
- const response = await fetch(url, action ? { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ action }) } : {});
293
- if (response.status === 404 && !action)
294
- return null;
295
- const value = await response.json();
296
- if (!response.ok)
297
- throw new Error(value.message ?? 'Lisp の状態を取得できません。接続を確認してください。');
298
- return value;
903
+ const sessionId = String(props.sessionId);
904
+ const [status, setStatus] = useState(null);
905
+ const [open, setOpen] = useState(false);
906
+ const [error, setError] = useState("");
907
+ const [busy, setBusy] = useState(false);
908
+ const [details, setDetails] = useState("");
909
+ const dialog = useRef(null);
910
+ const trigger = useRef(null);
911
+ const current = useRef(sessionId);
912
+ current.current = sessionId;
913
+ const request = async (action) => {
914
+ const url = new URL("/api/kiokuko.lisp", hostBase());
915
+ url.searchParams.set("sessionId", sessionId);
916
+ const response = await fetch(url, action ? { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ action }) } : {});
917
+ if (response.status === 404 && !action)
918
+ return null;
919
+ const value = await response.json();
920
+ if (!response.ok)
921
+ throw new Error(value.message ?? "Lisp \u306E\u72B6\u614B\u3092\u53D6\u5F97\u3067\u304D\u307E\u305B\u3093\u3002\u63A5\u7D9A\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002");
922
+ return value;
923
+ };
924
+ useEffect(() => {
925
+ if (open && dialog.current && !dialog.current.open)
926
+ dialog.current.showModal();
927
+ if (!open && dialog.current?.open) {
928
+ dialog.current.close();
929
+ trigger.current?.focus();
930
+ }
931
+ }, [open]);
932
+ useEffect(() => {
933
+ let disposed = false, timer, lastState = "";
934
+ setStatus(null);
935
+ setError("");
936
+ setOpen(false);
937
+ setDetails("");
938
+ setBusy(false);
939
+ const refresh = async () => {
940
+ try {
941
+ const value = await request();
942
+ if (disposed)
943
+ return;
944
+ if (value) {
945
+ setStatus(value);
946
+ setError("");
947
+ if (value.enabled && ["RECOVERY_REQUIRED", "STOP_UNCONFIRMED"].includes(value.state) && value.state !== lastState)
948
+ setOpen(true);
949
+ lastState = value.state;
950
+ } else if (lastState)
951
+ setError("Lisp \u306E\u63A5\u7D9A\u304C\u5931\u308F\u308C\u307E\u3057\u305F\u3002\u4FDD\u8B77\u306F\u89E3\u9664\u3055\u308C\u3066\u3044\u307E\u305B\u3093\u3002\u30D7\u30E9\u30B0\u30A4\u30F3\u3092\u623B\u3057\u3066 /kioku-lisp status \u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002");
952
+ } catch (failure) {
953
+ if (!disposed && lastState)
954
+ setError(messageOf2(failure));
955
+ } finally {
956
+ if (!disposed)
957
+ timer = setTimeout(() => void refresh(), document.hidden ? 1e4 : 2e3);
958
+ }
299
959
  };
300
- useEffect(() => {
301
- if (open && dialog.current && !dialog.current.open)
302
- dialog.current.showModal();
303
- if (!open && dialog.current?.open) {
304
- dialog.current.close();
305
- trigger.current?.focus();
306
- }
307
- }, [open]);
308
- useEffect(() => {
309
- let disposed = false, timer, lastState = '';
310
- setStatus(null);
311
- setError('');
312
- setOpen(false);
313
- setDetails('');
314
- setBusy(false);
315
- const refresh = async () => {
316
- try {
317
- const value = await request();
318
- if (disposed)
319
- return;
320
- if (value) {
321
- setStatus(value);
322
- setError('');
323
- if (value.enabled && ['RECOVERY_REQUIRED', 'STOP_UNCONFIRMED'].includes(value.state) && value.state !== lastState)
324
- setOpen(true);
325
- lastState = value.state;
326
- }
327
- else if (lastState)
328
- setError('Lisp の接続が失われました。保護は解除されていません。プラグインを戻して /kioku-lisp status を確認してください。');
329
- }
330
- catch (failure) {
331
- if (!disposed && lastState)
332
- setError(messageOf(failure));
333
- }
334
- finally {
335
- if (!disposed)
336
- timer = setTimeout(() => void refresh(), document.hidden ? 10000 : 2000);
337
- }
338
- };
339
- void refresh();
340
- return () => { disposed = true; clearTimeout(timer); };
341
- }, [sessionId]);
342
- const act = async (action) => {
343
- if (busy)
344
- return;
345
- setBusy(true);
346
- setError('');
347
- try {
348
- const result = await request(action);
349
- if (current.current !== sessionId)
350
- return;
351
- if (result?.code)
352
- setDetails(JSON.stringify(result, null, 2));
353
- else {
354
- setDetails('');
355
- if (result?.state)
356
- setStatus({ ...status, ...result, enabled: result.enabled ?? status?.enabled ?? true });
357
- }
358
- }
359
- catch (failure) {
360
- if (current.current === sessionId)
361
- setError(messageOf(failure));
362
- }
363
- finally {
364
- if (current.current === sessionId)
365
- setBusy(false);
366
- }
960
+ void refresh();
961
+ return () => {
962
+ disposed = true;
963
+ clearTimeout(timer);
367
964
  };
368
- if (!status?.enabled && !error)
369
- return null;
370
- const label = { READY: '実行可能', SUSPENDED: '休止中(次回自動起動)', EVALUATING: '処理中', PREFLIGHT: '起動中', STOPPING: '停止中', RECOVERY_REQUIRED: '確認が必要', STOP_UNCONFIRMED: '停止未確認' }[status?.state ?? ''] ?? '状態不明';
371
- return jsxs(Fragment, { children: [jsx('button', { type: 'button', ref: trigger, onClick: () => setOpen(true), children: `Lisp: ${label}` }),
372
- jsx('dialog', { ref: dialog, onCancel: () => setOpen(false), 'aria-label': 'Lisp の状態と復旧', style: { maxWidth: 'min(720px, 90vw)', maxHeight: '85vh' },
373
- children: jsxs('section', { children: [jsx('h2', { children: `Lisp: ${label}` }),
374
- jsx('p', { role: 'status', 'aria-live': 'polite', children: busy ? '処理しています。初回の起動には時間がかかる場合があります。' : status?.error?.message ?? '現在のセッションの状態です。' }),
375
- jsx('p', { children: status?.error?.recovery ?? status?.recovery ?? '' }),
376
- ...(error ? [jsx('p', { role: 'alert', children: error })] : []),
377
- ...(details ? [jsx('pre', { style: { whiteSpace: 'pre-wrap', overflowWrap: 'anywhere' }, children: details })] : []),
378
- jsx('p', { children: '未確定の変更は自動で再実行・復元しません。詳細は /kioku-lisp diagnostics でも確認できます。' }),
379
- jsx(NumberedPicker, { label: 'Lisp の操作', disabled: busy,
380
- options: [{ value: 'cancel', label: '停止する' }, { value: 'recover', label: '照合して新しい Lisp を起動', disabled: ['STOP_UNCONFIRMED', 'SUSPENDED'].includes(status?.state ?? '') }, { value: 'close', label: '閉じる' }],
381
- onChoose: (value) => value === 'close' ? setOpen(false) : void act(value) }),
382
- ] }) })] });
965
+ }, [sessionId]);
966
+ const act = async (action) => {
967
+ if (busy)
968
+ return;
969
+ setBusy(true);
970
+ setError("");
971
+ try {
972
+ const result = await request(action);
973
+ if (current.current !== sessionId)
974
+ return;
975
+ if (result?.code)
976
+ setDetails(JSON.stringify(result, null, 2));
977
+ else {
978
+ setDetails("");
979
+ if (result?.state)
980
+ setStatus({ ...status, ...result, enabled: result.enabled ?? status?.enabled ?? true });
981
+ }
982
+ } catch (failure) {
983
+ if (current.current === sessionId)
984
+ setError(messageOf2(failure));
985
+ } finally {
986
+ if (current.current === sessionId)
987
+ setBusy(false);
988
+ }
989
+ };
990
+ if (!status?.enabled && !error)
991
+ return null;
992
+ const label = { READY: "\u5B9F\u884C\u53EF\u80FD", SUSPENDED: "\u4F11\u6B62\u4E2D\uFF08\u6B21\u56DE\u81EA\u52D5\u8D77\u52D5\uFF09", EVALUATING: "\u51E6\u7406\u4E2D", PREFLIGHT: "\u8D77\u52D5\u4E2D", STOPPING: "\u505C\u6B62\u4E2D", RECOVERY_REQUIRED: "\u78BA\u8A8D\u304C\u5FC5\u8981", STOP_UNCONFIRMED: "\u505C\u6B62\u672A\u78BA\u8A8D" }[status?.state ?? ""] ?? "\u72B6\u614B\u4E0D\u660E";
993
+ return jsxs(Fragment, { children: [
994
+ jsx("button", { type: "button", ref: trigger, onClick: () => setOpen(true), children: `Lisp: ${label}` }),
995
+ jsx("dialog", {
996
+ ref: dialog,
997
+ onCancel: () => setOpen(false),
998
+ "aria-label": "Lisp \u306E\u72B6\u614B\u3068\u5FA9\u65E7",
999
+ style: { maxWidth: "min(720px, 90vw)", maxHeight: "85vh" },
1000
+ children: jsxs("section", { children: [
1001
+ jsx("h2", { children: `Lisp: ${label}` }),
1002
+ jsx("p", { role: "status", "aria-live": "polite", children: busy ? "\u51E6\u7406\u3057\u3066\u3044\u307E\u3059\u3002\u521D\u56DE\u306E\u8D77\u52D5\u306B\u306F\u6642\u9593\u304C\u304B\u304B\u308B\u5834\u5408\u304C\u3042\u308A\u307E\u3059\u3002" : status?.error?.message ?? "\u73FE\u5728\u306E\u30BB\u30C3\u30B7\u30E7\u30F3\u306E\u72B6\u614B\u3067\u3059\u3002" }),
1003
+ jsx("p", { children: status?.error?.recovery ?? status?.recovery ?? "" }),
1004
+ ...error ? [jsx("p", { role: "alert", children: error })] : [],
1005
+ ...details ? [jsx("pre", { style: { whiteSpace: "pre-wrap", overflowWrap: "anywhere" }, children: details })] : [],
1006
+ jsx("p", { children: "\u672A\u78BA\u5B9A\u306E\u5909\u66F4\u306F\u81EA\u52D5\u3067\u518D\u5B9F\u884C\u30FB\u5FA9\u5143\u3057\u307E\u305B\u3093\u3002\u8A73\u7D30\u306F /kioku-lisp diagnostics \u3067\u3082\u78BA\u8A8D\u3067\u304D\u307E\u3059\u3002" }),
1007
+ jsx(NumberedPicker, {
1008
+ label: "Lisp \u306E\u64CD\u4F5C",
1009
+ disabled: busy,
1010
+ options: [{ value: "cancel", label: "\u505C\u6B62\u3059\u308B" }, { value: "recover", label: "\u7167\u5408\u3057\u3066\u65B0\u3057\u3044 Lisp \u3092\u8D77\u52D5", disabled: ["STOP_UNCONFIRMED", "SUSPENDED"].includes(status?.state ?? "") }, { value: "close", label: "\u9589\u3058\u308B" }],
1011
+ onChoose: (value) => value === "close" ? setOpen(false) : void act(value)
1012
+ })
1013
+ ] })
1014
+ })
1015
+ ] });
383
1016
  }
384
- /** Read-only, Session-bound display with explicit delivery acknowledgement after render. */
385
1017
  function DeepSessionReports(props) {
386
- const sessionId = String(props.sessionId);
387
- const notices = props.notices === true;
388
- const label = notices ? 'Kiokuko' : 'Deep';
389
- const endpoint = notices ? '/api/kiokuko.notices' : '/api/kiokuko.deep';
390
- const [items, setItems] = useState([]);
391
- const [open, setOpen] = useState(false);
392
- const [error, setError] = useState('');
393
- const seen = useRef(new Set());
394
- const dialog = useRef(null);
395
- const trigger = useRef(null);
396
- useEffect(() => {
397
- if (open && dialog.current && !dialog.current.open)
398
- dialog.current.showModal();
399
- else if (!open && dialog.current?.open) {
400
- dialog.current.close();
401
- trigger.current?.focus();
1018
+ const sessionId = String(props.sessionId);
1019
+ const notices = props.notices === true;
1020
+ const label = notices ? "Kiokuko" : "Deep";
1021
+ const endpoint = notices ? "/api/kiokuko.notices" : "/api/kiokuko.deep";
1022
+ const [items, setItems] = useState([]);
1023
+ const [open, setOpen] = useState(false);
1024
+ const [error, setError] = useState("");
1025
+ const seen = useRef(/* @__PURE__ */ new Set());
1026
+ const dialog = useRef(null);
1027
+ const trigger = useRef(null);
1028
+ useEffect(() => {
1029
+ if (open && dialog.current && !dialog.current.open)
1030
+ dialog.current.showModal();
1031
+ else if (!open && dialog.current?.open) {
1032
+ dialog.current.close();
1033
+ trigger.current?.focus();
1034
+ }
1035
+ }, [open]);
1036
+ useEffect(() => {
1037
+ const controller = new AbortController();
1038
+ let timer;
1039
+ let etag = "";
1040
+ setItems([]);
1041
+ setOpen(false);
1042
+ seen.current = /* @__PURE__ */ new Set();
1043
+ const refresh = async () => {
1044
+ try {
1045
+ const url = new URL(endpoint, hostBase());
1046
+ url.searchParams.set("sessionId", sessionId);
1047
+ const response = await fetch(url, { signal: controller.signal, headers: etag ? { "if-none-match": etag } : {} });
1048
+ if (response.status === 304) {
1049
+ setError("");
1050
+ return;
402
1051
  }
403
- }, [open]);
404
- useEffect(() => {
405
- const controller = new AbortController();
406
- let timer;
407
- let etag = '';
408
- setItems([]);
1052
+ if (!response.ok)
1053
+ throw new Error(`${label}\u306E\u72B6\u614B\u3092\u518D\u78BA\u8A8D\u3067\u304D\u307E\u305B\u3093\u3002\u63A5\u7D9A\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002`);
1054
+ etag = response.headers.get("etag") ?? "";
1055
+ const data = await response.json();
1056
+ if (!Array.isArray(data.items) || data.items.some((item) => typeof item.id !== "string" || typeof item.text !== "string"))
1057
+ throw new Error(`${label}\u306E\u5FDC\u7B54\u3092\u8AAD\u307F\u53D6\u308C\u307E\u305B\u3093\u3002`);
1058
+ if (controller.signal.aborted)
1059
+ return;
1060
+ setItems(data.items);
1061
+ setError("");
1062
+ for (const item of data.items)
1063
+ if ((item.kind === "report" || notices) && !item.delivered && !seen.current.has(item.id)) {
1064
+ seen.current.add(item.id);
1065
+ setOpen(true);
1066
+ }
1067
+ } catch (failure) {
1068
+ if (!controller.signal.aborted)
1069
+ setError(messageOf2(failure));
1070
+ } finally {
1071
+ if (!controller.signal.aborted)
1072
+ timer = setTimeout(() => void refresh(), document.hidden ? 1e4 : 2e3);
1073
+ }
1074
+ };
1075
+ void refresh();
1076
+ return () => {
1077
+ controller.abort();
1078
+ clearTimeout(timer);
1079
+ };
1080
+ }, [sessionId, endpoint, label, notices]);
1081
+ useEffect(() => {
1082
+ const unread = items.filter((item) => !item.delivered && (open || !notices && item.kind === "status"));
1083
+ if (!unread.length)
1084
+ return;
1085
+ const controller = new AbortController();
1086
+ const url = new URL(endpoint, hostBase());
1087
+ url.searchParams.set("sessionId", sessionId);
1088
+ for (const item of unread)
1089
+ url.searchParams.append("id", item.id);
1090
+ let timer;
1091
+ const acknowledge = async () => {
1092
+ try {
1093
+ const response = await fetch(url, { method: "POST", signal: controller.signal });
1094
+ if (!response.ok)
1095
+ throw new Error(`${label}\u306E\u53D7\u9818\u78BA\u8A8D\u3092\u518D\u8A66\u884C\u3057\u3066\u3044\u307E\u3059\u3002\u56DE\u7B54\u306F\u4FDD\u6301\u3055\u308C\u3066\u3044\u307E\u3059\u3002`);
1096
+ } catch (failure) {
1097
+ if (!controller.signal.aborted) {
1098
+ setError(messageOf2(failure));
1099
+ timer = setTimeout(() => void acknowledge(), 5e3);
1100
+ }
1101
+ }
1102
+ };
1103
+ void acknowledge();
1104
+ return () => {
1105
+ controller.abort();
1106
+ clearTimeout(timer);
1107
+ };
1108
+ }, [sessionId, items, open, endpoint, label, notices]);
1109
+ if (!items.length && !error)
1110
+ return null;
1111
+ const status = items.find((item) => item.kind === "status")?.text ?? `${label}\u306E\u4FDD\u5B58\u6E08\u307F\u56DE\u7B54`;
1112
+ return jsxs(Fragment, { children: [
1113
+ jsx("button", { ref: trigger, type: "button", className: "kiokuko-session-log-button", onClick: () => setOpen(true), "aria-label": `${label}: ${status}`, children: items.some((item) => item.kind === "report") ? `${label}\u306E\u56DE\u7B54` : `${label}\u306E\u72B6\u614B` }),
1114
+ jsx("span", { role: "status", "aria-live": "polite", style: { position: "absolute", width: 1, height: 1, overflow: "hidden", clipPath: "inset(50%)" }, children: error || status.slice(0, 200) }),
1115
+ jsxs("dialog", {
1116
+ ref: dialog,
1117
+ "aria-label": label,
1118
+ onCancel: () => setOpen(false),
1119
+ onClose: () => {
409
1120
  setOpen(false);
410
- seen.current = new Set();
411
- const refresh = async () => {
412
- try {
413
- const url = new URL(endpoint, hostBase());
414
- url.searchParams.set('sessionId', sessionId);
415
- const response = await fetch(url, { signal: controller.signal, headers: etag ? { 'if-none-match': etag } : {} });
416
- if (response.status === 304) {
417
- setError('');
418
- return;
419
- }
420
- if (!response.ok)
421
- throw new Error(`${label}の状態を再確認できません。接続を確認してください。`);
422
- etag = response.headers.get('etag') ?? '';
423
- const data = await response.json();
424
- if (!Array.isArray(data.items) || data.items.some(item => typeof item.id !== 'string' || typeof item.text !== 'string'))
425
- throw new Error(`${label}の応答を読み取れません。`);
426
- if (controller.signal.aborted)
427
- return;
428
- setItems(data.items);
429
- setError('');
430
- for (const item of data.items)
431
- if ((item.kind === 'report' || notices) && !item.delivered && !seen.current.has(item.id)) {
432
- seen.current.add(item.id);
433
- setOpen(true);
434
- }
435
- }
436
- catch (failure) {
437
- if (!controller.signal.aborted)
438
- setError(messageOf(failure));
439
- }
440
- finally {
441
- if (!controller.signal.aborted)
442
- timer = setTimeout(() => void refresh(), document.hidden ? 10_000 : 2_000);
443
- }
444
- };
445
- void refresh();
446
- return () => { controller.abort(); clearTimeout(timer); };
447
- }, [sessionId, endpoint, label, notices]);
448
- useEffect(() => {
449
- const unread = items.filter(item => !item.delivered && (open || !notices && item.kind === 'status'));
450
- if (!unread.length)
451
- return;
452
- const controller = new AbortController();
453
- const url = new URL(endpoint, hostBase());
454
- url.searchParams.set('sessionId', sessionId);
455
- for (const item of unread)
456
- url.searchParams.append('id', item.id);
457
- let timer;
458
- const acknowledge = async () => {
459
- try {
460
- const response = await fetch(url, { method: 'POST', signal: controller.signal });
461
- if (!response.ok)
462
- throw new Error(`${label}の受領確認を再試行しています。回答は保持されています。`);
463
- }
464
- catch (failure) {
465
- if (!controller.signal.aborted) {
466
- setError(messageOf(failure));
467
- timer = setTimeout(() => void acknowledge(), 5_000);
468
- }
469
- }
470
- };
471
- void acknowledge();
472
- return () => { controller.abort(); clearTimeout(timer); };
473
- }, [sessionId, items, open, endpoint, label, notices]);
474
- if (!items.length && !error)
475
- return null;
476
- const status = items.find(item => item.kind === 'status')?.text ?? `${label}の保存済み回答`;
477
- return jsxs(Fragment, { children: [
478
- jsx('button', { ref: trigger, type: 'button', className: 'kiokuko-session-log-button', onClick: () => setOpen(true), 'aria-label': `${label}: ${status}`, children: items.some(item => item.kind === 'report') ? `${label}の回答` : `${label}の状態` }),
479
- jsx('span', { role: 'status', 'aria-live': 'polite', style: { position: 'absolute', width: 1, height: 1, overflow: 'hidden', clipPath: 'inset(50%)' }, children: error || status.slice(0, 200) }),
480
- jsxs('dialog', { ref: dialog, 'aria-label': label, onCancel: () => setOpen(false), onClose: () => { setOpen(false); trigger.current?.focus(); },
481
- style: { width: 'min(48rem, calc(100vw - 2rem))', maxHeight: 'calc(100dvh - 2rem)', boxSizing: 'border-box', margin: 'auto', padding: '20px', border: '1px solid var(--dsw-alias-border-l4, #ddd)', borderRadius: '16px', background: 'var(--dsw-alias-background-primary, Canvas)', color: 'var(--dsw-alias-label-primary, CanvasText)', overflow: 'auto' },
482
- children: [jsxs('div', { style: { display: 'flex', justifyContent: 'space-between', gap: '16px', alignItems: 'center' }, children: [jsx('h2', { style: { fontSize: '20px', margin: 0 }, children: label }), jsx('button', { type: 'button', className: 'kiokuko-session-log-button', onClick: () => setOpen(false), children: '閉じる' })] }),
483
- jsx('p', { style: { whiteSpace: 'pre-wrap', overflowWrap: 'anywhere' }, children: error || status }),
484
- ...items.filter(item => item.kind === 'report').map(item => jsx('section', { key: item.id, 'aria-label': '保存済みの回答', tabIndex: 0, style: { whiteSpace: 'pre-wrap', overflowWrap: 'anywhere', maxWidth: '100%', padding: '12px 0', lineHeight: 1.65 }, children: item.text }))] }),
485
- ] });
1121
+ trigger.current?.focus();
1122
+ },
1123
+ style: { width: "min(48rem, calc(100vw - 2rem))", maxHeight: "calc(100dvh - 2rem)", boxSizing: "border-box", margin: "auto", padding: "20px", border: "1px solid var(--dsw-alias-border-l4, #ddd)", borderRadius: "16px", background: "var(--dsw-alias-background-primary, Canvas)", color: "var(--dsw-alias-label-primary, CanvasText)", overflow: "auto" },
1124
+ children: [
1125
+ jsxs("div", { style: { display: "flex", justifyContent: "space-between", gap: "16px", alignItems: "center" }, children: [jsx("h2", { style: { fontSize: "20px", margin: 0 }, children: label }), jsx("button", { type: "button", className: "kiokuko-session-log-button", onClick: () => setOpen(false), children: "\u9589\u3058\u308B" })] }),
1126
+ jsx("p", { style: { whiteSpace: "pre-wrap", overflowWrap: "anywhere" }, children: error || status }),
1127
+ ...items.filter((item) => item.kind === "report").map((item) => jsx("section", { key: item.id, "aria-label": "\u4FDD\u5B58\u6E08\u307F\u306E\u56DE\u7B54", tabIndex: 0, style: { whiteSpace: "pre-wrap", overflowWrap: "anywhere", maxWidth: "100%", padding: "12px 0", lineHeight: 1.65 }, children: item.text }))
1128
+ ]
1129
+ })
1130
+ ] });
486
1131
  }
487
- const intakeDrafts = new WeakMap();
488
- // Named native cards may be reconstructed when reopened. Scope drafts to both
489
- // native session and request identity, with bounded retention for abandoned cards.
490
- const namedIntakeDrafts = new Map();
491
- const emptyDraft = () => ({ selected: null, checked: [], custom: '', ordinal: '' });
1132
+ var intakeDrafts = /* @__PURE__ */ new WeakMap();
1133
+ var namedIntakeDrafts = /* @__PURE__ */ new Map();
1134
+ var emptyDraft = () => ({ selected: null, checked: [], custom: "", ordinal: "" });
492
1135
  function draftKey(pending) {
493
- return pending.sessionId && pending.dismissal === 'hide' ? JSON.stringify([pending.sessionId, pending.key]) : undefined;
1136
+ return pending.sessionId && pending.dismissal === "hide" ? JSON.stringify([pending.sessionId, pending.key]) : void 0;
494
1137
  }
495
1138
  function readIntakeDraft(pending) {
496
- return intakeDrafts.get(pending) ?? namedIntakeDrafts.get(draftKey(pending) ?? '') ?? { page: 0, drafts: pending.questions.map(emptyDraft) };
1139
+ return intakeDrafts.get(pending) ?? namedIntakeDrafts.get(draftKey(pending) ?? "") ?? { page: 0, drafts: pending.questions.map(emptyDraft) };
497
1140
  }
498
1141
  function saveIntakeDraft(pending, batch) {
499
- const key = draftKey(pending);
500
- if (!batch) {
501
- intakeDrafts.delete(pending);
502
- if (key)
503
- namedIntakeDrafts.delete(key);
504
- return;
505
- }
506
- intakeDrafts.set(pending, batch);
507
- if (key) {
508
- namedIntakeDrafts.delete(key);
509
- namedIntakeDrafts.set(key, batch);
510
- if (namedIntakeDrafts.size > 100)
511
- namedIntakeDrafts.delete(namedIntakeDrafts.keys().next().value);
512
- }
1142
+ const key = draftKey(pending);
1143
+ if (!batch) {
1144
+ intakeDrafts.delete(pending);
1145
+ if (key)
1146
+ namedIntakeDrafts.delete(key);
1147
+ return;
1148
+ }
1149
+ intakeDrafts.set(pending, batch);
1150
+ if (key) {
1151
+ namedIntakeDrafts.delete(key);
1152
+ namedIntakeDrafts.set(key, batch);
1153
+ if (namedIntakeDrafts.size > 100)
1154
+ namedIntakeDrafts.delete(namedIntakeDrafts.keys().next().value);
1155
+ }
513
1156
  }
514
1157
  function pendingState(pending) {
515
- return pending.snapshot?.() ?? { state: 'open', channel: 'waterfall', closed: false };
1158
+ return pending.snapshot?.() ?? { state: "open", channel: "waterfall", closed: false };
516
1159
  }
517
1160
  function dismissQuestion(pending) {
518
- // Prefer the host's current semantics; a failed hide must never cancel instead.
519
- return pending.dismiss ? pending.dismiss() : pending.cancel();
1161
+ return pending.dismiss ? pending.dismiss() : pending.cancel();
520
1162
  }
521
1163
  function composingKey(event) {
522
- return !!(event.repeat || event.isComposing || event.keyCode === 229 || event.nativeEvent?.isComposing || event.nativeEvent?.keyCode === 229);
1164
+ return !!(event.repeat || event.isComposing || event.keyCode === 229 || event.nativeEvent?.isComposing || event.nativeEvent?.keyCode === 229);
523
1165
  }
524
1166
  function editingKey(event) {
525
- const target = event.target;
526
- return !!(target?.isContentEditable || ['INPUT', 'TEXTAREA', 'SELECT'].includes(target?.tagName ?? ''));
1167
+ const target = event.target;
1168
+ return !!(target?.isContentEditable || ["INPUT", "TEXTAREA", "SELECT"].includes(target?.tagName ?? ""));
527
1169
  }
528
1170
  function shortcutPlatform() {
529
- const mac = /Mac|iPhone|iPad|iPod/u.test(globalThis.navigator?.platform ?? '');
530
- const ua = globalThis.navigator?.userAgent ?? '';
531
- const control = !mac || /Safari\//u.test(ua) && !/(?:Chrome|Chromium|CriOS|Edg|OPR)\//u.test(ua);
532
- return { control, label: control ? 'Ctrl' : 'Cmd', aria: control ? 'Control' : 'Meta' };
1171
+ const mac = /Mac|iPhone|iPad|iPod/u.test(globalThis.navigator?.platform ?? "");
1172
+ const ua = globalThis.navigator?.userAgent ?? "";
1173
+ const control = !mac || /Safari\//u.test(ua) && !/(?:Chrome|Chromium|CriOS|Edg|OPR)\//u.test(ua);
1174
+ return { control, label: control ? "Ctrl" : "Cmd", aria: control ? "Control" : "Meta" };
533
1175
  }
534
1176
  function modifiedKey(event) {
535
- return !!(shortcutPlatform().control ? event.ctrlKey && !event.metaKey : event.metaKey && !event.ctrlKey);
1177
+ return !!(shortcutPlatform().control ? event.ctrlKey && !event.metaKey : event.metaKey && !event.ctrlKey);
536
1178
  }
537
1179
  function shortcutLabel(index, multi = false) {
538
- return index < 9 ? `${shortcutPlatform().label}+${index + 1}` : `${index + 1} → ${multi ? 'Space' : 'Enter'}`;
1180
+ return index < 9 ? `${shortcutPlatform().label}+${index + 1}` : `${index + 1} \u2192 ${multi ? "Space" : "Enter"}`;
539
1181
  }
540
1182
  function shortcutAria(index, count) {
541
- return index < 9 ? { 'aria-keyshortcuts': `${count <= 9 ? `${index + 1} ` : ''}${shortcutPlatform().aria}+${index + 1}` } : {};
1183
+ return index < 9 ? { "aria-keyshortcuts": `${count <= 9 ? `${index + 1} ` : ""}${shortcutPlatform().aria}+${index + 1}` } : {};
542
1184
  }
543
- const shortcutOwners = new WeakMap();
544
- /** Only the focused group (or the most recently focused visible group) owns a key. */
1185
+ var shortcutOwners = /* @__PURE__ */ new WeakMap();
545
1186
  function useShortcutGroup(ref, handler) {
546
- const latest = useRef(handler);
547
- latest.current = handler;
548
- useEffect(() => {
549
- const element = ref.current, doc = element?.ownerDocument;
550
- if (!element || !doc)
551
- return;
552
- const stack = shortcutOwners.get(doc) ?? [];
553
- shortcutOwners.set(doc, stack);
554
- stack.push(element);
555
- const claim = () => { const i = stack.indexOf(element); if (i >= 0)
556
- stack.splice(i, 1); stack.push(element); };
557
- const listener = (event) => {
558
- if (event.defaultPrevented || !element.isConnected || !element.getClientRects().length)
559
- return;
560
- const target = event.target;
561
- if (target?.nodeType && !element.contains(target))
562
- return;
563
- const group = target?.closest?.('[data-kiokuko-shortcuts]');
564
- if (group ? group !== element : stack.filter(item => item.isConnected && item.getClientRects().length).at(-1) !== element)
565
- return;
566
- const dialog = target?.closest?.('dialog,[role="dialog"]');
567
- if (dialog && !dialog.contains(element))
568
- return;
569
- if (group === element)
570
- claim();
571
- latest.current(event);
572
- };
573
- element.addEventListener?.('focusin', claim);
574
- element.addEventListener?.('pointerdown', claim);
575
- doc.addEventListener('keydown', listener, true);
576
- return () => {
577
- element.removeEventListener?.('focusin', claim);
578
- element.removeEventListener?.('pointerdown', claim);
579
- doc.removeEventListener('keydown', listener, true);
580
- const i = stack.indexOf(element);
581
- if (i >= 0)
582
- stack.splice(i, 1);
583
- };
584
- }, [ref]);
1187
+ const latest = useRef(handler);
1188
+ latest.current = handler;
1189
+ useEffect(() => {
1190
+ const element = ref.current, doc = element?.ownerDocument;
1191
+ if (!element || !doc)
1192
+ return;
1193
+ const stack = shortcutOwners.get(doc) ?? [];
1194
+ shortcutOwners.set(doc, stack);
1195
+ stack.push(element);
1196
+ const claim = () => {
1197
+ const i = stack.indexOf(element);
1198
+ if (i >= 0)
1199
+ stack.splice(i, 1);
1200
+ stack.push(element);
1201
+ };
1202
+ const listener = (event) => {
1203
+ if (event.defaultPrevented || !element.isConnected || !element.getClientRects().length)
1204
+ return;
1205
+ const target = event.target;
1206
+ if (target?.nodeType && !element.contains(target))
1207
+ return;
1208
+ const group = target?.closest?.("[data-kiokuko-shortcuts]");
1209
+ if (group ? group !== element : stack.filter((item) => item.isConnected && item.getClientRects().length).at(-1) !== element)
1210
+ return;
1211
+ const dialog = target?.closest?.('dialog,[role="dialog"]');
1212
+ if (dialog && !dialog.contains(element))
1213
+ return;
1214
+ if (group === element)
1215
+ claim();
1216
+ latest.current(event);
1217
+ };
1218
+ element.addEventListener?.("focusin", claim);
1219
+ element.addEventListener?.("pointerdown", claim);
1220
+ doc.addEventListener("keydown", listener, true);
1221
+ return () => {
1222
+ element.removeEventListener?.("focusin", claim);
1223
+ element.removeEventListener?.("pointerdown", claim);
1224
+ doc.removeEventListener("keydown", listener, true);
1225
+ const i = stack.indexOf(element);
1226
+ if (i >= 0)
1227
+ stack.splice(i, 1);
1228
+ };
1229
+ }, [ref]);
585
1230
  }
586
1231
  function isEnnoSearchQuestion(question) {
587
- return question.header === '実行方式とモデル' && /^enno-(?:provider|model)-(?:ideal|zenki|goki|worker|check)$/u.test(question.id);
1232
+ return question.header === "\u5B9F\u884C\u65B9\u5F0F\u3068\u30E2\u30C7\u30EB" && /^enno-(?:provider|model)-(?:ideal|zenki|goki|worker|check)$/u.test(question.id);
588
1233
  }
589
1234
  function questionInputKind(question) {
590
- if (isEnnoSearchQuestion(question) || (question.header === 'Deep planning' && question.id === 'deep-role-model'))
591
- return 'search';
592
- if (question.header === 'Deep planning' && question.id === 'deep-budget-value')
593
- return 'value';
594
- return 'choice';
1235
+ if (isEnnoSearchQuestion(question) || question.header === "Deep planning" && question.id === "deep-role-model")
1236
+ return "search";
1237
+ if (question.header === "Deep planning" && question.id === "deep-budget-value")
1238
+ return "value";
1239
+ return "choice";
595
1240
  }
596
1241
  function isPlanReview(p) {
597
- const q = p.questions[0];
598
- return p.questions.length === 1 && !!q && !q.multiSelect && q.intent?.kind === 'plan-review'
599
- && typeof q.detail === 'string' && (q.options?.length ?? 0) <= 3
600
- && q.options?.some(o => o.label === q.intent.approve) === true;
1242
+ const q = p.questions[0];
1243
+ return p.questions.length === 1 && !!q && !q.multiSelect && q.intent?.kind === "plan-review" && typeof q.detail === "string" && (q.options?.length ?? 0) <= 3 && q.options?.some((o) => o.label === q.intent.approve) === true;
601
1244
  }
602
1245
  function intakePending(props) {
603
- const p = props.pendingInteraction;
604
- if (!p || !Array.isArray(p.questions) || !p.questions.length || typeof p.answer !== 'function'
605
- || typeof p.dismiss !== 'function' && typeof p.cancel !== 'function')
606
- return null;
607
- if (p.sessionId && typeof props.sessionId === 'string' && props.sessionId !== p.sessionId)
608
- return null;
609
- if (!p.questions.every(q => typeof q.id === 'string' && typeof q.question === 'string'
610
- && (q.options === undefined || Array.isArray(q.options) && q.options.every((o) => typeof o.label === 'string'))))
611
- return null;
612
- if (p.kind === 'question')
613
- return p;
614
- const q = p.questions[0];
615
- return p.kind === 'plan-review' && p.questions.length === 1 && !q.multiSelect && q.intent?.kind === 'plan-review'
616
- && typeof q.detail === 'string' && (q.options?.length ?? 0) <= 3 && q.options?.some((o) => o.label === q.intent.approve) ? p : null;
1246
+ const p = props.pendingInteraction;
1247
+ if (!p || !Array.isArray(p.questions) || !p.questions.length || typeof p.answer !== "function" || typeof p.dismiss !== "function" && typeof p.cancel !== "function")
1248
+ return null;
1249
+ if (p.sessionId && typeof props.sessionId === "string" && props.sessionId !== p.sessionId)
1250
+ return null;
1251
+ if (!p.questions.every((q2) => typeof q2.id === "string" && typeof q2.question === "string" && (q2.options === void 0 || Array.isArray(q2.options) && q2.options.every((o) => typeof o.label === "string"))))
1252
+ return null;
1253
+ if (p.kind === "question")
1254
+ return p;
1255
+ const q = p.questions[0];
1256
+ return p.kind === "plan-review" && p.questions.length === 1 && !q.multiSelect && q.intent?.kind === "plan-review" && typeof q.detail === "string" && (q.options?.length ?? 0) <= 3 && q.options?.some((o) => o.label === q.intent.approve) ? p : null;
617
1257
  }
618
1258
  function IntakeQuestion(props) {
619
- const pending = props.matched, t = props.t;
620
- return jsx(IntakeQuestionCard, { key: `${pending.sessionId ?? ''}:${pending.key}`, pending,
621
- ...(isPlanReview(pending) ? { reviewCopy: { discuss: t('review.discuss'),
622
- labels: { code: { copyLabel: t('review.copy'), copiedLabel: t('review.copied') }, footnotes: t('review.footnotes') } } } : {}),
623
- });
1259
+ const pending = props.matched, t = props.t;
1260
+ return jsx(IntakeQuestionCard, {
1261
+ key: `${pending.sessionId ?? ""}:${pending.key}`,
1262
+ pending,
1263
+ ...isPlanReview(pending) ? { reviewCopy: {
1264
+ discuss: t("review.discuss"),
1265
+ labels: { code: { copyLabel: t("review.copy"), copiedLabel: t("review.copied") }, footnotes: t("review.footnotes") }
1266
+ } } : {}
1267
+ });
624
1268
  }
625
1269
  function IntakeQuestionCard(props) {
626
- const pending = props.pending;
627
- const reviewCopy = props.reviewCopy;
628
- const reviewing = isPlanReview(pending);
629
- const [batch, setBatch] = useState(() => readIntakeDraft(pending));
630
- const [snapshot, setSnapshot] = useState(() => pendingState(pending));
631
- const [busy, setBusy] = useState(false), [error, setError] = useState('');
632
- const inFlight = useRef(false), mounted = useRef(false), sent = useRef(false);
633
- const generation = useRef(0);
634
- const sentChannel = useRef('none');
635
- const card = useRef(null), optionElements = useRef([]);
636
- const original = pending.questions[batch.page];
637
- const options = original.options ?? [];
638
- const alternatives = options.filter(option => option.label !== original.intent?.approve);
639
- const question = { ...original, options: reviewing ? [{ label: reviewCopy.discuss },
640
- ...alternatives.slice(0, 1), ...options.filter(option => option.label === original.intent?.approve),
641
- ...alternatives.slice(1)] : options };
642
- const draft = batch.drafts[batch.page], multi = question.multiSelect === true, inputKind = questionInputKind(question);
643
- const editable = () => !pending.review && !pendingState(pending).closed && pendingState(pending).channel !== 'none';
644
- useEffect(() => {
645
- generation.current++;
646
- mounted.current = true;
647
- inFlight.current = false;
1270
+ const pending = props.pending;
1271
+ const reviewCopy = props.reviewCopy;
1272
+ const reviewing = isPlanReview(pending);
1273
+ const [batch, setBatch] = useState(() => readIntakeDraft(pending));
1274
+ const [snapshot, setSnapshot] = useState(() => pendingState(pending));
1275
+ const [busy, setBusy] = useState(false), [error, setError] = useState("");
1276
+ const inFlight = useRef(false), mounted = useRef(false), sent = useRef(false);
1277
+ const generation = useRef(0);
1278
+ const sentChannel = useRef("none");
1279
+ const card = useRef(null), optionElements = useRef([]);
1280
+ const original = pending.questions[batch.page];
1281
+ const options = original.options ?? [];
1282
+ const alternatives = options.filter((option) => option.label !== original.intent?.approve);
1283
+ const question = { ...original, options: reviewing ? [
1284
+ { label: reviewCopy.discuss },
1285
+ ...alternatives.slice(0, 1),
1286
+ ...options.filter((option) => option.label === original.intent?.approve),
1287
+ ...alternatives.slice(1)
1288
+ ] : options };
1289
+ const draft = batch.drafts[batch.page], multi = question.multiSelect === true, inputKind = questionInputKind(question);
1290
+ const editable = () => !pending.review && !pendingState(pending).closed && pendingState(pending).channel !== "none";
1291
+ useEffect(() => {
1292
+ generation.current++;
1293
+ mounted.current = true;
1294
+ inFlight.current = false;
1295
+ sent.current = false;
1296
+ setBusy(false);
1297
+ setError("");
1298
+ setBatch(readIntakeDraft(pending));
1299
+ const sync = () => {
1300
+ const next = pendingState(pending);
1301
+ setSnapshot(next);
1302
+ if (next.closed) {
1303
+ saveIntakeDraft(pending);
1304
+ return;
1305
+ }
1306
+ if (sent.current && sentChannel.current === "waterfall" && next.channel === "rpc") {
648
1307
  sent.current = false;
1308
+ inFlight.current = false;
649
1309
  setBusy(false);
650
- setError('');
651
- setBatch(readIntakeDraft(pending));
652
- const sync = () => {
653
- const next = pendingState(pending);
654
- setSnapshot(next);
655
- if (next.closed) {
656
- saveIntakeDraft(pending);
657
- return;
658
- }
659
- if (sent.current && sentChannel.current === 'waterfall' && next.channel === 'rpc') {
660
- sent.current = false;
661
- inFlight.current = false;
662
- setBusy(false);
663
- }
664
- };
665
- const unsubscribe = pending.subscribe?.(sync);
666
- sync();
667
- card.current?.focus();
1310
+ }
1311
+ };
1312
+ const unsubscribe = pending.subscribe?.(sync);
1313
+ sync();
1314
+ card.current?.focus();
1315
+ pending.holdFocus?.();
1316
+ const win = card.current?.ownerDocument?.defaultView;
1317
+ const release = () => pending.releaseFocus?.();
1318
+ const hold = () => {
1319
+ if (card.current?.contains(card.current.ownerDocument.activeElement))
668
1320
  pending.holdFocus?.();
669
- const win = card.current?.ownerDocument?.defaultView;
670
- const release = () => pending.releaseFocus?.();
671
- const hold = () => { if (card.current?.contains(card.current.ownerDocument.activeElement))
672
- pending.holdFocus?.(); };
673
- win?.addEventListener('blur', release);
674
- win?.addEventListener('focus', hold);
675
- return () => { generation.current++; mounted.current = false; unsubscribe?.(); pending.releaseFocus?.(); win?.removeEventListener('blur', release); win?.removeEventListener('focus', hold); };
676
- }, [pending]);
677
- useEffect(() => { if (draft.selected !== null)
678
- optionElements.current[draft.selected]?.scrollIntoView({ block: 'nearest' }); }, [draft.selected]);
679
- const update = (value) => {
680
- if (inFlight.current || !editable())
681
- return;
682
- const current = readIntakeDraft(pending), next = { ...current, drafts: current.drafts.map((d, i) => i === current.page ? value : d) };
683
- saveIntakeDraft(pending, next);
684
- setBatch(next);
685
- setError('');
686
- pending.engage?.();
687
1321
  };
688
- const choose = (index) => {
689
- const d = readIntakeDraft(pending).drafts[batch.page];
690
- update({ selected: index, checked: multi ? d.checked.includes(index) ? d.checked.filter(n => n !== index) : [...d.checked, index] : [], custom: '', ordinal: '' });
1322
+ win?.addEventListener("blur", release);
1323
+ win?.addEventListener("focus", hold);
1324
+ return () => {
1325
+ generation.current++;
1326
+ mounted.current = false;
1327
+ unsubscribe?.();
1328
+ pending.releaseFocus?.();
1329
+ win?.removeEventListener("blur", release);
1330
+ win?.removeEventListener("focus", hold);
691
1331
  };
692
- const page = (index) => { const next = { ...readIntakeDraft(pending), page: index }; saveIntakeDraft(pending, next); setBatch(next); setError(''); card.current?.focus(); };
693
- const settle = (cancel = false, all = false) => {
694
- if (inFlight.current || !mounted.current || pendingState(pending).closed || !cancel && !editable())
695
- return;
696
- const current = readIntakeDraft(pending), d = current.drafts[current.page];
697
- const answers = [];
698
- if (!cancel) {
699
- for (let i = 0; i <= (all ? pending.questions.length - 1 : current.page); i++) {
700
- const q = pending.questions[i], value = current.drafts[i], opts = reviewing ? question.options : q.options ?? [];
701
- let selected = q.multiSelect ? value.checked : value.selected === null ? [] : [value.selected];
702
- let custom = value.custom.trim();
703
- if (value.ordinal && (Number(value.ordinal) < 1 || Number(value.ordinal) > opts.length || q.multiSelect)) {
704
- setError(q.multiSelect ? '入力した番号はSpaceでチェックしてください。' : `番号は1〜${opts.length}で入力してください。`);
705
- return;
706
- }
707
- if (!reviewing && questionInputKind(q) === 'choice' && opts.length && /^[0-90-9]+$/u.test(custom)) {
708
- const n = Number(custom.normalize('NFKC'));
709
- if (n < 1 || n > opts.length) {
710
- setError(`番号は1〜${opts.length}で入力してください。`);
711
- return;
712
- }
713
- selected = q.multiSelect ? [...new Set([...selected, n - 1])] : [n - 1];
714
- custom = '';
715
- }
716
- if (!selected.length && !custom) {
717
- page(i);
718
- setError('選択肢を選ぶか、自由入力してください。');
719
- return;
720
- }
721
- answers.push({ id: q.id, selected: selected.map(n => opts[n].label), ...(custom ? { custom } : {}) });
722
- }
723
- if (!all && current.page < pending.questions.length - 1) {
724
- page(current.page + 1);
725
- return;
726
- }
727
- }
728
- inFlight.current = true;
729
- setBusy(true);
730
- setError('');
731
- const epoch = generation.current;
732
- const currentCard = () => mounted.current && generation.current === epoch;
733
- const dismissing = cancel || reviewing && d.selected === 0;
734
- void Promise.resolve().then(() => {
735
- if (!currentCard() || pendingState(pending).closed || !dismissing && !editable())
736
- throw new Error('この質問には現在回答できません。');
737
- sentChannel.current = pendingState(pending).channel;
738
- return dismissing ? dismissQuestion(pending) : pending.answer({ answers });
739
- }).then(() => {
740
- if (!currentCard())
741
- return;
742
- if (!pending.snapshot || pendingState(pending).closed || dismissing && pending.dismissal !== 'hide')
743
- saveIntakeDraft(pending);
744
- if (pending.snapshot && !dismissing && !pendingState(pending).closed) {
745
- sent.current = true;
746
- if (sentChannel.current === 'waterfall' && pendingState(pending).channel === 'rpc') {
747
- sent.current = false;
748
- inFlight.current = false;
749
- if (mounted.current)
750
- setBusy(false);
751
- }
752
- }
753
- }).catch(cause => { if (currentCard()) {
754
- inFlight.current = false;
755
- setBusy(false);
756
- setError(cause instanceof Error ? cause.message : String(cause));
757
- } });
758
- };
759
- const keyDown = (event) => {
760
- if (event.defaultPrevented || inFlight.current || composingKey(event) || event.altKey || event.shiftKey)
761
- return;
762
- if (event.key === 'Escape') {
763
- event.preventDefault();
764
- event.stopPropagation();
765
- void settle(true);
766
- return;
767
- }
768
- if (pending.review && event.key === 'Enter' && !event.ctrlKey && !event.metaKey && event.target?.tagName !== 'BUTTON' && batch.page < pending.questions.length - 1) {
769
- event.preventDefault();
770
- event.stopPropagation();
771
- page(batch.page + 1);
772
- return;
773
- }
774
- if (!editable())
775
- return;
776
- if (event.key === 'Enter' && modifiedKey(event)) {
777
- event.preventDefault();
778
- event.stopPropagation();
779
- settle(false, true);
780
- return;
781
- }
782
- if (editingKey(event)) {
783
- if (event.key === 'Enter' && !event.ctrlKey && !event.metaKey && !event.target?.isContentEditable) {
784
- event.preventDefault();
785
- event.stopPropagation();
786
- if (!multi || batch.page < pending.questions.length - 1)
787
- settle();
788
- }
789
- return;
1332
+ }, [pending]);
1333
+ useEffect(() => {
1334
+ if (draft.selected !== null)
1335
+ optionElements.current[draft.selected]?.scrollIntoView({ block: "nearest" });
1336
+ }, [draft.selected]);
1337
+ const update = (value) => {
1338
+ if (inFlight.current || !editable())
1339
+ return;
1340
+ const current = readIntakeDraft(pending), next = { ...current, drafts: current.drafts.map((d, i) => i === current.page ? value : d) };
1341
+ saveIntakeDraft(pending, next);
1342
+ setBatch(next);
1343
+ setError("");
1344
+ pending.engage?.();
1345
+ };
1346
+ const choose = (index) => {
1347
+ const d = readIntakeDraft(pending).drafts[batch.page];
1348
+ update({ selected: index, checked: multi ? d.checked.includes(index) ? d.checked.filter((n) => n !== index) : [...d.checked, index] : [], custom: "", ordinal: "" });
1349
+ };
1350
+ const page = (index) => {
1351
+ const next = { ...readIntakeDraft(pending), page: index };
1352
+ saveIntakeDraft(pending, next);
1353
+ setBatch(next);
1354
+ setError("");
1355
+ card.current?.focus();
1356
+ };
1357
+ const settle = (cancel = false, all = false) => {
1358
+ if (inFlight.current || !mounted.current || pendingState(pending).closed || !cancel && !editable())
1359
+ return;
1360
+ const current = readIntakeDraft(pending), d = current.drafts[current.page];
1361
+ const answers = [];
1362
+ if (!cancel) {
1363
+ for (let i = 0; i <= (all ? pending.questions.length - 1 : current.page); i++) {
1364
+ const q = pending.questions[i], value = current.drafts[i], opts = reviewing ? question.options : q.options ?? [];
1365
+ let selected = q.multiSelect ? value.checked : value.selected === null ? [] : [value.selected];
1366
+ let custom = value.custom.trim();
1367
+ if (value.ordinal && (Number(value.ordinal) < 1 || Number(value.ordinal) > opts.length || q.multiSelect)) {
1368
+ setError(q.multiSelect ? "\u5165\u529B\u3057\u305F\u756A\u53F7\u306FSpace\u3067\u30C1\u30A7\u30C3\u30AF\u3057\u3066\u304F\u3060\u3055\u3044\u3002" : `\u756A\u53F7\u306F1\u301C${opts.length}\u3067\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044\u3002`);
1369
+ return;
790
1370
  }
791
- const modified = modifiedKey(event);
792
- if ((event.ctrlKey || event.metaKey) && !modified)
793
- return;
794
- const digit = /^(?:Digit|Numpad)([0-9])$/u.exec(event.code ?? '')?.[1] ?? event.key;
795
- const d = readIntakeDraft(pending).drafts[batch.page];
796
- if (modified && /^[1-9]$/u.test(digit) && Number(digit) <= question.options.length) {
797
- event.preventDefault();
798
- event.stopPropagation();
799
- choose(Number(digit) - 1);
800
- card.current?.focus();
801
- return;
802
- }
803
- if (modified)
804
- return;
805
- if (question.options.length && (/^[0-9]$/u.test(digit) || event.key === 'Backspace' && d.ordinal)) {
806
- if (question.options.length <= 9 && (!/^[1-9]$/u.test(digit) || Number(digit) > question.options.length))
807
- return;
808
- event.preventDefault();
809
- event.stopPropagation();
810
- if (question.options.length <= 9)
811
- choose(Number(digit) - 1);
812
- else {
813
- const ordinal = event.key === 'Backspace' ? d.ordinal.slice(0, -1) : (d.ordinal + digit).slice(0, 9);
814
- const n = Number(ordinal), valid = n >= 1 && n <= question.options.length;
815
- update({ ...d, ordinal, custom: '', selected: !multi && valid ? n - 1 : multi ? d.selected : null });
816
- if (ordinal && !valid)
817
- setError(`番号「${ordinal}」は範囲外です。Backspaceで訂正してください。`);
818
- }
819
- card.current?.focus();
1371
+ if (!reviewing && questionInputKind(q) === "choice" && opts.length && /^[0-90-9]+$/u.test(custom)) {
1372
+ const n = Number(custom.normalize("NFKC"));
1373
+ if (n < 1 || n > opts.length) {
1374
+ setError(`\u756A\u53F7\u306F1\u301C${opts.length}\u3067\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044\u3002`);
820
1375
  return;
1376
+ }
1377
+ selected = q.multiSelect ? [.../* @__PURE__ */ new Set([...selected, n - 1])] : [n - 1];
1378
+ custom = "";
821
1379
  }
822
- if (event.key === ' ' && multi && d.ordinal) {
823
- event.preventDefault();
824
- event.stopPropagation();
825
- const n = Number(d.ordinal);
826
- if (n >= 1 && n <= question.options.length)
827
- choose(n - 1);
828
- return;
1380
+ if (!selected.length && !custom) {
1381
+ page(i);
1382
+ setError("\u9078\u629E\u80A2\u3092\u9078\u3076\u304B\u3001\u81EA\u7531\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044\u3002");
1383
+ return;
829
1384
  }
830
- if (event.key === 'Enter' && event.target?.tagName !== 'BUTTON') {
831
- event.preventDefault();
832
- event.stopPropagation();
833
- if (!multi || batch.page < pending.questions.length - 1)
834
- settle();
1385
+ answers.push({ id: q.id, selected: selected.map((n) => opts[n].label), ...custom ? { custom } : {} });
1386
+ }
1387
+ if (!all && current.page < pending.questions.length - 1) {
1388
+ page(current.page + 1);
1389
+ return;
1390
+ }
1391
+ }
1392
+ inFlight.current = true;
1393
+ setBusy(true);
1394
+ setError("");
1395
+ const epoch = generation.current;
1396
+ const currentCard = () => mounted.current && generation.current === epoch;
1397
+ const dismissing = cancel || reviewing && d.selected === 0;
1398
+ void Promise.resolve().then(() => {
1399
+ if (!currentCard() || pendingState(pending).closed || !dismissing && !editable())
1400
+ throw new Error("\u3053\u306E\u8CEA\u554F\u306B\u306F\u73FE\u5728\u56DE\u7B54\u3067\u304D\u307E\u305B\u3093\u3002");
1401
+ sentChannel.current = pendingState(pending).channel;
1402
+ return dismissing ? dismissQuestion(pending) : pending.answer({ answers });
1403
+ }).then(() => {
1404
+ if (!currentCard())
1405
+ return;
1406
+ if (!pending.snapshot || pendingState(pending).closed || dismissing && pending.dismissal !== "hide")
1407
+ saveIntakeDraft(pending);
1408
+ if (pending.snapshot && !dismissing && !pendingState(pending).closed) {
1409
+ sent.current = true;
1410
+ if (sentChannel.current === "waterfall" && pendingState(pending).channel === "rpc") {
1411
+ sent.current = false;
1412
+ inFlight.current = false;
1413
+ if (mounted.current)
1414
+ setBusy(false);
835
1415
  }
836
- };
837
- useShortcutGroup(card, keyDown);
838
- const disabled = busy || !editable(), titleId = `kiokuko-intake-${pending.key}`;
839
- const hint = question.options.length ? `${shortcutPlatform().label}+1〜${Math.min(9, question.options.length)}、または数字で${multi ? 'チェック切替' : '選択'}。${question.options.length > 9 ? `10以上は番号を続けて入力、Backspaceで訂正${multi ? '、Spaceでチェック' : ''}。` : ''}${multi ? `${shortcutPlatform().label}+Enterで全回答を送信。` : 'Enterで確定。'}` : `自由入力後、${multi ? shortcutPlatform().label + '+' : ''}Enterで確定。Shift+Enterで改行。`;
840
- return jsxs('section', { className: 'kiokuko-intake', tabIndex: 0, 'data-kiokuko-shortcuts': '',
841
- ref: (el) => { card.current = el; }, 'aria-labelledby': titleId, 'aria-busy': busy, onKeyDown: keyDown,
842
- onFocusCapture: () => pending.holdFocus?.(),
843
- onBlurCapture: (event) => { if (!card.current?.contains(event.relatedTarget))
844
- pending.releaseFocus?.(); },
845
- children: [
846
- jsxs('header', { children: [jsx('h2', { id: titleId, children: question.question }),
847
- jsx('button', { type: 'button', disabled: busy || snapshot.closed, onClick: () => settle(true), 'aria-label': '質問を閉じる', children: '閉じる(Esc)' })] }),
848
- jsx('p', { id: `${titleId}-keys`, children: pending.review ? '回答済みの内容です。Enterで次の質問を表示、Escで閉じます。' : hint }),
849
- jsxs('div', { className: 'kiokuko-intake-body', children: [
850
- reviewing ? jsx(MarkdownText, { text: question.detail, labels: reviewCopy.labels }) : question.detail ? jsx('p', { children: question.detail }) : null,
851
- !pending.review && !snapshot.closed && snapshot.state === 'continued' ? jsx('p', { children: '処理は継続中です。この質問には後から回答できます。' }) : null,
852
- snapshot.countdown ? jsx('p', { children: `回答待ち: ${Math.ceil(snapshot.countdown.remainingMs / 1000)}秒${snapshot.countdown.running ? '' : '(操作中は停止)'}` }) : null,
853
- pending.review ? jsx('p', { children: `回答済み: ${pending.review.find(a => a.id === question.id)?.selected.join('、') ?? ''} ${pending.review.find(a => a.id === question.id)?.custom ?? ''}` }) : null,
854
- !pending.review && !editable() ? jsx('p', { role: 'status', children: snapshot.closed ? 'この質問は終了しました。' : '回答経路の接続を待っています。' }) : null,
855
- jsx('div', { 'aria-label': '選択肢', 'aria-describedby': `${titleId}-keys`, children: question.options.map((option, index) => jsxs('button', {
856
- type: 'button', className: 'kiokuko-intake-option', disabled,
857
- ref: (el) => { optionElements.current[index] = el; },
858
- ...(multi ? { role: 'checkbox', 'aria-checked': draft.checked.includes(index) } : { 'aria-pressed': draft.selected === index }),
859
- ...(!pending.review ? shortcutAria(index, question.options.length) : {}),
860
- onClick: () => choose(index),
861
- onKeyDown: (event) => {
862
- if (event.key === 'Enter' && !multi && !composingKey(event) && !event.ctrlKey && !event.metaKey && !event.shiftKey && !event.altKey
863
- && readIntakeDraft(pending).drafts[batch.page].selected === index) {
864
- event.preventDefault();
865
- event.stopPropagation();
866
- settle();
867
- }
868
- },
869
- children: [jsx('strong', { children: `${index + 1}. ${option.label}` }), option.description ? jsx('span', { children: option.description }) : null,
870
- !pending.review ? jsx('kbd', { className: 'kiokuko-intake-shortcut', 'aria-hidden': true, children: shortcutLabel(index, multi) }) : null],
871
- }, index)) }),
872
- !reviewing ? jsx('label', { htmlFor: `${titleId}-custom`, children: inputKind === 'search' ? '検索(数字も検索語として入力できます)' : inputKind === 'value' ? '値を入力' : '自由入力(任意・Shift+Enterで改行)' }) : null,
873
- !reviewing ? jsx('textarea', { id: `${titleId}-custom`, rows: 1, disabled, value: draft.custom,
874
- onChange: (e) => update({ ...emptyDraft(), checked: draft.checked, custom: e.target.value }),
875
- }) : null,
876
- ] }),
877
- jsxs('footer', { children: [
878
- batch.page > 0 ? jsx('button', { type: 'button', disabled: busy, onClick: () => page(batch.page - 1), children: '前の質問' }) : null,
879
- pending.questions.length > 1 ? jsx('span', { children: `${batch.page + 1}/${pending.questions.length}` }) : null,
880
- jsx('span', { role: 'status', 'aria-live': 'polite', children: error || (busy ? '送信中…' : draft.ordinal ? `入力番号: ${draft.ordinal}` : multi ? `${draft.checked.length}件を選択中` : draft.selected === null ? '' : `${draft.selected + 1}. ${question.options[draft.selected].label}を選択中`) }),
881
- pending.review ? batch.page < pending.questions.length - 1 ? jsx('button', { type: 'button', onClick: () => page(batch.page + 1), children: '次の質問(Enter)' }) : null
882
- : jsx('button', { type: 'button', disabled: disabled || (multi ? !draft.checked.length && !draft.custom.trim() : draft.selected === null && !draft.custom.trim()), onClick: () => settle(), children: batch.page < pending.questions.length - 1 ? '次の質問(Enter)' : `確定(${multi ? shortcutPlatform().label + '+' : ''}Enter)` }),
883
- ] }),
884
- ],
1416
+ }
1417
+ }).catch((cause) => {
1418
+ if (currentCard()) {
1419
+ inFlight.current = false;
1420
+ setBusy(false);
1421
+ setError(cause instanceof Error ? cause.message : String(cause));
1422
+ }
885
1423
  });
1424
+ };
1425
+ const keyDown = (event) => {
1426
+ if (event.defaultPrevented || inFlight.current || composingKey(event) || event.altKey || event.shiftKey)
1427
+ return;
1428
+ if (event.key === "Escape") {
1429
+ event.preventDefault();
1430
+ event.stopPropagation();
1431
+ void settle(true);
1432
+ return;
1433
+ }
1434
+ if (pending.review && event.key === "Enter" && !event.ctrlKey && !event.metaKey && event.target?.tagName !== "BUTTON" && batch.page < pending.questions.length - 1) {
1435
+ event.preventDefault();
1436
+ event.stopPropagation();
1437
+ page(batch.page + 1);
1438
+ return;
1439
+ }
1440
+ if (!editable())
1441
+ return;
1442
+ if (event.key === "Enter" && modifiedKey(event)) {
1443
+ event.preventDefault();
1444
+ event.stopPropagation();
1445
+ settle(false, true);
1446
+ return;
1447
+ }
1448
+ if (editingKey(event)) {
1449
+ if (event.key === "Enter" && !event.ctrlKey && !event.metaKey && !event.target?.isContentEditable) {
1450
+ event.preventDefault();
1451
+ event.stopPropagation();
1452
+ if (!multi || batch.page < pending.questions.length - 1)
1453
+ settle();
1454
+ }
1455
+ return;
1456
+ }
1457
+ const modified = modifiedKey(event);
1458
+ if ((event.ctrlKey || event.metaKey) && !modified)
1459
+ return;
1460
+ const digit = /^(?:Digit|Numpad)([0-9])$/u.exec(event.code ?? "")?.[1] ?? event.key;
1461
+ const d = readIntakeDraft(pending).drafts[batch.page];
1462
+ if (modified && /^[1-9]$/u.test(digit) && Number(digit) <= question.options.length) {
1463
+ event.preventDefault();
1464
+ event.stopPropagation();
1465
+ choose(Number(digit) - 1);
1466
+ card.current?.focus();
1467
+ return;
1468
+ }
1469
+ if (modified)
1470
+ return;
1471
+ if (question.options.length && (/^[0-9]$/u.test(digit) || event.key === "Backspace" && d.ordinal)) {
1472
+ if (question.options.length <= 9 && (!/^[1-9]$/u.test(digit) || Number(digit) > question.options.length))
1473
+ return;
1474
+ event.preventDefault();
1475
+ event.stopPropagation();
1476
+ if (question.options.length <= 9)
1477
+ choose(Number(digit) - 1);
1478
+ else {
1479
+ const ordinal = event.key === "Backspace" ? d.ordinal.slice(0, -1) : (d.ordinal + digit).slice(0, 9);
1480
+ const n = Number(ordinal), valid = n >= 1 && n <= question.options.length;
1481
+ update({ ...d, ordinal, custom: "", selected: !multi && valid ? n - 1 : multi ? d.selected : null });
1482
+ if (ordinal && !valid)
1483
+ setError(`\u756A\u53F7\u300C${ordinal}\u300D\u306F\u7BC4\u56F2\u5916\u3067\u3059\u3002Backspace\u3067\u8A02\u6B63\u3057\u3066\u304F\u3060\u3055\u3044\u3002`);
1484
+ }
1485
+ card.current?.focus();
1486
+ return;
1487
+ }
1488
+ if (event.key === " " && multi && d.ordinal) {
1489
+ event.preventDefault();
1490
+ event.stopPropagation();
1491
+ const n = Number(d.ordinal);
1492
+ if (n >= 1 && n <= question.options.length)
1493
+ choose(n - 1);
1494
+ return;
1495
+ }
1496
+ if (event.key === "Enter" && event.target?.tagName !== "BUTTON") {
1497
+ event.preventDefault();
1498
+ event.stopPropagation();
1499
+ if (!multi || batch.page < pending.questions.length - 1)
1500
+ settle();
1501
+ }
1502
+ };
1503
+ useShortcutGroup(card, keyDown);
1504
+ const disabled = busy || !editable(), titleId = `kiokuko-intake-${pending.key}`;
1505
+ const hint = question.options.length ? `${shortcutPlatform().label}+1\u301C${Math.min(9, question.options.length)}\u3001\u307E\u305F\u306F\u6570\u5B57\u3067${multi ? "\u30C1\u30A7\u30C3\u30AF\u5207\u66FF" : "\u9078\u629E"}\u3002${question.options.length > 9 ? `10\u4EE5\u4E0A\u306F\u756A\u53F7\u3092\u7D9A\u3051\u3066\u5165\u529B\u3001Backspace\u3067\u8A02\u6B63${multi ? "\u3001Space\u3067\u30C1\u30A7\u30C3\u30AF" : ""}\u3002` : ""}${multi ? `${shortcutPlatform().label}+Enter\u3067\u5168\u56DE\u7B54\u3092\u9001\u4FE1\u3002` : "Enter\u3067\u78BA\u5B9A\u3002"}` : `\u81EA\u7531\u5165\u529B\u5F8C\u3001${multi ? shortcutPlatform().label + "+" : ""}Enter\u3067\u78BA\u5B9A\u3002Shift+Enter\u3067\u6539\u884C\u3002`;
1506
+ return jsxs("section", {
1507
+ className: "kiokuko-intake",
1508
+ tabIndex: 0,
1509
+ "data-kiokuko-shortcuts": "",
1510
+ ref: (el) => {
1511
+ card.current = el;
1512
+ },
1513
+ "aria-labelledby": titleId,
1514
+ "aria-busy": busy,
1515
+ onKeyDown: keyDown,
1516
+ onFocusCapture: () => pending.holdFocus?.(),
1517
+ onBlurCapture: (event) => {
1518
+ if (!card.current?.contains(event.relatedTarget))
1519
+ pending.releaseFocus?.();
1520
+ },
1521
+ children: [
1522
+ jsxs("header", { children: [
1523
+ jsx("h2", { id: titleId, children: question.question }),
1524
+ jsx("button", { type: "button", disabled: busy || snapshot.closed, onClick: () => settle(true), "aria-label": "\u8CEA\u554F\u3092\u9589\u3058\u308B", children: "\u9589\u3058\u308B\uFF08Esc\uFF09" })
1525
+ ] }),
1526
+ jsx("p", { id: `${titleId}-keys`, children: pending.review ? "\u56DE\u7B54\u6E08\u307F\u306E\u5185\u5BB9\u3067\u3059\u3002Enter\u3067\u6B21\u306E\u8CEA\u554F\u3092\u8868\u793A\u3001Esc\u3067\u9589\u3058\u307E\u3059\u3002" : hint }),
1527
+ jsxs("div", { className: "kiokuko-intake-body", children: [
1528
+ reviewing ? jsx(MarkdownText, { text: question.detail, labels: reviewCopy.labels }) : question.detail ? jsx("p", { children: question.detail }) : null,
1529
+ !pending.review && !snapshot.closed && snapshot.state === "continued" ? jsx("p", { children: "\u51E6\u7406\u306F\u7D99\u7D9A\u4E2D\u3067\u3059\u3002\u3053\u306E\u8CEA\u554F\u306B\u306F\u5F8C\u304B\u3089\u56DE\u7B54\u3067\u304D\u307E\u3059\u3002" }) : null,
1530
+ snapshot.countdown ? jsx("p", { children: `\u56DE\u7B54\u5F85\u3061: ${Math.ceil(snapshot.countdown.remainingMs / 1e3)}\u79D2${snapshot.countdown.running ? "" : "\uFF08\u64CD\u4F5C\u4E2D\u306F\u505C\u6B62\uFF09"}` }) : null,
1531
+ pending.review ? jsx("p", { children: `\u56DE\u7B54\u6E08\u307F: ${pending.review.find((a) => a.id === question.id)?.selected.join("\u3001") ?? ""} ${pending.review.find((a) => a.id === question.id)?.custom ?? ""}` }) : null,
1532
+ !pending.review && !editable() ? jsx("p", { role: "status", children: snapshot.closed ? "\u3053\u306E\u8CEA\u554F\u306F\u7D42\u4E86\u3057\u307E\u3057\u305F\u3002" : "\u56DE\u7B54\u7D4C\u8DEF\u306E\u63A5\u7D9A\u3092\u5F85\u3063\u3066\u3044\u307E\u3059\u3002" }) : null,
1533
+ jsx("div", { "aria-label": "\u9078\u629E\u80A2", "aria-describedby": `${titleId}-keys`, children: question.options.map((option, index) => jsxs("button", {
1534
+ type: "button",
1535
+ className: "kiokuko-intake-option",
1536
+ disabled,
1537
+ ref: (el) => {
1538
+ optionElements.current[index] = el;
1539
+ },
1540
+ ...multi ? { role: "checkbox", "aria-checked": draft.checked.includes(index) } : { "aria-pressed": draft.selected === index },
1541
+ ...!pending.review ? shortcutAria(index, question.options.length) : {},
1542
+ onClick: () => choose(index),
1543
+ onKeyDown: (event) => {
1544
+ if (event.key === "Enter" && !multi && !composingKey(event) && !event.ctrlKey && !event.metaKey && !event.shiftKey && !event.altKey && readIntakeDraft(pending).drafts[batch.page].selected === index) {
1545
+ event.preventDefault();
1546
+ event.stopPropagation();
1547
+ settle();
1548
+ }
1549
+ },
1550
+ children: [
1551
+ jsx("strong", { children: `${index + 1}. ${option.label}` }),
1552
+ option.description ? jsx("span", { children: option.description }) : null,
1553
+ !pending.review ? jsx("kbd", { className: "kiokuko-intake-shortcut", "aria-hidden": true, children: shortcutLabel(index, multi) }) : null
1554
+ ]
1555
+ }, index)) }),
1556
+ !reviewing ? jsx("label", { htmlFor: `${titleId}-custom`, children: inputKind === "search" ? "\u691C\u7D22\uFF08\u6570\u5B57\u3082\u691C\u7D22\u8A9E\u3068\u3057\u3066\u5165\u529B\u3067\u304D\u307E\u3059\uFF09" : inputKind === "value" ? "\u5024\u3092\u5165\u529B" : "\u81EA\u7531\u5165\u529B\uFF08\u4EFB\u610F\u30FBShift+Enter\u3067\u6539\u884C\uFF09" }) : null,
1557
+ !reviewing ? jsx("textarea", {
1558
+ id: `${titleId}-custom`,
1559
+ rows: 1,
1560
+ disabled,
1561
+ value: draft.custom,
1562
+ onChange: (e) => update({ ...emptyDraft(), checked: draft.checked, custom: e.target.value })
1563
+ }) : null
1564
+ ] }),
1565
+ jsxs("footer", { children: [
1566
+ batch.page > 0 ? jsx("button", { type: "button", disabled: busy, onClick: () => page(batch.page - 1), children: "\u524D\u306E\u8CEA\u554F" }) : null,
1567
+ pending.questions.length > 1 ? jsx("span", { children: `${batch.page + 1}/${pending.questions.length}` }) : null,
1568
+ jsx("span", { role: "status", "aria-live": "polite", children: error || (busy ? "\u9001\u4FE1\u4E2D\u2026" : draft.ordinal ? `\u5165\u529B\u756A\u53F7: ${draft.ordinal}` : multi ? `${draft.checked.length}\u4EF6\u3092\u9078\u629E\u4E2D` : draft.selected === null ? "" : `${draft.selected + 1}. ${question.options[draft.selected].label}\u3092\u9078\u629E\u4E2D`) }),
1569
+ pending.review ? batch.page < pending.questions.length - 1 ? jsx("button", { type: "button", onClick: () => page(batch.page + 1), children: "\u6B21\u306E\u8CEA\u554F\uFF08Enter\uFF09" }) : null : jsx("button", { type: "button", disabled: disabled || (multi ? !draft.checked.length && !draft.custom.trim() : draft.selected === null && !draft.custom.trim()), onClick: () => settle(), children: batch.page < pending.questions.length - 1 ? "\u6B21\u306E\u8CEA\u554F\uFF08Enter\uFF09" : `\u78BA\u5B9A\uFF08${multi ? shortcutPlatform().label + "+" : ""}Enter\uFF09` })
1570
+ ] })
1571
+ ]
1572
+ });
886
1573
  }
887
1574
  function installIntakeStyle() {
888
- if (typeof document === 'undefined')
889
- return undefined;
890
- const style = document.createElement('style');
891
- style.textContent = `
1575
+ if (typeof document === "undefined")
1576
+ return void 0;
1577
+ const style = document.createElement("style");
1578
+ style.textContent = `
892
1579
  .kiokuko-numbered-picker{min-width:0;position:relative}
893
1580
  .kiokuko-numbered-options{border:1px solid var(--dsw-alias-border-l4,#bbb);padding:8px;border-radius:8px;max-height:50dvh;overflow:auto;min-width:0}
894
1581
  .kiokuko-numbered-options[hidden]{display:none}
@@ -915,627 +1602,805 @@ window.__ModuleLoader__.load({
915
1602
  .kiokuko-intake footer [role=status]{flex:1;min-width:0;font-size:12px;overflow-wrap:anywhere}
916
1603
  @media(max-width:640px){.kiokuko-intake{position:fixed;inset:auto 8px 8px;width:auto;max-width:none;max-height:min(480px,75dvh);z-index:10}}
917
1604
  `;
918
- document.head.appendChild(style);
919
- return () => style.remove();
1605
+ document.head.appendChild(style);
1606
+ return () => style.remove();
920
1607
  }
921
- const REVIEW_NS = 'kiokuko-session-log-download';
922
- const REVIEW_KIND = 'kiokuko-diff-review';
923
- const REVIEW_TAB_ID = 'kiokuko-dsh/diff-review';
924
- const REVIEW_ERRORS = {
925
- repository_changed: '差分の取得中または取得後に変更がありました。取得し直してください。',
926
- file_changed: '現在のファイルは取得済みの差分と一致しません。差分内の内容を確認してください。',
927
- file_link_unavailable: 'この差分から現在のファイルへは移動できません。',
928
- turn_snapshot_unavailable: 'このターンの差分記録は利用できません。現在の差分を選んで取得してください。',
929
- repo_unavailable: 'このセッションの Git リポジトリを確認できません。',
930
- session_unavailable: '選択中のセッションを確認できません。開き直してください。',
931
- session_workspace_mismatch: 'セッションと作業場所が一致しません。',
932
- diff_too_large: '差分が上限を超えました。対象を絞って取得してください。',
933
- git_timeout: 'Git の読み取りが時間内に終わりませんでした。',
934
- git_unavailable: 'Git の読み取りサービスを利用できません。',
935
- model_unavailable: '選択したモデルを利用できません。モデルの設定を確認してください。',
936
- analysis_in_progress: 'このセッションでは分析が進行中です。完了または停止後に再試行してください。',
937
- capture_in_progress: '差分を取得中です。完了後に再試行してください。',
938
- review_expired: '結果の保存期間が過ぎました。差分を取得し直してください。',
939
- unmerged_conflict: '競合中のファイルがあります。解決してから分析してください。',
940
- unsafe_review_input: 'レビューの目的に秘密情報らしい内容があります。削除して再試行してください。',
941
- analysis_timeout: '分析が時間内に終わりませんでした。取得済みの差分は表示しています。',
942
- provider_failure: 'モデルへの接続に失敗しました。取得済みの差分は表示しています。',
943
- invalid_model_json: 'モデルの出力を読み取れませんでした。取得済みの差分は表示しています。',
944
- invalid_model_schema: 'モデルの出力形式が一致しません。取得済みの差分は表示しています。',
945
- model_incomplete: 'モデルの回答が完了しませんでした。取得済みの差分は表示しています。',
946
- invalid_evidence_reference: '存在しない根拠を参照する説明を除外しました。',
947
- context_limit: '文脈が入力上限を超え、分析できませんでした。',
948
- chunk_or_hunk_limit: '入力上限により一部の変更を分析していません。',
949
- cancelled: '分析を停止しました。取得済みの差分は表示しています。',
1608
+ var REVIEW_NS = "kiokuko-session-log-download";
1609
+ var REVIEW_KIND = "kiokuko-diff-review";
1610
+ var REVIEW_TAB_ID = "kiokuko-dsh/diff-review";
1611
+ var REVIEW_ERRORS = {
1612
+ repository_changed: "\u5DEE\u5206\u306E\u53D6\u5F97\u4E2D\u307E\u305F\u306F\u53D6\u5F97\u5F8C\u306B\u5909\u66F4\u304C\u3042\u308A\u307E\u3057\u305F\u3002\u53D6\u5F97\u3057\u76F4\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
1613
+ file_changed: "\u73FE\u5728\u306E\u30D5\u30A1\u30A4\u30EB\u306F\u53D6\u5F97\u6E08\u307F\u306E\u5DEE\u5206\u3068\u4E00\u81F4\u3057\u307E\u305B\u3093\u3002\u5DEE\u5206\u5185\u306E\u5185\u5BB9\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
1614
+ file_link_unavailable: "\u3053\u306E\u5DEE\u5206\u304B\u3089\u73FE\u5728\u306E\u30D5\u30A1\u30A4\u30EB\u3078\u306F\u79FB\u52D5\u3067\u304D\u307E\u305B\u3093\u3002",
1615
+ turn_snapshot_unavailable: "\u3053\u306E\u30BF\u30FC\u30F3\u306E\u5DEE\u5206\u8A18\u9332\u306F\u5229\u7528\u3067\u304D\u307E\u305B\u3093\u3002\u73FE\u5728\u306E\u5DEE\u5206\u3092\u9078\u3093\u3067\u53D6\u5F97\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
1616
+ repo_unavailable: "\u3053\u306E\u30BB\u30C3\u30B7\u30E7\u30F3\u306E Git \u30EA\u30DD\u30B8\u30C8\u30EA\u3092\u78BA\u8A8D\u3067\u304D\u307E\u305B\u3093\u3002",
1617
+ session_unavailable: "\u9078\u629E\u4E2D\u306E\u30BB\u30C3\u30B7\u30E7\u30F3\u3092\u78BA\u8A8D\u3067\u304D\u307E\u305B\u3093\u3002\u958B\u304D\u76F4\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
1618
+ session_workspace_mismatch: "\u30BB\u30C3\u30B7\u30E7\u30F3\u3068\u4F5C\u696D\u5834\u6240\u304C\u4E00\u81F4\u3057\u307E\u305B\u3093\u3002",
1619
+ diff_too_large: "\u5DEE\u5206\u304C\u4E0A\u9650\u3092\u8D85\u3048\u307E\u3057\u305F\u3002\u5BFE\u8C61\u3092\u7D5E\u3063\u3066\u53D6\u5F97\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
1620
+ git_timeout: "Git \u306E\u8AAD\u307F\u53D6\u308A\u304C\u6642\u9593\u5185\u306B\u7D42\u308F\u308A\u307E\u305B\u3093\u3067\u3057\u305F\u3002",
1621
+ git_unavailable: "Git \u306E\u8AAD\u307F\u53D6\u308A\u30B5\u30FC\u30D3\u30B9\u3092\u5229\u7528\u3067\u304D\u307E\u305B\u3093\u3002",
1622
+ model_unavailable: "\u9078\u629E\u3057\u305F\u30E2\u30C7\u30EB\u3092\u5229\u7528\u3067\u304D\u307E\u305B\u3093\u3002\u30E2\u30C7\u30EB\u306E\u8A2D\u5B9A\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
1623
+ analysis_in_progress: "\u3053\u306E\u30BB\u30C3\u30B7\u30E7\u30F3\u3067\u306F\u5206\u6790\u304C\u9032\u884C\u4E2D\u3067\u3059\u3002\u5B8C\u4E86\u307E\u305F\u306F\u505C\u6B62\u5F8C\u306B\u518D\u8A66\u884C\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
1624
+ capture_in_progress: "\u5DEE\u5206\u3092\u53D6\u5F97\u4E2D\u3067\u3059\u3002\u5B8C\u4E86\u5F8C\u306B\u518D\u8A66\u884C\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
1625
+ review_expired: "\u7D50\u679C\u306E\u4FDD\u5B58\u671F\u9593\u304C\u904E\u304E\u307E\u3057\u305F\u3002\u5DEE\u5206\u3092\u53D6\u5F97\u3057\u76F4\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
1626
+ unmerged_conflict: "\u7AF6\u5408\u4E2D\u306E\u30D5\u30A1\u30A4\u30EB\u304C\u3042\u308A\u307E\u3059\u3002\u89E3\u6C7A\u3057\u3066\u304B\u3089\u5206\u6790\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
1627
+ unsafe_review_input: "\u30EC\u30D3\u30E5\u30FC\u306E\u76EE\u7684\u306B\u79D8\u5BC6\u60C5\u5831\u3089\u3057\u3044\u5185\u5BB9\u304C\u3042\u308A\u307E\u3059\u3002\u524A\u9664\u3057\u3066\u518D\u8A66\u884C\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
1628
+ analysis_timeout: "\u5206\u6790\u304C\u6642\u9593\u5185\u306B\u7D42\u308F\u308A\u307E\u305B\u3093\u3067\u3057\u305F\u3002\u53D6\u5F97\u6E08\u307F\u306E\u5DEE\u5206\u306F\u8868\u793A\u3057\u3066\u3044\u307E\u3059\u3002",
1629
+ provider_failure: "\u30E2\u30C7\u30EB\u3078\u306E\u63A5\u7D9A\u306B\u5931\u6557\u3057\u307E\u3057\u305F\u3002\u53D6\u5F97\u6E08\u307F\u306E\u5DEE\u5206\u306F\u8868\u793A\u3057\u3066\u3044\u307E\u3059\u3002",
1630
+ invalid_model_json: "\u30E2\u30C7\u30EB\u306E\u51FA\u529B\u3092\u8AAD\u307F\u53D6\u308C\u307E\u305B\u3093\u3067\u3057\u305F\u3002\u53D6\u5F97\u6E08\u307F\u306E\u5DEE\u5206\u306F\u8868\u793A\u3057\u3066\u3044\u307E\u3059\u3002",
1631
+ invalid_model_schema: "\u30E2\u30C7\u30EB\u306E\u51FA\u529B\u5F62\u5F0F\u304C\u4E00\u81F4\u3057\u307E\u305B\u3093\u3002\u53D6\u5F97\u6E08\u307F\u306E\u5DEE\u5206\u306F\u8868\u793A\u3057\u3066\u3044\u307E\u3059\u3002",
1632
+ model_incomplete: "\u30E2\u30C7\u30EB\u306E\u56DE\u7B54\u304C\u5B8C\u4E86\u3057\u307E\u305B\u3093\u3067\u3057\u305F\u3002\u53D6\u5F97\u6E08\u307F\u306E\u5DEE\u5206\u306F\u8868\u793A\u3057\u3066\u3044\u307E\u3059\u3002",
1633
+ invalid_evidence_reference: "\u5B58\u5728\u3057\u306A\u3044\u6839\u62E0\u3092\u53C2\u7167\u3059\u308B\u8AAC\u660E\u3092\u9664\u5916\u3057\u307E\u3057\u305F\u3002",
1634
+ context_limit: "\u6587\u8108\u304C\u5165\u529B\u4E0A\u9650\u3092\u8D85\u3048\u3001\u5206\u6790\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F\u3002",
1635
+ chunk_or_hunk_limit: "\u5165\u529B\u4E0A\u9650\u306B\u3088\u308A\u4E00\u90E8\u306E\u5909\u66F4\u3092\u5206\u6790\u3057\u3066\u3044\u307E\u305B\u3093\u3002",
1636
+ cancelled: "\u5206\u6790\u3092\u505C\u6B62\u3057\u307E\u3057\u305F\u3002\u53D6\u5F97\u6E08\u307F\u306E\u5DEE\u5206\u306F\u8868\u793A\u3057\u3066\u3044\u307E\u3059\u3002"
950
1637
  };
951
- function reviewErrorMessage(code) { return REVIEW_ERRORS[code] ?? `レビューを続行できません(${code})。`; }
952
- const REVIEW_STATES = { 'facts-only': '差分のみ', analyzing: '分析中', analyzed: '分析完了', partial: '一部未分析', cancelled: '停止', failed: '分析失敗' };
953
- const REVIEW_CONTEXT_SOURCES = { 'current-run': '実行中のタスク', 'completed-run': '完了済みタスク', 'review-input': '利用者入力', unavailable: '取得不可' };
954
- const REVIEW_MEMORY_STATES = { available: '取得済み', empty: '該当なし', unavailable: '取得不可', withheld: '保留', mismatch: '不一致' };
955
- const REVIEW_FRESHNESS = { current: '現在と一致', stale: '取得後に変更あり', unknown: '現在との一致は未確認' };
1638
+ function reviewErrorMessage(code) {
1639
+ return REVIEW_ERRORS[code] ?? `\u30EC\u30D3\u30E5\u30FC\u3092\u7D9A\u884C\u3067\u304D\u307E\u305B\u3093\uFF08${code}\uFF09\u3002`;
1640
+ }
1641
+ var REVIEW_STATES = { "facts-only": "\u5DEE\u5206\u306E\u307F", analyzing: "\u5206\u6790\u4E2D", analyzed: "\u5206\u6790\u5B8C\u4E86", partial: "\u4E00\u90E8\u672A\u5206\u6790", cancelled: "\u505C\u6B62", failed: "\u5206\u6790\u5931\u6557" };
1642
+ var REVIEW_CONTEXT_SOURCES = { "current-run": "\u5B9F\u884C\u4E2D\u306E\u30BF\u30B9\u30AF", "completed-run": "\u5B8C\u4E86\u6E08\u307F\u30BF\u30B9\u30AF", "review-input": "\u5229\u7528\u8005\u5165\u529B", unavailable: "\u53D6\u5F97\u4E0D\u53EF" };
1643
+ var REVIEW_MEMORY_STATES = { available: "\u53D6\u5F97\u6E08\u307F", empty: "\u8A72\u5F53\u306A\u3057", unavailable: "\u53D6\u5F97\u4E0D\u53EF", withheld: "\u4FDD\u7559", mismatch: "\u4E0D\u4E00\u81F4" };
1644
+ var REVIEW_FRESHNESS = { current: "\u73FE\u5728\u3068\u4E00\u81F4", stale: "\u53D6\u5F97\u5F8C\u306B\u5909\u66F4\u3042\u308A", unknown: "\u73FE\u5728\u3068\u306E\u4E00\u81F4\u306F\u672A\u78BA\u8A8D" };
956
1645
  function initialReviewSession() {
957
- return { loading: false, busy: false, cancelling: false, error: '', availability: 'unknown', modelAvailability: 'unknown', models: [], untracked: [], turns: [], mode: 'current',
958
- selected: [], selectedUntracked: [], modelKey: '', purpose: '', runId: '', activeFileId: '', fileListOpen: true };
1646
+ return {
1647
+ loading: false,
1648
+ busy: false,
1649
+ cancelling: false,
1650
+ error: "",
1651
+ availability: "unknown",
1652
+ modelAvailability: "unknown",
1653
+ models: [],
1654
+ untracked: [],
1655
+ turns: [],
1656
+ mode: "current",
1657
+ selected: [],
1658
+ selectedUntracked: [],
1659
+ modelKey: "",
1660
+ purpose: "",
1661
+ runId: "",
1662
+ activeFileId: "",
1663
+ fileListOpen: true
1664
+ };
959
1665
  }
960
1666
  function matchesReviewMode(review, mode, turnSeq) {
961
- return review?.snapshot.mode === mode && (mode !== 'turn' || review.snapshot.turnSeq === turnSeq);
1667
+ return review?.snapshot.mode === mode && (mode !== "turn" || review.snapshot.turnSeq === turnSeq);
962
1668
  }
963
- class DiffReviewClientController {
964
- store = createSnapshotStore({ bySession: {} });
965
- timers = new Map();
966
- generations = new Map();
967
- closed = false;
968
- nextGeneration(sessionId) {
969
- const next = (this.generations.get(sessionId) ?? 0) + 1;
970
- this.generations.set(sessionId, next);
971
- return next;
1669
+ var DiffReviewClientController = class {
1670
+ store = createSnapshotStore({ bySession: {} });
1671
+ timers = /* @__PURE__ */ new Map();
1672
+ generations = /* @__PURE__ */ new Map();
1673
+ closed = false;
1674
+ nextGeneration(sessionId) {
1675
+ const next = (this.generations.get(sessionId) ?? 0) + 1;
1676
+ this.generations.set(sessionId, next);
1677
+ return next;
1678
+ }
1679
+ current(sessionId) {
1680
+ return this.store.getSnapshot().bySession[sessionId] ?? initialReviewSession();
1681
+ }
1682
+ publish(sessionId, next) {
1683
+ if (this.closed)
1684
+ return;
1685
+ this.store.update((state) => {
1686
+ state.bySession = { ...state.bySession, [sessionId]: next };
1687
+ });
1688
+ }
1689
+ change(sessionId, changes) {
1690
+ this.publish(sessionId, { ...this.current(sessionId), ...changes });
1691
+ }
1692
+ url(sessionId, reviewId) {
1693
+ const url = new URL("/api/kiokuko.diff-review", hostBase());
1694
+ url.searchParams.set("sessionId", sessionId);
1695
+ if (reviewId)
1696
+ url.searchParams.set("reviewId", reviewId);
1697
+ return url;
1698
+ }
1699
+ async json(url, body) {
1700
+ let response;
1701
+ try {
1702
+ response = await fetch(url, body ? { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(body) } : {});
1703
+ } catch {
1704
+ throw new Error("DSH \u306B\u63A5\u7D9A\u3067\u304D\u307E\u305B\u3093\u3002\u63A5\u7D9A\u3092\u78BA\u8A8D\u3057\u3066\u518D\u8A66\u884C\u3057\u3066\u304F\u3060\u3055\u3044\u3002");
972
1705
  }
973
- current(sessionId) { return this.store.getSnapshot().bySession[sessionId] ?? initialReviewSession(); }
974
- publish(sessionId, next) {
975
- if (this.closed)
976
- return;
977
- this.store.update(state => { state.bySession = { ...state.bySession, [sessionId]: next }; });
1706
+ let value;
1707
+ try {
1708
+ value = await response.json();
1709
+ } catch {
1710
+ throw new Error("DSH \u306E\u5FDC\u7B54\u3092\u8AAD\u307F\u53D6\u308C\u307E\u305B\u3093\u3002\u518D\u8A66\u884C\u3057\u3066\u304F\u3060\u3055\u3044\u3002");
978
1711
  }
979
- change(sessionId, changes) { this.publish(sessionId, { ...this.current(sessionId), ...changes }); }
980
- url(sessionId, reviewId) {
981
- const url = new URL('/api/kiokuko.diff-review', hostBase());
982
- url.searchParams.set('sessionId', sessionId);
983
- if (reviewId)
984
- url.searchParams.set('reviewId', reviewId);
985
- return url;
1712
+ if (!response.ok)
1713
+ throw new Error(typeof value.code === "string" ? reviewErrorMessage(value.code) : `HTTP ${response.status}`);
1714
+ return value;
1715
+ }
1716
+ async load(sessionId) {
1717
+ const generation = this.nextGeneration(sessionId);
1718
+ this.change(sessionId, { loading: true, error: "" });
1719
+ try {
1720
+ const response = await this.json(this.url(sessionId));
1721
+ if (this.generations.get(sessionId) !== generation)
1722
+ return;
1723
+ const review = response.review;
1724
+ const models = response.models ?? [];
1725
+ const modelKeys = models.map((item) => JSON.stringify([item.provider, item.model]));
1726
+ const current = this.current(sessionId);
1727
+ const untracked = response.untracked ?? [];
1728
+ this.change(sessionId, {
1729
+ loading: false,
1730
+ busy: review?.state === "analyzing",
1731
+ cancelling: false,
1732
+ availability: String(response.availability),
1733
+ modelAvailability: String(response.modelAvailability ?? "unknown"),
1734
+ models,
1735
+ review,
1736
+ untracked,
1737
+ selectedUntracked: current.selectedUntracked.filter((path) => untracked.includes(path)),
1738
+ turns: response.turns ?? [],
1739
+ ...review ? {
1740
+ selected: current.review?.reviewId === review.reviewId ? current.selected.filter((id) => review.snapshot.files.some((file) => file.fileId === id && file.kind === "text")) : review.snapshot.files.filter((file) => file.kind === "text").map((file) => file.fileId),
1741
+ activeFileId: current.review?.reviewId === review.reviewId && review.snapshot.files.some((file) => file.fileId === current.activeFileId) ? current.activeFileId : review.snapshot.files[0]?.fileId || ""
1742
+ } : {},
1743
+ modelKey: modelKeys.includes(current.modelKey) ? current.modelKey : "",
1744
+ error: !review && current.review ? "\u4FDD\u5B58\u671F\u9593\u304C\u904E\u304E\u307E\u3057\u305F\u3002\u5DEE\u5206\u3092\u53D6\u5F97\u3057\u76F4\u3057\u3066\u304F\u3060\u3055\u3044\u3002" : ""
1745
+ });
1746
+ if (review?.state === "analyzing")
1747
+ this.poll(sessionId, review.reviewId);
1748
+ } catch (error) {
1749
+ if (this.generations.get(sessionId) === generation)
1750
+ this.change(sessionId, { loading: false, busy: false, error: messageOf2(error) });
986
1751
  }
987
- async json(url, body) {
988
- let response;
989
- try {
990
- response = await fetch(url, body ? { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(body) } : {});
991
- }
992
- catch {
993
- throw new Error('DSH に接続できません。接続を確認して再試行してください。');
994
- }
995
- let value;
996
- try {
997
- value = await response.json();
998
- }
999
- catch {
1000
- throw new Error('DSH の応答を読み取れません。再試行してください。');
1001
- }
1002
- if (!response.ok)
1003
- throw new Error(typeof value.code === 'string' ? reviewErrorMessage(value.code) : `HTTP ${response.status}`);
1004
- return value;
1752
+ }
1753
+ async capture(sessionId) {
1754
+ const current = this.current(sessionId);
1755
+ if (current.busy || current.loading || current.cancelling || current.review?.state === "analyzing")
1756
+ return;
1757
+ const generation = this.nextGeneration(sessionId);
1758
+ this.change(sessionId, { busy: true, error: "" });
1759
+ try {
1760
+ const review = await this.json(this.url(sessionId), {
1761
+ action: "capture",
1762
+ sessionId,
1763
+ mode: current.mode,
1764
+ ...current.mode === "turn" ? { turnSeq: current.turnSeq } : {},
1765
+ untracked: current.selectedUntracked,
1766
+ requestId: crypto.randomUUID()
1767
+ });
1768
+ if (this.generations.get(sessionId) !== generation)
1769
+ return;
1770
+ this.change(sessionId, {
1771
+ loading: false,
1772
+ busy: false,
1773
+ review,
1774
+ selected: review.snapshot.files.filter((file) => file.kind === "text").map((file) => file.fileId),
1775
+ activeFileId: review.snapshot.files[0]?.fileId ?? "",
1776
+ fileListOpen: false
1777
+ });
1778
+ await this.load(sessionId);
1779
+ } catch (error) {
1780
+ if (this.generations.get(sessionId) === generation)
1781
+ this.change(sessionId, { loading: false, busy: false, error: messageOf2(error) });
1005
1782
  }
1006
- async load(sessionId) {
1007
- const generation = this.nextGeneration(sessionId);
1008
- this.change(sessionId, { loading: true, error: '' });
1009
- try {
1010
- const response = await this.json(this.url(sessionId));
1011
- if (this.generations.get(sessionId) !== generation)
1012
- return;
1013
- const review = response.review;
1014
- const models = response.models ?? [];
1015
- const modelKeys = models.map(item => JSON.stringify([item.provider, item.model]));
1016
- const current = this.current(sessionId);
1017
- const untracked = response.untracked ?? [];
1018
- this.change(sessionId, { loading: false, busy: review?.state === 'analyzing', cancelling: false,
1019
- availability: String(response.availability), modelAvailability: String(response.modelAvailability ?? 'unknown'), models, review,
1020
- untracked, selectedUntracked: current.selectedUntracked.filter(path => untracked.includes(path)), turns: response.turns ?? [],
1021
- ...(review ? { selected: current.review?.reviewId === review.reviewId ? current.selected.filter(id => review.snapshot.files.some(file => file.fileId === id && file.kind === 'text')) : review.snapshot.files.filter(file => file.kind === 'text').map(file => file.fileId),
1022
- activeFileId: current.review?.reviewId === review.reviewId && review.snapshot.files.some(file => file.fileId === current.activeFileId) ? current.activeFileId : review.snapshot.files[0]?.fileId || '' } : {}),
1023
- modelKey: modelKeys.includes(current.modelKey) ? current.modelKey : '',
1024
- error: !review && current.review ? '保存期間が過ぎました。差分を取得し直してください。' : '' });
1025
- if (review?.state === 'analyzing')
1026
- this.poll(sessionId, review.reviewId);
1027
- }
1028
- catch (error) {
1029
- if (this.generations.get(sessionId) === generation)
1030
- this.change(sessionId, { loading: false, busy: false, error: messageOf(error) });
1031
- }
1783
+ }
1784
+ async analyze(sessionId) {
1785
+ const current = this.current(sessionId);
1786
+ if (current.busy || current.loading || current.cancelling || !current.review || !matchesReviewMode(current.review, current.mode, current.turnSeq) || current.review.state === "analyzing" || !current.selected.length || !current.modelKey)
1787
+ return;
1788
+ let provider, model;
1789
+ try {
1790
+ [provider, model] = JSON.parse(current.modelKey);
1791
+ } catch {
1792
+ return;
1032
1793
  }
1033
- async capture(sessionId) {
1034
- const current = this.current(sessionId);
1035
- if (current.busy || current.loading || current.cancelling || current.review?.state === 'analyzing')
1036
- return;
1037
- const generation = this.nextGeneration(sessionId);
1038
- this.change(sessionId, { busy: true, error: '' });
1039
- try {
1040
- const review = await this.json(this.url(sessionId), { action: 'capture', sessionId, mode: current.mode,
1041
- ...(current.mode === 'turn' ? { turnSeq: current.turnSeq } : {}), untracked: current.selectedUntracked, requestId: crypto.randomUUID() });
1042
- if (this.generations.get(sessionId) !== generation)
1043
- return;
1044
- this.change(sessionId, { loading: false, busy: false, review, selected: review.snapshot.files.filter(file => file.kind === 'text').map(file => file.fileId),
1045
- activeFileId: review.snapshot.files[0]?.fileId ?? '', fileListOpen: false });
1046
- await this.load(sessionId);
1047
- }
1048
- catch (error) {
1049
- if (this.generations.get(sessionId) === generation)
1050
- this.change(sessionId, { loading: false, busy: false, error: messageOf(error) });
1051
- }
1794
+ if (!provider || !model)
1795
+ return;
1796
+ const generation = this.nextGeneration(sessionId);
1797
+ this.change(sessionId, { busy: true, error: "" });
1798
+ try {
1799
+ const review = await this.json(this.url(sessionId), {
1800
+ action: "analyze",
1801
+ sessionId,
1802
+ reviewId: current.review.reviewId,
1803
+ requestId: crypto.randomUUID(),
1804
+ selectedFileIds: current.selected,
1805
+ provider,
1806
+ model,
1807
+ ...current.purpose.trim() ? { purpose: current.purpose.trim() } : {},
1808
+ ...current.runId ? { runId: current.runId } : {}
1809
+ });
1810
+ if (this.generations.get(sessionId) !== generation)
1811
+ return;
1812
+ this.change(sessionId, { loading: false, review });
1813
+ this.poll(sessionId, review.reviewId);
1814
+ } catch (error) {
1815
+ if (this.generations.get(sessionId) === generation)
1816
+ this.change(sessionId, { busy: false, error: messageOf2(error) });
1052
1817
  }
1053
- async analyze(sessionId) {
1054
- const current = this.current(sessionId);
1055
- if (current.busy || current.loading || current.cancelling || !current.review || !matchesReviewMode(current.review, current.mode, current.turnSeq) || current.review.state === 'analyzing' || !current.selected.length || !current.modelKey)
1056
- return;
1057
- let provider, model;
1058
- try {
1059
- [provider, model] = JSON.parse(current.modelKey);
1060
- }
1061
- catch {
1062
- return;
1063
- }
1064
- if (!provider || !model)
1065
- return;
1066
- const generation = this.nextGeneration(sessionId);
1067
- this.change(sessionId, { busy: true, error: '' });
1068
- try {
1069
- const review = await this.json(this.url(sessionId), { action: 'analyze', sessionId, reviewId: current.review.reviewId,
1070
- requestId: crypto.randomUUID(), selectedFileIds: current.selected, provider, model,
1071
- ...(current.purpose.trim() ? { purpose: current.purpose.trim() } : {}), ...(current.runId ? { runId: current.runId } : {}) });
1072
- if (this.generations.get(sessionId) !== generation)
1073
- return;
1074
- this.change(sessionId, { loading: false, review });
1075
- this.poll(sessionId, review.reviewId);
1076
- }
1077
- catch (error) {
1078
- if (this.generations.get(sessionId) === generation)
1079
- this.change(sessionId, { busy: false, error: messageOf(error) });
1818
+ }
1819
+ poll(sessionId, reviewId) {
1820
+ clearTimeout(this.timers.get(sessionId));
1821
+ const generation = this.generations.get(sessionId);
1822
+ const tick = async () => {
1823
+ try {
1824
+ const review = await this.json(this.url(sessionId, reviewId));
1825
+ if (this.generations.get(sessionId) !== generation || this.current(sessionId).review?.reviewId !== reviewId || this.current(sessionId).cancelling)
1826
+ return;
1827
+ this.change(sessionId, { review, busy: review.state === "analyzing", error: "" });
1828
+ if (review.state === "analyzing")
1829
+ this.timers.set(sessionId, setTimeout(() => void tick(), document.hidden ? 5e3 : 1e3));
1830
+ } catch (error) {
1831
+ if (this.generations.get(sessionId) === generation && this.current(sessionId).review?.reviewId === reviewId && !this.current(sessionId).cancelling) {
1832
+ this.change(sessionId, { busy: false, error: messageOf2(error) });
1080
1833
  }
1834
+ }
1835
+ };
1836
+ this.timers.set(sessionId, setTimeout(() => void tick(), 500));
1837
+ }
1838
+ async refreshReview(sessionId, reviewId) {
1839
+ const generation = this.nextGeneration(sessionId);
1840
+ clearTimeout(this.timers.get(sessionId));
1841
+ this.change(sessionId, { loading: true, error: "" });
1842
+ try {
1843
+ const review = await this.json(this.url(sessionId, reviewId));
1844
+ if (this.generations.get(sessionId) === generation && this.current(sessionId).review?.reviewId === reviewId) {
1845
+ this.change(sessionId, { loading: false, busy: review.state === "analyzing", review, error: "" });
1846
+ if (review.state === "analyzing")
1847
+ this.poll(sessionId, reviewId);
1848
+ }
1849
+ } catch (error) {
1850
+ if (this.generations.get(sessionId) === generation && this.current(sessionId).review?.reviewId === reviewId)
1851
+ this.change(sessionId, { loading: false, busy: false, error: messageOf2(error) });
1081
1852
  }
1082
- poll(sessionId, reviewId) {
1083
- clearTimeout(this.timers.get(sessionId));
1084
- const generation = this.generations.get(sessionId);
1085
- const tick = async () => {
1086
- try {
1087
- const review = await this.json(this.url(sessionId, reviewId));
1088
- if (this.generations.get(sessionId) !== generation || this.current(sessionId).review?.reviewId !== reviewId || this.current(sessionId).cancelling)
1089
- return;
1090
- this.change(sessionId, { review, busy: review.state === 'analyzing', error: '' });
1091
- if (review.state === 'analyzing')
1092
- this.timers.set(sessionId, setTimeout(() => void tick(), document.hidden ? 5000 : 1000));
1093
- }
1094
- catch (error) {
1095
- if (this.generations.get(sessionId) === generation && this.current(sessionId).review?.reviewId === reviewId && !this.current(sessionId).cancelling) {
1096
- this.change(sessionId, { busy: false, error: messageOf(error) });
1097
- }
1098
- }
1099
- };
1100
- this.timers.set(sessionId, setTimeout(() => void tick(), 500));
1853
+ }
1854
+ async cancel(sessionId, reviewId) {
1855
+ if (this.current(sessionId).cancelling || this.current(sessionId).review?.reviewId !== reviewId)
1856
+ return;
1857
+ const generation = this.nextGeneration(sessionId);
1858
+ clearTimeout(this.timers.get(sessionId));
1859
+ this.change(sessionId, { loading: false, cancelling: true, error: "" });
1860
+ try {
1861
+ const review = await this.json(this.url(sessionId), { action: "cancel", sessionId, reviewId });
1862
+ if (this.generations.get(sessionId) === generation && this.current(sessionId).review?.reviewId === reviewId)
1863
+ this.change(sessionId, { review, busy: false, cancelling: false, error: "" });
1864
+ } catch (error) {
1865
+ if (this.generations.get(sessionId) === generation && this.current(sessionId).review?.reviewId === reviewId)
1866
+ this.change(sessionId, { busy: false, cancelling: false, error: messageOf2(error) });
1101
1867
  }
1102
- async refreshReview(sessionId, reviewId) {
1103
- const generation = this.nextGeneration(sessionId);
1104
- clearTimeout(this.timers.get(sessionId));
1105
- this.change(sessionId, { loading: true, error: '' });
1106
- try {
1107
- const review = await this.json(this.url(sessionId, reviewId));
1108
- if (this.generations.get(sessionId) === generation && this.current(sessionId).review?.reviewId === reviewId) {
1109
- this.change(sessionId, { loading: false, busy: review.state === 'analyzing', review, error: '' });
1110
- if (review.state === 'analyzing')
1111
- this.poll(sessionId, reviewId);
1112
- }
1113
- }
1114
- catch (error) {
1115
- if (this.generations.get(sessionId) === generation && this.current(sessionId).review?.reviewId === reviewId)
1116
- this.change(sessionId, { loading: false, busy: false, error: messageOf(error) });
1117
- }
1868
+ }
1869
+ exportUrl(sessionId, reviewId, format) {
1870
+ const url = this.url(sessionId, reviewId);
1871
+ url.searchParams.set("format", format);
1872
+ return url.toString();
1873
+ }
1874
+ async openCurrentFile(sessionId, reviewId, fileId, openFile) {
1875
+ try {
1876
+ const url = this.url(sessionId, reviewId);
1877
+ url.searchParams.set("format", "file-link");
1878
+ url.searchParams.set("fileId", fileId);
1879
+ const response = await this.json(url);
1880
+ if (typeof response.address !== "string" || !response.address.startsWith("dsh-resource://file/session/") || !openFile(response.address)) {
1881
+ throw new Error("\u30D5\u30A1\u30A4\u30EB\u30BF\u30D6\u3092\u958B\u3051\u307E\u305B\u3093\u3002\u53D6\u5F97\u6E08\u307F\u306E\u5DEE\u5206\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002");
1882
+ }
1883
+ } catch (error) {
1884
+ this.change(sessionId, { error: messageOf2(error) });
1118
1885
  }
1119
- async cancel(sessionId, reviewId) {
1120
- if (this.current(sessionId).cancelling || this.current(sessionId).review?.reviewId !== reviewId)
1121
- return;
1122
- const generation = this.nextGeneration(sessionId);
1123
- clearTimeout(this.timers.get(sessionId));
1124
- this.change(sessionId, { loading: false, cancelling: true, error: '' });
1125
- try {
1126
- const review = await this.json(this.url(sessionId), { action: 'cancel', sessionId, reviewId });
1127
- if (this.generations.get(sessionId) === generation && this.current(sessionId).review?.reviewId === reviewId)
1128
- this.change(sessionId, { review, busy: false, cancelling: false, error: '' });
1129
- }
1130
- catch (error) {
1131
- if (this.generations.get(sessionId) === generation && this.current(sessionId).review?.reviewId === reviewId)
1132
- this.change(sessionId, { busy: false, cancelling: false, error: messageOf(error) });
1133
- }
1886
+ }
1887
+ async dispose() {
1888
+ this.closed = true;
1889
+ for (const timer of this.timers.values())
1890
+ clearTimeout(timer);
1891
+ this.timers.clear();
1892
+ }
1893
+ };
1894
+ function SnapshotDiff(props) {
1895
+ return jsxs("div", { className: "kiokuko-review-diff", children: props.file.hunks.map((hunk) => {
1896
+ let oldLine = hunk.oldStart, newLine = hunk.newStart;
1897
+ return jsxs("section", { id: `kiokuko-review-${props.file.fileId}-${hunk.id}`, tabIndex: -1, children: [
1898
+ jsx("h4", { children: `@@ -${hunk.oldStart} +${hunk.newStart} @@` }),
1899
+ jsx("pre", { children: hunk.lines.map((line, index) => {
1900
+ const prefix = line[0];
1901
+ const old = prefix === "+" || prefix === "\\" ? "" : String(oldLine++);
1902
+ const next = prefix === "-" || prefix === "\\" ? "" : String(newLine++);
1903
+ return jsx("div", {
1904
+ className: prefix === "+" ? "added" : prefix === "-" ? "removed" : "",
1905
+ tabIndex: -1,
1906
+ "data-snapshot-id": props.snapshotId,
1907
+ "data-file-id": props.file.fileId,
1908
+ "data-hunk-id": hunk.id,
1909
+ ...old ? { "data-old-line": old } : {},
1910
+ ...next ? { "data-new-line": next } : {},
1911
+ children: `${old.padStart(5)} ${next.padStart(5)} ${line}`
1912
+ }, index);
1913
+ }) })
1914
+ ] }, hunk.id);
1915
+ }) });
1916
+ }
1917
+ function NumberedPicker(props) {
1918
+ const [open, setOpen] = useState(false), [index, setIndex] = useState(-1), [ordinal, setOrdinal] = useState("");
1919
+ const buffer = useRef(""), selected = useRef(-1), group = useRef(null), trigger = useRef(null);
1920
+ const shown = props.inline || open;
1921
+ const close = () => {
1922
+ setOpen(false);
1923
+ buffer.current = "";
1924
+ setOrdinal("");
1925
+ trigger.current?.focus();
1926
+ };
1927
+ useEffect(() => {
1928
+ if (shown && !props.inline)
1929
+ group.current?.focus();
1930
+ }, [shown, props.inline]);
1931
+ const pick = (n, confirm) => {
1932
+ const option = props.options[n];
1933
+ if (!option || option.disabled && !props.onToggle)
1934
+ return;
1935
+ selected.current = n;
1936
+ setIndex(n);
1937
+ if (props.inline || confirm)
1938
+ props.onChoose(option.value);
1939
+ if (confirm && !props.inline)
1940
+ close();
1941
+ };
1942
+ const keys = (e) => {
1943
+ if (!shown || props.disabled || composingKey(e) || editingKey(e) || e.altKey || e.shiftKey || e.defaultPrevented)
1944
+ return;
1945
+ if (e.key === "Escape" && !props.inline) {
1946
+ e.preventDefault();
1947
+ e.stopPropagation();
1948
+ close();
1949
+ return;
1134
1950
  }
1135
- exportUrl(sessionId, reviewId, format) {
1136
- const url = this.url(sessionId, reviewId);
1137
- url.searchParams.set('format', format);
1138
- return url.toString();
1951
+ const optionButton = e.target?.closest?.("button[data-kiokuko-option]");
1952
+ if (e.key === " " && props.onToggle && optionButton && !e.ctrlKey && !e.metaKey) {
1953
+ e.preventDefault();
1954
+ e.stopPropagation();
1955
+ const option = props.options[Number(optionButton.dataset.kiokukoOption)];
1956
+ if (option && !option.disabled)
1957
+ props.onToggle(option.value);
1958
+ return;
1139
1959
  }
1140
- async openCurrentFile(sessionId, reviewId, fileId, openFile) {
1141
- try {
1142
- const url = this.url(sessionId, reviewId);
1143
- url.searchParams.set('format', 'file-link');
1144
- url.searchParams.set('fileId', fileId);
1145
- const response = await this.json(url);
1146
- if (typeof response.address !== 'string' || !response.address.startsWith('dsh-resource://file/session/') || !openFile(response.address)) {
1147
- throw new Error('ファイルタブを開けません。取得済みの差分を確認してください。');
1148
- }
1149
- }
1150
- catch (error) {
1151
- this.change(sessionId, { error: messageOf(error) });
1152
- }
1960
+ if ((e.key === "Enter" || e.key === " ") && e.target?.tagName === "BUTTON")
1961
+ return;
1962
+ const modified = modifiedKey(e);
1963
+ if ((e.ctrlKey || e.metaKey) && !modified)
1964
+ return;
1965
+ const digit = /^(?:Digit|Numpad)([0-9])$/u.exec(e.code ?? "")?.[1] ?? e.key;
1966
+ if (/^[0-9]$/u.test(digit) && (!modified || digit !== "0") || !modified && e.key === "Backspace" && buffer.current) {
1967
+ e.preventDefault();
1968
+ e.stopPropagation();
1969
+ buffer.current = modified || props.options.length <= 9 ? digit : e.key === "Backspace" ? buffer.current.slice(0, -1) : (buffer.current + digit).slice(0, 9);
1970
+ setOrdinal(buffer.current);
1971
+ pick(Number(buffer.current) - 1, false);
1972
+ return;
1153
1973
  }
1154
- async dispose() {
1155
- this.closed = true;
1156
- for (const timer of this.timers.values())
1157
- clearTimeout(timer);
1158
- this.timers.clear();
1974
+ if (modified)
1975
+ return;
1976
+ if (e.key === "Enter" || e.key === " " && props.onToggle) {
1977
+ e.preventDefault();
1978
+ e.stopPropagation();
1979
+ const n = buffer.current ? Number(buffer.current) - 1 : selected.current;
1980
+ const item = props.options[n];
1981
+ if (!item)
1982
+ return;
1983
+ if (e.key === " ") {
1984
+ if (!item.disabled)
1985
+ props.onToggle?.(item.value);
1986
+ } else
1987
+ pick(n, true);
1988
+ buffer.current = "";
1989
+ setOrdinal("");
1159
1990
  }
1160
- }
1161
- function SnapshotDiff(props) {
1162
- return jsxs('div', { className: 'kiokuko-review-diff', children: props.file.hunks.map(hunk => {
1163
- let oldLine = hunk.oldStart, newLine = hunk.newStart;
1164
- return jsxs('section', { id: `kiokuko-review-${props.file.fileId}-${hunk.id}`, tabIndex: -1, children: [
1165
- jsx('h4', { children: `@@ -${hunk.oldStart} +${hunk.newStart} @@` }),
1166
- jsx('pre', { children: hunk.lines.map((line, index) => {
1167
- const prefix = line[0];
1168
- const old = prefix === '+' || prefix === '\\' ? '' : String(oldLine++);
1169
- const next = prefix === '-' || prefix === '\\' ? '' : String(newLine++);
1170
- return jsx('div', { className: prefix === '+' ? 'added' : prefix === '-' ? 'removed' : '', tabIndex: -1,
1171
- 'data-snapshot-id': props.snapshotId, 'data-file-id': props.file.fileId, 'data-hunk-id': hunk.id,
1172
- ...(old ? { 'data-old-line': old } : {}), ...(next ? { 'data-new-line': next } : {}),
1173
- children: `${old.padStart(5)} ${next.padStart(5)} ${line}` }, index);
1174
- }) }),
1175
- ] }, hunk.id);
1176
- }) });
1177
- }
1178
- /** A local selection surface: choosing never starts the consumer's operation. */
1179
- function NumberedPicker(props) {
1180
- const [open, setOpen] = useState(false), [index, setIndex] = useState(-1), [ordinal, setOrdinal] = useState('');
1181
- const buffer = useRef(''), selected = useRef(-1), group = useRef(null), trigger = useRef(null);
1182
- const shown = props.inline || open;
1183
- const close = () => { setOpen(false); buffer.current = ''; setOrdinal(''); trigger.current?.focus(); };
1184
- useEffect(() => { if (shown && !props.inline)
1185
- group.current?.focus(); }, [shown, props.inline]);
1186
- const pick = (n, confirm) => {
1187
- const option = props.options[n];
1188
- if (!option || option.disabled && !props.onToggle)
1189
- return;
1190
- selected.current = n;
1191
- setIndex(n);
1192
- if (props.inline || confirm)
1193
- props.onChoose(option.value);
1194
- if (confirm && !props.inline)
1195
- close();
1196
- };
1197
- const keys = (e) => {
1198
- if (!shown || props.disabled || composingKey(e) || editingKey(e) || e.altKey || e.shiftKey || e.defaultPrevented)
1199
- return;
1200
- if (e.key === 'Escape' && !props.inline) {
1201
- e.preventDefault();
1202
- e.stopPropagation();
1203
- close();
1204
- return;
1205
- }
1206
- const optionButton = e.target?.closest?.('button[data-kiokuko-option]');
1207
- if (e.key === ' ' && props.onToggle && optionButton && !e.ctrlKey && !e.metaKey) {
1208
- e.preventDefault();
1209
- e.stopPropagation();
1210
- const option = props.options[Number(optionButton.dataset.kiokukoOption)];
1211
- if (option && !option.disabled)
1212
- props.onToggle(option.value);
1213
- return;
1214
- }
1215
- if ((e.key === 'Enter' || e.key === ' ') && e.target?.tagName === 'BUTTON')
1216
- return;
1217
- const modified = modifiedKey(e);
1218
- if ((e.ctrlKey || e.metaKey) && !modified)
1219
- return;
1220
- const digit = /^(?:Digit|Numpad)([0-9])$/u.exec(e.code ?? '')?.[1] ?? e.key;
1221
- if (/^[0-9]$/u.test(digit) && (!modified || digit !== '0') || !modified && e.key === 'Backspace' && buffer.current) {
1222
- e.preventDefault();
1223
- e.stopPropagation();
1224
- buffer.current = modified || props.options.length <= 9 ? digit : e.key === 'Backspace' ? buffer.current.slice(0, -1) : (buffer.current + digit).slice(0, 9);
1225
- setOrdinal(buffer.current);
1226
- pick(Number(buffer.current) - 1, false);
1227
- return;
1228
- }
1229
- if (modified)
1230
- return;
1231
- if (e.key === 'Enter' || e.key === ' ' && props.onToggle) {
1232
- e.preventDefault();
1233
- e.stopPropagation();
1234
- const n = buffer.current ? Number(buffer.current) - 1 : selected.current;
1235
- const item = props.options[n];
1236
- if (!item)
1237
- return;
1238
- if (e.key === ' ') {
1239
- if (!item.disabled)
1240
- props.onToggle?.(item.value);
1241
- }
1242
- else
1243
- pick(n, true);
1244
- buffer.current = '';
1245
- setOrdinal('');
1246
- }
1247
- };
1248
- useShortcutGroup(group, keys);
1249
- // Keep the group mounted so a popup opened after its parent mounts acquires
1250
- // the same document listener and relinquishes ownership when hidden.
1251
- return jsxs('div', { className: 'kiokuko-numbered-picker', children: [
1252
- !props.inline ? jsx('button', { type: 'button', ref: trigger, disabled: props.disabled, 'aria-haspopup': 'dialog', 'aria-expanded': open,
1253
- onClick: () => setOpen(!open), children: `${props.label}: ${props.options.find(o => o.value === props.value)?.label ?? '選択'}` }) : null,
1254
- jsxs('section', { hidden: !shown, tabIndex: 0, ref: (el) => { group.current = el; },
1255
- 'data-kiokuko-shortcuts': '', role: props.inline ? 'group' : 'dialog', 'aria-label': props.label, className: 'kiokuko-numbered-options', onKeyDown: keys,
1256
- children: [jsx('p', { children: `${props.label} — 数字 / ${shortcutPlatform().label}+1〜9で選択。${props.inline ? 'Spaceでチェック切替。' : 'Enterで確定、Escで閉じる。'}10以上は連続入力、Backspaceで訂正。` }),
1257
- jsx('p', { role: 'status', children: ordinal ? `入力番号: ${ordinal}${Number(ordinal) < 1 || Number(ordinal) > props.options.length ? '(範囲外)' : ''}` : '' }),
1258
- ...props.options.map((o, n) => jsxs('div', { className: 'kiokuko-review-file-row', children: [
1259
- props.onToggle ? jsx('input', { type: 'checkbox', checked: props.checked?.includes(o.value) ?? false, disabled: props.disabled || o.disabled,
1260
- 'aria-label': `${o.label} を選択対象にする`, onChange: () => props.onToggle?.(o.value) }) : null,
1261
- jsxs('button', { type: 'button', 'data-kiokuko-option': n, disabled: props.disabled || !props.onToggle && o.disabled, 'aria-pressed': (index < 0 ? props.value === o.value : index === n), ...shortcutAria(n, props.options.length),
1262
- onClick: () => { buffer.current = ''; setOrdinal(''); pick(n, !props.inline); },
1263
- children: [jsx('span', { children: `${n + 1}. ${o.label}` }), jsx('kbd', { children: shortcutLabel(n, !!props.onToggle) })] }),
1264
- ] }, o.value)),
1265
- !props.inline ? jsx('button', { type: 'button', onClick: close, children: '閉じる(Esc)' }) : null,
1266
- ] }),
1267
- ] });
1991
+ };
1992
+ useShortcutGroup(group, keys);
1993
+ return jsxs("div", { className: "kiokuko-numbered-picker", children: [
1994
+ !props.inline ? jsx("button", {
1995
+ type: "button",
1996
+ ref: trigger,
1997
+ disabled: props.disabled,
1998
+ "aria-haspopup": "dialog",
1999
+ "aria-expanded": open,
2000
+ onClick: () => setOpen(!open),
2001
+ children: `${props.label}: ${props.options.find((o) => o.value === props.value)?.label ?? "\u9078\u629E"}`
2002
+ }) : null,
2003
+ jsxs("section", {
2004
+ hidden: !shown,
2005
+ tabIndex: 0,
2006
+ ref: (el) => {
2007
+ group.current = el;
2008
+ },
2009
+ "data-kiokuko-shortcuts": "",
2010
+ role: props.inline ? "group" : "dialog",
2011
+ "aria-label": props.label,
2012
+ className: "kiokuko-numbered-options",
2013
+ onKeyDown: keys,
2014
+ children: [
2015
+ jsx("p", { children: `${props.label} \u2014 \u6570\u5B57 / ${shortcutPlatform().label}+1\u301C9\u3067\u9078\u629E\u3002${props.inline ? "Space\u3067\u30C1\u30A7\u30C3\u30AF\u5207\u66FF\u3002" : "Enter\u3067\u78BA\u5B9A\u3001Esc\u3067\u9589\u3058\u308B\u3002"}10\u4EE5\u4E0A\u306F\u9023\u7D9A\u5165\u529B\u3001Backspace\u3067\u8A02\u6B63\u3002` }),
2016
+ jsx("p", { role: "status", children: ordinal ? `\u5165\u529B\u756A\u53F7: ${ordinal}${Number(ordinal) < 1 || Number(ordinal) > props.options.length ? "\uFF08\u7BC4\u56F2\u5916\uFF09" : ""}` : "" }),
2017
+ ...props.options.map((o, n) => jsxs("div", { className: "kiokuko-review-file-row", children: [
2018
+ props.onToggle ? jsx("input", {
2019
+ type: "checkbox",
2020
+ checked: props.checked?.includes(o.value) ?? false,
2021
+ disabled: props.disabled || o.disabled,
2022
+ "aria-label": `${o.label} \u3092\u9078\u629E\u5BFE\u8C61\u306B\u3059\u308B`,
2023
+ onChange: () => props.onToggle?.(o.value)
2024
+ }) : null,
2025
+ jsxs("button", {
2026
+ type: "button",
2027
+ "data-kiokuko-option": n,
2028
+ disabled: props.disabled || !props.onToggle && o.disabled,
2029
+ "aria-pressed": index < 0 ? props.value === o.value : index === n,
2030
+ ...shortcutAria(n, props.options.length),
2031
+ onClick: () => {
2032
+ buffer.current = "";
2033
+ setOrdinal("");
2034
+ pick(n, !props.inline);
2035
+ },
2036
+ children: [jsx("span", { children: `${n + 1}. ${o.label}` }), jsx("kbd", { children: shortcutLabel(n, !!props.onToggle) })]
2037
+ })
2038
+ ] }, o.value)),
2039
+ !props.inline ? jsx("button", { type: "button", onClick: close, children: "\u9589\u3058\u308B\uFF08Esc\uFF09" }) : null
2040
+ ]
2041
+ })
2042
+ ] });
1268
2043
  }
1269
2044
  function DiffReviewTab(props) {
1270
- const sessionId = String(props.sessionId);
1271
- const useReview = props.useDiffReview;
1272
- const controller = props.reviewController;
1273
- const openFile = props.openCurrentFile;
1274
- const useTabInfo = props.useTabInfo;
1275
- const { tab } = useTabInfo();
1276
- const state = useReview(value => value.bySession[sessionId]) ?? initialReviewSession();
1277
- const wasVisible = useRef(tab.visible);
1278
- useEffect(() => { void controller.load(sessionId); }, [sessionId]);
1279
- useEffect(() => {
1280
- if (tab.visible && !wasVisible.current && state.review)
1281
- void controller.refreshReview(sessionId, state.review.reviewId);
1282
- wasVisible.current = tab.visible;
1283
- }, [sessionId, tab.visible, state.review?.reviewId]);
1284
- useEffect(() => {
1285
- const onClose = () => { const review = controller.store.getSnapshot().bySession[sessionId]?.review; if (review?.state === 'analyzing')
1286
- void controller.cancel(sessionId, review.reviewId); };
1287
- tab.signal.addEventListener('abort', onClose, { once: true });
1288
- return () => tab.signal.removeEventListener('abort', onClose);
1289
- }, [sessionId, tab.signal]);
1290
- const review = state.review;
1291
- const reviewMatchesSelection = matchesReviewMode(review, state.mode, state.turnSeq);
1292
- const active = review?.snapshot.files.find(file => file.fileId === state.activeFileId) ?? review?.snapshot.files[0];
1293
- const selectFile = (fileId) => controller.change(sessionId, { selected: state.selected.includes(fileId)
1294
- ? state.selected.filter(id => id !== fileId) : [...state.selected, fileId] });
1295
- const visitEvidence = (id, anchor) => {
1296
- const [fileId, hunkId] = id.split(':');
1297
- if (!fileId || !review?.snapshot.files.some(file => file.fileId === fileId))
1298
- return;
1299
- controller.change(sessionId, { activeFileId: fileId, fileListOpen: false });
1300
- requestAnimationFrame(() => {
1301
- if (!hunkId) {
1302
- document.getElementById(`kiokuko-review-${review.reviewId}-detail`)?.focus();
1303
- return;
1304
- }
1305
- const section = document.getElementById(`kiokuko-review-${fileId}-${hunkId}`);
1306
- const line = anchor?.fileId === fileId && anchor.hunkId === hunkId
1307
- ? [...(section?.querySelectorAll('[data-old-line],[data-new-line]') ?? [])].find(element => element.dataset[anchor.side === 'old' ? 'oldLine' : 'newLine'] === String(anchor.startLine)) : undefined;
1308
- const target = line ?? section;
1309
- target?.focus({ preventScroll: true });
1310
- target?.scrollIntoView({ block: 'center' });
1311
- });
2045
+ const sessionId = String(props.sessionId);
2046
+ const useReview = props.useDiffReview;
2047
+ const controller = props.reviewController;
2048
+ const openFile = props.openCurrentFile;
2049
+ const useTabInfo = props.useTabInfo;
2050
+ const { tab } = useTabInfo();
2051
+ const state = useReview((value) => value.bySession[sessionId]) ?? initialReviewSession();
2052
+ const wasVisible = useRef(tab.visible);
2053
+ useEffect(() => {
2054
+ void controller.load(sessionId);
2055
+ }, [sessionId]);
2056
+ useEffect(() => {
2057
+ if (tab.visible && !wasVisible.current && state.review)
2058
+ void controller.refreshReview(sessionId, state.review.reviewId);
2059
+ wasVisible.current = tab.visible;
2060
+ }, [sessionId, tab.visible, state.review?.reviewId]);
2061
+ useEffect(() => {
2062
+ const onClose = () => {
2063
+ const review2 = controller.store.getSnapshot().bySession[sessionId]?.review;
2064
+ if (review2?.state === "analyzing")
2065
+ void controller.cancel(sessionId, review2.reviewId);
1312
2066
  };
1313
- const analysisGroups = review?.analysis ? [
1314
- { label: '影響', items: review.analysis.impact },
1315
- { label: '破壊的変更', items: review.analysis.breakingChanges },
1316
- { label: 'テストの不足', items: review.analysis.testGaps },
1317
- { label: '過去判断との不一致', items: review.analysis.memoryConflicts },
1318
- { label: '仮定', items: review.analysis.assumptions },
1319
- ] : [];
1320
- return jsxs('section', { className: 'kiokuko-review', 'aria-label': 'Diff レビュー', children: [
1321
- jsx('h2', { children: 'Diff レビュー' }),
1322
- jsxs('div', { className: 'kiokuko-review-controls', children: [
1323
- jsx(NumberedPicker, { label: '比較対象', value: state.mode, options: [
1324
- { value: 'current', label: '未コミット全体' }, { value: 'staged', label: 'ステージ済み' },
1325
- { value: 'unstaged', label: '未ステージ' }, { value: 'turn', label: 'このターン' }
1326
- ],
1327
- onChoose: (value) => controller.change(sessionId, { mode: value }) }),
1328
- ...(state.mode === 'turn' ? [jsx(NumberedPicker, { label: 'ターン差分', value: String(state.turnSeq ?? ''),
1329
- options: [{ value: '', label: '選択' }, ...state.turns.map(seq => ({ value: String(seq), label: `seq ${seq}` }))],
1330
- onChoose: (value) => controller.change(sessionId, { turnSeq: value === '' ? undefined : Number(value) }) }, 'turn')] : []),
1331
- jsx('button', { type: 'button', disabled: state.busy || state.loading || state.cancelling || review?.state === 'analyzing' || state.mode === 'turn' && state.turnSeq === undefined, onClick: () => void controller.capture(sessionId), children: review ? '取得し直す' : '差分を取得' }),
1332
- review ? jsx('button', { type: 'button', disabled: state.loading || state.cancelling || state.busy && review.state !== 'analyzing', onClick: () => void controller.refreshReview(sessionId, review.reviewId), children: '状態を確認' }) : null,
1333
- ] }),
1334
- state.untracked.length ? jsx(NumberedPicker, { label: '未追跡ファイル(明示選択)', inline: true,
1335
- options: state.untracked.map(path => ({ value: path, label: path })), checked: state.selectedUntracked,
1336
- onChoose: () => { }, onToggle: (path) => controller.change(sessionId, { selectedUntracked: state.selectedUntracked.includes(path)
1337
- ? state.selectedUntracked.filter(item => item !== path) : [...state.selectedUntracked, path] }) }) : null,
1338
- jsx('p', { role: 'status', 'aria-live': 'polite', children: state.cancelling ? '分析を停止中' : state.loading ? '確認中' : state.busy ? '処理中' : state.error || (state.availability === 'repo_unavailable' ? 'Git リポジトリを確認できません' : '') }),
1339
- review ? jsxs(Fragment, { children: [
1340
- jsx('p', { children: `${review.snapshot.capturedAt} / ${review.snapshot.mode} / ${REVIEW_STATES[review.state] ?? review.state} / 鮮度: ${REVIEW_FRESHNESS[review.freshness] ?? review.freshness}` }),
1341
- jsx('p', { children: `文脈: ${REVIEW_CONTEXT_SOURCES[review.context.source] ?? review.context.source} / メモリ: ${REVIEW_MEMORY_STATES[review.context.memory] ?? review.context.memory}${review.context.reason ? ` (${review.context.reason})` : ''}` }),
1342
- review.context.task ? jsx('p', { children: `タスク: ${review.context.task}` }) : null,
1343
- review.context.reviewInput ? jsx('p', { children: `このレビューの目的(利用者入力): ${review.context.reviewInput}` }) : null,
1344
- ...(review.context.candidates?.length ? [jsx(NumberedPicker, { label: 'タスク', value: state.runId,
1345
- options: [{ value: '', label: '指定なし' }, ...review.context.candidates.map(item => ({ value: item.runId, label: `${item.task} (${item.status})` }))],
1346
- onChoose: (value) => controller.change(sessionId, { runId: value }) }, 'runs')] : []),
1347
- jsx('p', { children: `変更ファイル: ${review.snapshot.files.length} / ${review.snapshot.totalFiles}` }),
1348
- review.snapshot.totalFiles > review.snapshot.files.length ? jsx('p', { role: 'status', children: `${review.snapshot.totalFiles - review.snapshot.files.length} 件は取得上限で未収集です。全体リスクは未確定です。` }) : null,
1349
- jsx('p', { children: `送信対象: ${state.selected.length} ファイル / ${review.snapshot.files.filter(file => file.kind !== 'text').length} ファイルはテキスト分析対象外` }),
1350
- !reviewMatchesSelection ? jsx('p', { role: 'status', children: '比較対象を変更しました。「取得し直す」で新しい差分を確認してください。' }) : null,
1351
- !state.selected.length ? jsx('p', { role: 'status', children: review.snapshot.files.some(file => file.kind === 'text')
1352
- ? '分析するテキストファイルを変更ファイル一覧で選択してください。' : '分析できるテキストファイルがありません。差分は引き続き確認できます。' }) : null,
1353
- jsxs('div', { className: 'kiokuko-review-analysis-controls', children: [
1354
- jsx(NumberedPicker, { label: 'モデル', value: state.modelKey, disabled: state.busy || state.loading || state.cancelling,
1355
- options: [{ value: '', label: '選択' }, ...state.models.map(item => ({ value: JSON.stringify([item.provider, item.model]), label: `${item.provider}/${item.model}` }))],
1356
- onChoose: (value) => controller.change(sessionId, { modelKey: value }) }),
1357
- jsxs('label', { children: ['レビューの目的(任意)', jsx('textarea', { value: state.purpose, disabled: state.busy || state.loading || state.cancelling, maxLength: 4000, onChange: (event) => controller.change(sessionId, { purpose: event.target.value }) })] }),
1358
- jsx('button', { type: 'button', disabled: state.busy || state.loading || state.cancelling || review.state === 'analyzing' || !reviewMatchesSelection || !state.selected.length || !state.modelKey, onClick: () => void controller.analyze(sessionId), children: '分析する' }),
1359
- review.state === 'analyzing' ? jsx('button', { type: 'button', disabled: state.cancelling, onClick: () => void controller.cancel(sessionId, review.reviewId), children: state.cancelling ? '停止中' : '停止' }) : null,
1360
- ] }),
1361
- !state.models.length && !state.loading ? jsxs('p', { role: 'status', children: [
1362
- state.modelAvailability === 'no_models' ? '登録済みモデルがありません。DSH のモデル設定を確認してください。'
1363
- : state.modelAvailability === 'catalog_error' ? 'モデル一覧を取得できませんでした。'
1364
- : state.modelAvailability === 'service_unavailable' ? 'モデルサービスを利用できません。DSH の接続を確認してください。'
1365
- : 'モデル一覧を確認できません。DSH の接続とモデル設定を確認してください。',
1366
- ' ', jsx('button', { type: 'button', disabled: state.busy || state.cancelling, onClick: () => void controller.load(sessionId), children: 'モデルを再取得' }),
1367
- ] }) : null,
1368
- jsx('button', { type: 'button', className: 'kiokuko-review-list-toggle', onClick: () => {
1369
- controller.change(sessionId, { fileListOpen: !state.fileListOpen });
1370
- requestAnimationFrame(() => {
1371
- const pane = document.getElementById(`kiokuko-review-${review.reviewId}-${state.fileListOpen ? 'detail' : 'files'}`);
1372
- (pane?.querySelector('[data-kiokuko-shortcuts]') ?? pane)?.focus();
1373
- });
1374
- },
1375
- children: state.fileListOpen ? '差分に戻る' : '変更ファイル一覧' }),
1376
- jsxs('div', { className: 'kiokuko-review-main', 'data-list-open': String(state.fileListOpen), children: [
1377
- jsx('nav', { id: `kiokuko-review-${review.reviewId}-files`, tabIndex: -1, 'aria-label': '変更ファイル', children: jsx(NumberedPicker, { label: '変更ファイル', inline: true, value: active?.fileId, checked: state.selected,
1378
- options: review.snapshot.files.map(file => ({ value: file.fileId, label: `${file.displayPath} (${file.layer}, ${file.kind})`, disabled: file.kind !== 'text' })),
1379
- onChoose: (fileId) => controller.change(sessionId, { activeFileId: fileId }), onToggle: selectFile }) }),
1380
- active ? jsxs('article', { id: `kiokuko-review-${review.reviewId}-detail`, tabIndex: -1, children: [jsx('h3', { children: active.displayPath }),
1381
- active.kind === 'text' && active.newDigest && review.snapshot.mode !== 'turn' ? jsx('button', { type: 'button',
1382
- onClick: () => void controller.openCurrentFile(sessionId, review.reviewId, active.fileId, openFile), children: '現在のファイルへ' }) : null,
1383
- active.reason ? jsx('p', { children: `除外・制限: ${active.reason}` }) : null,
1384
- jsx(SnapshotDiff, { file: active, snapshotId: review.snapshot.snapshotId })] }) : jsx('p', { children: '変更はありません' }),
1385
- ] }),
1386
- review.context.execution?.length ? jsxs('section', { children: [jsx('h3', { children: '記録された実行証拠' }), ...review.context.execution.map((item, index) => jsx('p', { children: `${item.command}: ${item.status} / 今回の差分との一致: ${item.snapshotMatch}` }, index))] }) : null,
1387
- jsxs('section', { children: [jsx('h3', { children: 'AI の解釈' }), jsx('p', { children: review.summary || '分析なし' }),
1388
- review.analysis ? jsx('p', { children: `全体リスク: ${review.analysis.overallRisk}` }) : null,
1389
- ...analysisGroups.filter(group => group.items.length).map(group => jsxs('section', { children: [jsx('h4', { children: group.label }), jsx('ul', { children: group.items.map((item, index) => jsx('li', { children: item }, index)) })] }, group.label)),
1390
- ...review.claims.map((claim, index) => jsxs('p', { children: [claim.text, ` (確信度: ${claim.confidence}) `,
1391
- ...claim.evidenceIds.map(id => jsx('button', { type: 'button', onClick: () => visitEvidence(id, claim.anchor), children: `根拠 ${id}` }, id)),
1392
- ...(claim.unverifiedAssumptions.length ? [jsx('span', { children: ` 未検証の仮定: ${claim.unverifiedAssumptions.join('、')}` })] : [])] }, index))] }),
1393
- review.unanalyzedFileIds?.length ? jsx('p', { children: `未分析: ${review.unanalyzedFileIds.map(id => review.snapshot.files.find(file => file.fileId === id)?.displayPath ?? id).join('、')}` }) : null,
1394
- review.errors.length ? jsx('p', { role: 'status', children: `未検証: ${review.errors.map(reviewErrorMessage).join(' ')}` }) : null,
1395
- jsxs('div', { className: 'kiokuko-review-exports', children: [jsx('a', { href: controller.exportUrl(sessionId, review.reviewId, 'markdown'), children: 'Markdown を保存' }), jsx('a', { href: controller.exportUrl(sessionId, review.reviewId, 'json'), children: 'JSON を保存' })] }),
1396
- ] }) : null,
1397
- ] });
2067
+ tab.signal.addEventListener("abort", onClose, { once: true });
2068
+ return () => tab.signal.removeEventListener("abort", onClose);
2069
+ }, [sessionId, tab.signal]);
2070
+ const review = state.review;
2071
+ const reviewMatchesSelection = matchesReviewMode(review, state.mode, state.turnSeq);
2072
+ const active = review?.snapshot.files.find((file) => file.fileId === state.activeFileId) ?? review?.snapshot.files[0];
2073
+ const selectFile = (fileId) => controller.change(sessionId, { selected: state.selected.includes(fileId) ? state.selected.filter((id) => id !== fileId) : [...state.selected, fileId] });
2074
+ const visitEvidence = (id, anchor) => {
2075
+ const [fileId, hunkId] = id.split(":");
2076
+ if (!fileId || !review?.snapshot.files.some((file) => file.fileId === fileId))
2077
+ return;
2078
+ controller.change(sessionId, { activeFileId: fileId, fileListOpen: false });
2079
+ requestAnimationFrame(() => {
2080
+ if (!hunkId) {
2081
+ document.getElementById(`kiokuko-review-${review.reviewId}-detail`)?.focus();
2082
+ return;
2083
+ }
2084
+ const section = document.getElementById(`kiokuko-review-${fileId}-${hunkId}`);
2085
+ const line = anchor?.fileId === fileId && anchor.hunkId === hunkId ? [...section?.querySelectorAll("[data-old-line],[data-new-line]") ?? []].find((element) => element.dataset[anchor.side === "old" ? "oldLine" : "newLine"] === String(anchor.startLine)) : void 0;
2086
+ const target = line ?? section;
2087
+ target?.focus({ preventScroll: true });
2088
+ target?.scrollIntoView({ block: "center" });
2089
+ });
2090
+ };
2091
+ const analysisGroups = review?.analysis ? [
2092
+ { label: "\u5F71\u97FF", items: review.analysis.impact },
2093
+ { label: "\u7834\u58CA\u7684\u5909\u66F4", items: review.analysis.breakingChanges },
2094
+ { label: "\u30C6\u30B9\u30C8\u306E\u4E0D\u8DB3", items: review.analysis.testGaps },
2095
+ { label: "\u904E\u53BB\u5224\u65AD\u3068\u306E\u4E0D\u4E00\u81F4", items: review.analysis.memoryConflicts },
2096
+ { label: "\u4EEE\u5B9A", items: review.analysis.assumptions }
2097
+ ] : [];
2098
+ return jsxs("section", { className: "kiokuko-review", "aria-label": "Diff \u30EC\u30D3\u30E5\u30FC", children: [
2099
+ jsx("h2", { children: "Diff \u30EC\u30D3\u30E5\u30FC" }),
2100
+ jsxs("div", { className: "kiokuko-review-controls", children: [
2101
+ jsx(NumberedPicker, {
2102
+ label: "\u6BD4\u8F03\u5BFE\u8C61",
2103
+ value: state.mode,
2104
+ options: [
2105
+ { value: "current", label: "\u672A\u30B3\u30DF\u30C3\u30C8\u5168\u4F53" },
2106
+ { value: "staged", label: "\u30B9\u30C6\u30FC\u30B8\u6E08\u307F" },
2107
+ { value: "unstaged", label: "\u672A\u30B9\u30C6\u30FC\u30B8" },
2108
+ { value: "turn", label: "\u3053\u306E\u30BF\u30FC\u30F3" }
2109
+ ],
2110
+ onChoose: (value) => controller.change(sessionId, { mode: value })
2111
+ }),
2112
+ ...state.mode === "turn" ? [jsx(NumberedPicker, {
2113
+ label: "\u30BF\u30FC\u30F3\u5DEE\u5206",
2114
+ value: String(state.turnSeq ?? ""),
2115
+ options: [{ value: "", label: "\u9078\u629E" }, ...state.turns.map((seq) => ({ value: String(seq), label: `seq ${seq}` }))],
2116
+ onChoose: (value) => controller.change(sessionId, { turnSeq: value === "" ? void 0 : Number(value) })
2117
+ }, "turn")] : [],
2118
+ jsx("button", { type: "button", disabled: state.busy || state.loading || state.cancelling || review?.state === "analyzing" || state.mode === "turn" && state.turnSeq === void 0, onClick: () => void controller.capture(sessionId), children: review ? "\u53D6\u5F97\u3057\u76F4\u3059" : "\u5DEE\u5206\u3092\u53D6\u5F97" }),
2119
+ review ? jsx("button", { type: "button", disabled: state.loading || state.cancelling || state.busy && review.state !== "analyzing", onClick: () => void controller.refreshReview(sessionId, review.reviewId), children: "\u72B6\u614B\u3092\u78BA\u8A8D" }) : null
2120
+ ] }),
2121
+ state.untracked.length ? jsx(NumberedPicker, {
2122
+ label: "\u672A\u8FFD\u8DE1\u30D5\u30A1\u30A4\u30EB\uFF08\u660E\u793A\u9078\u629E\uFF09",
2123
+ inline: true,
2124
+ options: state.untracked.map((path) => ({ value: path, label: path })),
2125
+ checked: state.selectedUntracked,
2126
+ onChoose: () => {
2127
+ },
2128
+ onToggle: (path) => controller.change(sessionId, { selectedUntracked: state.selectedUntracked.includes(path) ? state.selectedUntracked.filter((item) => item !== path) : [...state.selectedUntracked, path] })
2129
+ }) : null,
2130
+ jsx("p", { role: "status", "aria-live": "polite", children: state.cancelling ? "\u5206\u6790\u3092\u505C\u6B62\u4E2D" : state.loading ? "\u78BA\u8A8D\u4E2D" : state.busy ? "\u51E6\u7406\u4E2D" : state.error || (state.availability === "repo_unavailable" ? "Git \u30EA\u30DD\u30B8\u30C8\u30EA\u3092\u78BA\u8A8D\u3067\u304D\u307E\u305B\u3093" : "") }),
2131
+ review ? jsxs(Fragment, { children: [
2132
+ jsx("p", { children: `${review.snapshot.capturedAt} / ${review.snapshot.mode} / ${REVIEW_STATES[review.state] ?? review.state} / \u9BAE\u5EA6: ${REVIEW_FRESHNESS[review.freshness] ?? review.freshness}` }),
2133
+ jsx("p", { children: `\u6587\u8108: ${REVIEW_CONTEXT_SOURCES[review.context.source] ?? review.context.source} / \u30E1\u30E2\u30EA: ${REVIEW_MEMORY_STATES[review.context.memory] ?? review.context.memory}${review.context.reason ? ` (${review.context.reason})` : ""}` }),
2134
+ review.context.task ? jsx("p", { children: `\u30BF\u30B9\u30AF: ${review.context.task}` }) : null,
2135
+ review.context.reviewInput ? jsx("p", { children: `\u3053\u306E\u30EC\u30D3\u30E5\u30FC\u306E\u76EE\u7684\uFF08\u5229\u7528\u8005\u5165\u529B\uFF09: ${review.context.reviewInput}` }) : null,
2136
+ ...review.context.candidates?.length ? [jsx(NumberedPicker, {
2137
+ label: "\u30BF\u30B9\u30AF",
2138
+ value: state.runId,
2139
+ options: [{ value: "", label: "\u6307\u5B9A\u306A\u3057" }, ...review.context.candidates.map((item) => ({ value: item.runId, label: `${item.task} (${item.status})` }))],
2140
+ onChoose: (value) => controller.change(sessionId, { runId: value })
2141
+ }, "runs")] : [],
2142
+ jsx("p", { children: `\u5909\u66F4\u30D5\u30A1\u30A4\u30EB: ${review.snapshot.files.length} / ${review.snapshot.totalFiles}` }),
2143
+ review.snapshot.totalFiles > review.snapshot.files.length ? jsx("p", { role: "status", children: `${review.snapshot.totalFiles - review.snapshot.files.length} \u4EF6\u306F\u53D6\u5F97\u4E0A\u9650\u3067\u672A\u53CE\u96C6\u3067\u3059\u3002\u5168\u4F53\u30EA\u30B9\u30AF\u306F\u672A\u78BA\u5B9A\u3067\u3059\u3002` }) : null,
2144
+ jsx("p", { children: `\u9001\u4FE1\u5BFE\u8C61: ${state.selected.length} \u30D5\u30A1\u30A4\u30EB / ${review.snapshot.files.filter((file) => file.kind !== "text").length} \u30D5\u30A1\u30A4\u30EB\u306F\u30C6\u30AD\u30B9\u30C8\u5206\u6790\u5BFE\u8C61\u5916` }),
2145
+ !reviewMatchesSelection ? jsx("p", { role: "status", children: "\u6BD4\u8F03\u5BFE\u8C61\u3092\u5909\u66F4\u3057\u307E\u3057\u305F\u3002\u300C\u53D6\u5F97\u3057\u76F4\u3059\u300D\u3067\u65B0\u3057\u3044\u5DEE\u5206\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002" }) : null,
2146
+ !state.selected.length ? jsx("p", { role: "status", children: review.snapshot.files.some((file) => file.kind === "text") ? "\u5206\u6790\u3059\u308B\u30C6\u30AD\u30B9\u30C8\u30D5\u30A1\u30A4\u30EB\u3092\u5909\u66F4\u30D5\u30A1\u30A4\u30EB\u4E00\u89A7\u3067\u9078\u629E\u3057\u3066\u304F\u3060\u3055\u3044\u3002" : "\u5206\u6790\u3067\u304D\u308B\u30C6\u30AD\u30B9\u30C8\u30D5\u30A1\u30A4\u30EB\u304C\u3042\u308A\u307E\u305B\u3093\u3002\u5DEE\u5206\u306F\u5F15\u304D\u7D9A\u304D\u78BA\u8A8D\u3067\u304D\u307E\u3059\u3002" }) : null,
2147
+ jsxs("div", { className: "kiokuko-review-analysis-controls", children: [
2148
+ jsx(NumberedPicker, {
2149
+ label: "\u30E2\u30C7\u30EB",
2150
+ value: state.modelKey,
2151
+ disabled: state.busy || state.loading || state.cancelling,
2152
+ options: [{ value: "", label: "\u9078\u629E" }, ...state.models.map((item) => ({ value: JSON.stringify([item.provider, item.model]), label: `${item.provider}/${item.model}` }))],
2153
+ onChoose: (value) => controller.change(sessionId, { modelKey: value })
2154
+ }),
2155
+ jsxs("label", { children: ["\u30EC\u30D3\u30E5\u30FC\u306E\u76EE\u7684\uFF08\u4EFB\u610F\uFF09", jsx("textarea", { value: state.purpose, disabled: state.busy || state.loading || state.cancelling, maxLength: 4e3, onChange: (event) => controller.change(sessionId, { purpose: event.target.value }) })] }),
2156
+ jsx("button", { type: "button", disabled: state.busy || state.loading || state.cancelling || review.state === "analyzing" || !reviewMatchesSelection || !state.selected.length || !state.modelKey, onClick: () => void controller.analyze(sessionId), children: "\u5206\u6790\u3059\u308B" }),
2157
+ review.state === "analyzing" ? jsx("button", { type: "button", disabled: state.cancelling, onClick: () => void controller.cancel(sessionId, review.reviewId), children: state.cancelling ? "\u505C\u6B62\u4E2D" : "\u505C\u6B62" }) : null
2158
+ ] }),
2159
+ !state.models.length && !state.loading ? jsxs("p", { role: "status", children: [
2160
+ state.modelAvailability === "no_models" ? "\u767B\u9332\u6E08\u307F\u30E2\u30C7\u30EB\u304C\u3042\u308A\u307E\u305B\u3093\u3002DSH \u306E\u30E2\u30C7\u30EB\u8A2D\u5B9A\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002" : state.modelAvailability === "catalog_error" ? "\u30E2\u30C7\u30EB\u4E00\u89A7\u3092\u53D6\u5F97\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F\u3002" : state.modelAvailability === "service_unavailable" ? "\u30E2\u30C7\u30EB\u30B5\u30FC\u30D3\u30B9\u3092\u5229\u7528\u3067\u304D\u307E\u305B\u3093\u3002DSH \u306E\u63A5\u7D9A\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002" : "\u30E2\u30C7\u30EB\u4E00\u89A7\u3092\u78BA\u8A8D\u3067\u304D\u307E\u305B\u3093\u3002DSH \u306E\u63A5\u7D9A\u3068\u30E2\u30C7\u30EB\u8A2D\u5B9A\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
2161
+ " ",
2162
+ jsx("button", { type: "button", disabled: state.busy || state.cancelling, onClick: () => void controller.load(sessionId), children: "\u30E2\u30C7\u30EB\u3092\u518D\u53D6\u5F97" })
2163
+ ] }) : null,
2164
+ jsx("button", {
2165
+ type: "button",
2166
+ className: "kiokuko-review-list-toggle",
2167
+ onClick: () => {
2168
+ controller.change(sessionId, { fileListOpen: !state.fileListOpen });
2169
+ requestAnimationFrame(() => {
2170
+ const pane = document.getElementById(`kiokuko-review-${review.reviewId}-${state.fileListOpen ? "detail" : "files"}`);
2171
+ (pane?.querySelector("[data-kiokuko-shortcuts]") ?? pane)?.focus();
2172
+ });
2173
+ },
2174
+ children: state.fileListOpen ? "\u5DEE\u5206\u306B\u623B\u308B" : "\u5909\u66F4\u30D5\u30A1\u30A4\u30EB\u4E00\u89A7"
2175
+ }),
2176
+ jsxs("div", { className: "kiokuko-review-main", "data-list-open": String(state.fileListOpen), children: [
2177
+ jsx("nav", { id: `kiokuko-review-${review.reviewId}-files`, tabIndex: -1, "aria-label": "\u5909\u66F4\u30D5\u30A1\u30A4\u30EB", children: jsx(NumberedPicker, {
2178
+ label: "\u5909\u66F4\u30D5\u30A1\u30A4\u30EB",
2179
+ inline: true,
2180
+ value: active?.fileId,
2181
+ checked: state.selected,
2182
+ options: review.snapshot.files.map((file) => ({ value: file.fileId, label: `${file.displayPath} (${file.layer}, ${file.kind})`, disabled: file.kind !== "text" })),
2183
+ onChoose: (fileId) => controller.change(sessionId, { activeFileId: fileId }),
2184
+ onToggle: selectFile
2185
+ }) }),
2186
+ active ? jsxs("article", { id: `kiokuko-review-${review.reviewId}-detail`, tabIndex: -1, children: [
2187
+ jsx("h3", { children: active.displayPath }),
2188
+ active.kind === "text" && active.newDigest && review.snapshot.mode !== "turn" ? jsx("button", {
2189
+ type: "button",
2190
+ onClick: () => void controller.openCurrentFile(sessionId, review.reviewId, active.fileId, openFile),
2191
+ children: "\u73FE\u5728\u306E\u30D5\u30A1\u30A4\u30EB\u3078"
2192
+ }) : null,
2193
+ active.reason ? jsx("p", { children: `\u9664\u5916\u30FB\u5236\u9650: ${active.reason}` }) : null,
2194
+ jsx(SnapshotDiff, { file: active, snapshotId: review.snapshot.snapshotId })
2195
+ ] }) : jsx("p", { children: "\u5909\u66F4\u306F\u3042\u308A\u307E\u305B\u3093" })
2196
+ ] }),
2197
+ review.context.execution?.length ? jsxs("section", { children: [jsx("h3", { children: "\u8A18\u9332\u3055\u308C\u305F\u5B9F\u884C\u8A3C\u62E0" }), ...review.context.execution.map((item, index) => jsx("p", { children: `${item.command}: ${item.status} / \u4ECA\u56DE\u306E\u5DEE\u5206\u3068\u306E\u4E00\u81F4: ${item.snapshotMatch}` }, index))] }) : null,
2198
+ jsxs("section", { children: [
2199
+ jsx("h3", { children: "AI \u306E\u89E3\u91C8" }),
2200
+ jsx("p", { children: review.summary || "\u5206\u6790\u306A\u3057" }),
2201
+ review.analysis ? jsx("p", { children: `\u5168\u4F53\u30EA\u30B9\u30AF: ${review.analysis.overallRisk}` }) : null,
2202
+ ...analysisGroups.filter((group) => group.items.length).map((group) => jsxs("section", { children: [jsx("h4", { children: group.label }), jsx("ul", { children: group.items.map((item, index) => jsx("li", { children: item }, index)) })] }, group.label)),
2203
+ ...review.claims.map((claim, index) => jsxs("p", { children: [
2204
+ claim.text,
2205
+ ` (\u78BA\u4FE1\u5EA6: ${claim.confidence}) `,
2206
+ ...claim.evidenceIds.map((id) => jsx("button", { type: "button", onClick: () => visitEvidence(id, claim.anchor), children: `\u6839\u62E0 ${id}` }, id)),
2207
+ ...claim.unverifiedAssumptions.length ? [jsx("span", { children: ` \u672A\u691C\u8A3C\u306E\u4EEE\u5B9A: ${claim.unverifiedAssumptions.join("\u3001")}` })] : []
2208
+ ] }, index))
2209
+ ] }),
2210
+ review.unanalyzedFileIds?.length ? jsx("p", { children: `\u672A\u5206\u6790: ${review.unanalyzedFileIds.map((id) => review.snapshot.files.find((file) => file.fileId === id)?.displayPath ?? id).join("\u3001")}` }) : null,
2211
+ review.errors.length ? jsx("p", { role: "status", children: `\u672A\u691C\u8A3C: ${review.errors.map(reviewErrorMessage).join(" ")}` }) : null,
2212
+ jsxs("div", { className: "kiokuko-review-exports", children: [jsx("a", { href: controller.exportUrl(sessionId, review.reviewId, "markdown"), children: "Markdown \u3092\u4FDD\u5B58" }), jsx("a", { href: controller.exportUrl(sessionId, review.reviewId, "json"), children: "JSON \u3092\u4FDD\u5B58" })] })
2213
+ ] }) : null
2214
+ ] });
1398
2215
  }
1399
2216
  function DiffReviewHeaderAction(props) {
1400
- const openPane = props.openDiffReview;
1401
- const [open, setOpen] = useState(false);
1402
- const dialog = useRef(null);
1403
- const trigger = useRef(null);
1404
- const fallbackAbort = useRef(new AbortController());
1405
- useEffect(() => {
1406
- if (open && dialog.current && !dialog.current.open)
1407
- dialog.current.showModal();
1408
- if (!open && dialog.current?.open) {
1409
- dialog.current.close();
1410
- trigger.current?.focus();
1411
- }
1412
- }, [open]);
1413
- const close = () => { fallbackAbort.current.abort(); setOpen(false); trigger.current?.focus(); };
1414
- const openReview = () => {
1415
- if (openPane())
1416
- return;
1417
- fallbackAbort.current = new AbortController();
1418
- setOpen(true);
1419
- };
1420
- return jsxs(Fragment, { children: [
1421
- jsx('button', { ref: trigger, type: 'button', className: 'kiokuko-session-log-button', onClick: openReview, children: 'Diff レビュー' }),
1422
- open ? jsxs('dialog', { ref: dialog, onCancel: close, onClose: close, 'aria-label': 'Diff レビュー',
1423
- className: 'kiokuko-review-fallback', children: [
1424
- jsx('button', { type: 'button', onClick: close, children: '閉じる' }),
1425
- jsx(DiffReviewTab, { ...props, useTabInfo: () => ({ tab: { signal: fallbackAbort.current.signal, visible: true } }) }),
1426
- ] }) : null,
1427
- ] });
2217
+ const openPane = props.openDiffReview;
2218
+ const [open, setOpen] = useState(false);
2219
+ const dialog = useRef(null);
2220
+ const trigger = useRef(null);
2221
+ const fallbackAbort = useRef(new AbortController());
2222
+ useEffect(() => {
2223
+ if (open && dialog.current && !dialog.current.open)
2224
+ dialog.current.showModal();
2225
+ if (!open && dialog.current?.open) {
2226
+ dialog.current.close();
2227
+ trigger.current?.focus();
2228
+ }
2229
+ }, [open]);
2230
+ const close = () => {
2231
+ fallbackAbort.current.abort();
2232
+ setOpen(false);
2233
+ trigger.current?.focus();
2234
+ };
2235
+ const openReview = () => {
2236
+ if (openPane())
2237
+ return;
2238
+ fallbackAbort.current = new AbortController();
2239
+ setOpen(true);
2240
+ };
2241
+ return jsxs(Fragment, { children: [
2242
+ jsx("button", { ref: trigger, type: "button", className: "kiokuko-session-log-button", onClick: openReview, children: "Diff \u30EC\u30D3\u30E5\u30FC" }),
2243
+ open ? jsxs("dialog", {
2244
+ ref: dialog,
2245
+ onCancel: close,
2246
+ onClose: close,
2247
+ "aria-label": "Diff \u30EC\u30D3\u30E5\u30FC",
2248
+ className: "kiokuko-review-fallback",
2249
+ children: [
2250
+ jsx("button", { type: "button", onClick: close, children: "\u9589\u3058\u308B" }),
2251
+ jsx(DiffReviewTab, { ...props, useTabInfo: () => ({ tab: { signal: fallbackAbort.current.signal, visible: true } }) })
2252
+ ]
2253
+ }) : null
2254
+ ] });
1428
2255
  }
1429
2256
  function installReviewStyle() {
1430
- const style = document.createElement('style');
1431
- style.dataset.pluginCss = 'kiokuko-diff-review';
1432
- style.textContent = `.kiokuko-review-fallback{width:min(1000px,95vw);height:min(85vh,900px);padding:8px;box-sizing:border-box}.kiokuko-review{box-sizing:border-box;height:100%;overflow:auto;padding:12px;color:var(--dsw-alias-label-primary,CanvasText);background:var(--dsw-alias-background-primary,Canvas);font:13px/1.5 var(--dsw-font-family,system-ui)}.kiokuko-review h2{margin:0 0 12px;font-size:17px}.kiokuko-review h3{font-size:14px}.kiokuko-review button,.kiokuko-review select,.kiokuko-review textarea{font:inherit;color:inherit;background:transparent;border:1px solid var(--dsw-alias-border-l4,#888);border-radius:6px;padding:6px;min-height:36px}.kiokuko-review button{cursor:pointer}.kiokuko-review button:disabled{opacity:.55;cursor:default}.kiokuko-review :focus-visible{outline:2px solid Highlight;outline-offset:2px}.kiokuko-review-controls,.kiokuko-review-analysis-controls,.kiokuko-review-exports{display:flex;flex-wrap:wrap;gap:8px;align-items:end;margin:8px 0}.kiokuko-review label{display:flex;flex-direction:column;gap:3px}.kiokuko-review fieldset label{display:inline-flex;flex-direction:row;align-items:center;margin:4px 8px}.kiokuko-review-main{display:grid;grid-template-columns:minmax(130px,32%) minmax(0,1fr);gap:10px;min-width:0}.kiokuko-review-main nav{overflow:auto;min-width:0;max-height:55vh}.kiokuko-review-main article{overflow:auto;min-width:0}.kiokuko-review-file-row{display:flex;align-items:center;overflow-wrap:anywhere}.kiokuko-review-file-row button{text-align:left;border:0;overflow-wrap:anywhere}.kiokuko-review-file-row button[aria-current=true]{font-weight:700;text-decoration:underline}.kiokuko-review-diff pre{font:12px/1.4 ui-monospace,monospace;overflow:auto;white-space:pre}.kiokuko-review-diff .added{background:color-mix(in srgb,green 15%,transparent)}.kiokuko-review-diff .removed{background:color-mix(in srgb,red 15%,transparent)}.kiokuko-review-exports a{color:inherit;text-decoration:underline}@media(max-width:600px){.kiokuko-review-main{grid-template-columns:1fr}.kiokuko-review-main nav{max-height:180px}}`;
1433
- style.textContent += `.kiokuko-review{container-type:inline-size}.kiokuko-review-list-toggle{display:none}
2257
+ const style = document.createElement("style");
2258
+ style.dataset.pluginCss = "kiokuko-diff-review";
2259
+ style.textContent = `.kiokuko-review-fallback{width:min(1000px,95vw);height:min(85vh,900px);padding:8px;box-sizing:border-box}.kiokuko-review{box-sizing:border-box;height:100%;overflow:auto;padding:12px;color:var(--dsw-alias-label-primary,CanvasText);background:var(--dsw-alias-background-primary,Canvas);font:13px/1.5 var(--dsw-font-family,system-ui)}.kiokuko-review h2{margin:0 0 12px;font-size:17px}.kiokuko-review h3{font-size:14px}.kiokuko-review button,.kiokuko-review select,.kiokuko-review textarea{font:inherit;color:inherit;background:transparent;border:1px solid var(--dsw-alias-border-l4,#888);border-radius:6px;padding:6px;min-height:36px}.kiokuko-review button{cursor:pointer}.kiokuko-review button:disabled{opacity:.55;cursor:default}.kiokuko-review :focus-visible{outline:2px solid Highlight;outline-offset:2px}.kiokuko-review-controls,.kiokuko-review-analysis-controls,.kiokuko-review-exports{display:flex;flex-wrap:wrap;gap:8px;align-items:end;margin:8px 0}.kiokuko-review label{display:flex;flex-direction:column;gap:3px}.kiokuko-review fieldset label{display:inline-flex;flex-direction:row;align-items:center;margin:4px 8px}.kiokuko-review-main{display:grid;grid-template-columns:minmax(130px,32%) minmax(0,1fr);gap:10px;min-width:0}.kiokuko-review-main nav{overflow:auto;min-width:0;max-height:55vh}.kiokuko-review-main article{overflow:auto;min-width:0}.kiokuko-review-file-row{display:flex;align-items:center;overflow-wrap:anywhere}.kiokuko-review-file-row button{text-align:left;border:0;overflow-wrap:anywhere}.kiokuko-review-file-row button[aria-current=true]{font-weight:700;text-decoration:underline}.kiokuko-review-diff pre{font:12px/1.4 ui-monospace,monospace;overflow:auto;white-space:pre}.kiokuko-review-diff .added{background:color-mix(in srgb,green 15%,transparent)}.kiokuko-review-diff .removed{background:color-mix(in srgb,red 15%,transparent)}.kiokuko-review-exports a{color:inherit;text-decoration:underline}@media(max-width:600px){.kiokuko-review-main{grid-template-columns:1fr}.kiokuko-review-main nav{max-height:180px}}`;
2260
+ style.textContent += `.kiokuko-review{container-type:inline-size}.kiokuko-review-list-toggle{display:none}
1434
2261
  .kiokuko-review-analysis-controls{flex-direction:column;align-items:stretch}
1435
2262
  .kiokuko-review-analysis-controls label{min-width:0}
1436
2263
  .kiokuko-review-analysis-controls select,.kiokuko-review-analysis-controls textarea{box-sizing:border-box;width:100%;min-width:0;max-width:100%}
1437
2264
  .kiokuko-review-analysis-controls button{align-self:flex-start}
1438
2265
  .kiokuko-review-diff [tabindex="-1"]:focus{outline:2px solid Highlight;outline-offset:2px}
1439
2266
  @container (max-width:800px){.kiokuko-review-main{grid-template-columns:minmax(0,1fr)}.kiokuko-review-list-toggle{display:block}.kiokuko-review-main[data-list-open=false] nav{display:none}.kiokuko-review-main[data-list-open=true] article{display:none}}`;
1440
- document.head.appendChild(style);
1441
- return () => style.remove();
2267
+ document.head.appendChild(style);
2268
+ return () => style.remove();
1442
2269
  }
1443
- /** Register Kiokuko's streaming Session-export browser surface. */
1444
2270
  function apply(ctx) {
1445
- const installApprovals = (scope, approvalScope, modern) => {
1446
- scope.slots.inject('settings.general.item', () => scope.slots.register({ name: 'settings.general.item', id: 'kiokuko-lisp-approval', locale: LOCALE_NAMESPACE,
1447
- inject: () => ({ approvalScope, modern }),
1448
- }, LispApprovalControl));
1449
- };
1450
- ctx.inject?.(['settingsScope'], scope => installApprovals(scope, scope.settingsScope.bind({ namespace: 'kiokuko-lisp' }), false));
1451
- ctx.inject?.(['configForms'], scope => installApprovals(scope, scope.configForms.get('kiokuko-dsh'), true));
1452
- const reviewController = new DiffReviewClientController();
1453
- let rightSidebar;
1454
- ctx.effect(() => async () => reviewController.dispose(), 'kiokuko-dsh: diff review browser lifecycle');
1455
- ctx.effect(installReviewStyle, 'kiokuko-dsh: diff review style');
1456
- ctx.inject?.(['sidebarRightTabs', 'sidebarRight'], scope => {
1457
- const t = scope.locale.bind?.(REVIEW_NS) ?? ((key) => key);
1458
- scope.effect(() => { rightSidebar = scope.sidebarRight; return () => { if (rightSidebar === scope.sidebarRight)
1459
- rightSidebar = undefined; }; }, 'kiokuko-dsh: diff review pane availability');
1460
- scope.effect(() => {
1461
- const dispose = scope.sidebarRightTabs.register({
1462
- id: REVIEW_TAB_ID, kind: REVIEW_KIND, priority: 'extension', title: () => t('review.title'),
1463
- guide: [{ id: 'open', order: 30, title: () => t('review.title'), description: () => t('review.description') }],
1464
- });
1465
- return () => { void dispose(); };
1466
- }, 'kiokuko-dsh: diff review page');
1467
- scope.effect(() => scope.slots.inject('sidebar.right.pane.tab', () => scope.slots.register({
1468
- name: 'sidebar.right.pane.tab', key: REVIEW_TAB_ID, locale: REVIEW_NS,
1469
- inject: () => ({ hooks: { diffReview: reviewController.store }, reviewController,
1470
- openCurrentFile: (address) => { if (!rightSidebar?.openResource)
1471
- return false; try {
1472
- rightSidebar.openResource(address);
1473
- return true;
1474
- }
1475
- catch {
1476
- return false;
1477
- } } }),
1478
- }, DiffReviewTab)), 'kiokuko-dsh: diff review tab body');
1479
- });
1480
- ctx.slots.inject('conversation.session.header.utilities', () => ctx.slots.register({
1481
- name: 'conversation.session.header.utilities', id: 'kiokuko-diff-review-open', locale: REVIEW_NS,
1482
- inject: () => ({ hooks: { diffReview: reviewController.store }, reviewController,
1483
- openDiffReview: () => { if (!rightSidebar)
1484
- return false; try {
1485
- rightSidebar.openTab(REVIEW_KIND);
1486
- return true;
1487
- }
1488
- catch {
1489
- return false;
1490
- } },
1491
- openCurrentFile: (address) => { if (!rightSidebar?.openResource)
1492
- return false; try {
1493
- rightSidebar.openResource(address);
1494
- return true;
1495
- }
1496
- catch {
1497
- return false;
1498
- } } }),
1499
- }, DiffReviewHeaderAction));
1500
- ctx.slots.inject('conversation.input.left', () => ctx.slots.register({
1501
- name: 'conversation.input.left', id: 'kiokuko-lisp-status', locale: LOCALE_NAMESPACE,
1502
- }, LispSessionStatus));
1503
- ctx.slots.inject('conversation.composer', () => ctx.slots.register({
1504
- name: 'conversation.composer', priority: -10, select: intakePending, locale: LOCALE_NAMESPACE,
1505
- }, IntakeQuestion));
1506
- ctx.effect(installIntakeStyle, 'kiokuko-dsh: intake style');
1507
- ctx.slots.inject('conversation.session.header.utilities', () => ctx.slots.register({
1508
- name: 'conversation.session.header.utilities', id: 'kiokuko-deep-reports', locale: LOCALE_NAMESPACE,
1509
- }, DeepSessionReports));
1510
- ctx.slots.inject('conversation.session.header.utilities', () => ctx.slots.register({
1511
- name: 'conversation.session.header.utilities', id: 'kiokuko-session-notices', locale: LOCALE_NAMESPACE,
1512
- }, (props) => jsx(DeepSessionReports, { ...props, notices: true })));
1513
- ctx.uiConversation.events.register(completionReportDefinition);
1514
- ctx.slots.inject('conversation.chat.node', () => ctx.slots.register({
1515
- name: 'conversation.chat.node', key: 'kiokuko-completion-report', locale: LOCALE_NAMESPACE,
1516
- }, CompletionReport));
1517
- const controller = new SessionLogDownloadController();
1518
- ctx.effect(() => async () => controller.dispose(), 'kiokuko-dsh: browser download lifecycle');
1519
- ctx.effect(installStyle, 'kiokuko-dsh: browser download style');
1520
- ctx.effect(() => ctx.locale.register(LOCALE_NAMESPACE, { en, ja, zh }), 'kiokuko-dsh: browser dictionaries');
1521
- ctx.on('command/executed', (sessionId, commandName, result) => {
1522
- if (commandName === 'export' && result.kind === 'success')
1523
- void controller.download(sessionId);
1524
- });
1525
- ctx.slots.inject('conversation.session.header.utilities', () => ctx.slots.register({
1526
- name: 'conversation.session.header.utilities',
1527
- id: 'kiokuko-session-log-download',
1528
- locale: LOCALE_NAMESPACE,
1529
- inject: () => ({
1530
- hooks: { sessionLogDownload: controller.store },
1531
- request: (sessionId) => controller.download(sessionId),
1532
- dismiss: (sessionId) => controller.dismiss(sessionId),
1533
- }),
1534
- }, SessionLogDownloadHeaderAction));
2271
+ mountModelsClient(ctx);
2272
+ const installApprovals = (scope, approvalScope, modern) => {
2273
+ scope.slots.inject("settings.general.item", () => scope.slots.register({
2274
+ name: "settings.general.item",
2275
+ id: "kiokuko-lisp-approval",
2276
+ locale: LOCALE_NAMESPACE,
2277
+ inject: () => ({ approvalScope, modern })
2278
+ }, LispApprovalControl));
2279
+ };
2280
+ ctx.inject?.(["settingsScope"], (scope) => installApprovals(scope, scope.settingsScope.bind({ namespace: "kiokuko-lisp" }), false));
2281
+ ctx.inject?.(["configForms"], (scope) => installApprovals(scope, scope.configForms.get("kiokuko-dsh"), true));
2282
+ const reviewController = new DiffReviewClientController();
2283
+ let rightSidebar;
2284
+ ctx.effect(() => async () => reviewController.dispose(), "kiokuko-dsh: diff review browser lifecycle");
2285
+ ctx.effect(installReviewStyle, "kiokuko-dsh: diff review style");
2286
+ ctx.inject?.(["sidebarRightTabs", "sidebarRight"], (scope) => {
2287
+ const t = scope.locale.bind?.(REVIEW_NS) ?? ((key) => key);
2288
+ scope.effect(() => {
2289
+ rightSidebar = scope.sidebarRight;
2290
+ return () => {
2291
+ if (rightSidebar === scope.sidebarRight)
2292
+ rightSidebar = void 0;
2293
+ };
2294
+ }, "kiokuko-dsh: diff review pane availability");
2295
+ scope.effect(() => {
2296
+ const dispose = scope.sidebarRightTabs.register({
2297
+ id: REVIEW_TAB_ID,
2298
+ kind: REVIEW_KIND,
2299
+ priority: "extension",
2300
+ title: () => t("review.title"),
2301
+ guide: [{ id: "open", order: 30, title: () => t("review.title"), description: () => t("review.description") }]
2302
+ });
2303
+ return () => {
2304
+ void dispose();
2305
+ };
2306
+ }, "kiokuko-dsh: diff review page");
2307
+ scope.effect(() => scope.slots.inject("sidebar.right.pane.tab", () => scope.slots.register({
2308
+ name: "sidebar.right.pane.tab",
2309
+ key: REVIEW_TAB_ID,
2310
+ locale: REVIEW_NS,
2311
+ inject: () => ({
2312
+ hooks: { diffReview: reviewController.store },
2313
+ reviewController,
2314
+ openCurrentFile: (address) => {
2315
+ if (!rightSidebar?.openResource)
2316
+ return false;
2317
+ try {
2318
+ rightSidebar.openResource(address);
2319
+ return true;
2320
+ } catch {
2321
+ return false;
2322
+ }
2323
+ }
2324
+ })
2325
+ }, DiffReviewTab)), "kiokuko-dsh: diff review tab body");
2326
+ });
2327
+ ctx.slots.inject("conversation.session.header.utilities", () => ctx.slots.register({
2328
+ name: "conversation.session.header.utilities",
2329
+ id: "kiokuko-diff-review-open",
2330
+ locale: REVIEW_NS,
2331
+ inject: () => ({
2332
+ hooks: { diffReview: reviewController.store },
2333
+ reviewController,
2334
+ openDiffReview: () => {
2335
+ if (!rightSidebar)
2336
+ return false;
2337
+ try {
2338
+ rightSidebar.openTab(REVIEW_KIND);
2339
+ return true;
2340
+ } catch {
2341
+ return false;
2342
+ }
2343
+ },
2344
+ openCurrentFile: (address) => {
2345
+ if (!rightSidebar?.openResource)
2346
+ return false;
2347
+ try {
2348
+ rightSidebar.openResource(address);
2349
+ return true;
2350
+ } catch {
2351
+ return false;
2352
+ }
2353
+ }
2354
+ })
2355
+ }, DiffReviewHeaderAction));
2356
+ ctx.slots.inject("conversation.input.left", () => ctx.slots.register({
2357
+ name: "conversation.input.left",
2358
+ id: "kiokuko-lisp-status",
2359
+ locale: LOCALE_NAMESPACE
2360
+ }, LispSessionStatus));
2361
+ ctx.slots.inject("conversation.composer", () => ctx.slots.register({
2362
+ name: "conversation.composer",
2363
+ priority: -10,
2364
+ select: intakePending,
2365
+ locale: LOCALE_NAMESPACE
2366
+ }, IntakeQuestion));
2367
+ ctx.effect(installIntakeStyle, "kiokuko-dsh: intake style");
2368
+ ctx.slots.inject("conversation.session.header.utilities", () => ctx.slots.register({
2369
+ name: "conversation.session.header.utilities",
2370
+ id: "kiokuko-deep-reports",
2371
+ locale: LOCALE_NAMESPACE
2372
+ }, DeepSessionReports));
2373
+ ctx.slots.inject("conversation.session.header.utilities", () => ctx.slots.register({
2374
+ name: "conversation.session.header.utilities",
2375
+ id: "kiokuko-session-notices",
2376
+ locale: LOCALE_NAMESPACE
2377
+ }, (props) => jsx(DeepSessionReports, { ...props, notices: true })));
2378
+ ctx.uiConversation.events.register(completionReportDefinition);
2379
+ ctx.slots.inject("conversation.chat.node", () => ctx.slots.register({
2380
+ name: "conversation.chat.node",
2381
+ key: "kiokuko-completion-report",
2382
+ locale: LOCALE_NAMESPACE
2383
+ }, CompletionReport));
2384
+ const controller = new SessionLogDownloadController();
2385
+ ctx.effect(() => async () => controller.dispose(), "kiokuko-dsh: browser download lifecycle");
2386
+ ctx.effect(installStyle, "kiokuko-dsh: browser download style");
2387
+ ctx.effect(() => ctx.locale.register(LOCALE_NAMESPACE, { en, ja, zh }), "kiokuko-dsh: browser dictionaries");
2388
+ ctx.on("command/executed", (sessionId, commandName, result) => {
2389
+ if (commandName === "export" && result.kind === "success")
2390
+ void controller.download(sessionId);
2391
+ });
2392
+ ctx.slots.inject("conversation.session.header.utilities", () => ctx.slots.register({
2393
+ name: "conversation.session.header.utilities",
2394
+ id: "kiokuko-session-log-download",
2395
+ locale: LOCALE_NAMESPACE,
2396
+ inject: () => ({
2397
+ hooks: { sessionLogDownload: controller.store },
2398
+ request: (sessionId) => controller.download(sessionId),
2399
+ dismiss: (sessionId) => controller.dismiss(sessionId)
2400
+ })
2401
+ }, SessionLogDownloadHeaderAction));
1535
2402
  }
1536
- exports.apply = apply;
1537
- exports.downloadDshSessionLog = downloadDshSessionLog;
1538
- exports.inject = inject;
2403
+
1539
2404
  return module.exports;
1540
2405
  }
1541
2406
  });