@loicngr/kobo 2.1.26 → 2.1.29

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 (178) hide show
  1. package/AGENTS.md +7 -4
  2. package/CHANGELOG.md +15 -0
  3. package/CONFIGURATION.md +124 -71
  4. package/CONTRIBUTING.md +1 -1
  5. package/README.md +1 -1
  6. package/SECURITY.md +33 -6
  7. package/dist/mcp-server/kobo-tasks-server.js +36 -1021
  8. package/dist/mcp-server/managed-mcp-dispatcher.js +960 -0
  9. package/dist/mcp-server/workspace-dialogue-client.js +26 -1
  10. package/dist/server/index.js +52 -19
  11. package/dist/server/middleware/host-check-middleware.js +8 -5
  12. package/dist/server/middleware/network-auth-middleware.js +51 -29
  13. package/dist/server/middleware/security-headers-middleware.js +19 -0
  14. package/dist/server/middleware/traffic-limits-middleware.js +74 -0
  15. package/dist/server/routes/auth.js +73 -0
  16. package/dist/server/routes/internal-mcp.js +137 -0
  17. package/dist/server/routes/mcp-credentials.js +30 -0
  18. package/dist/server/routes/mcp.js +156 -76
  19. package/dist/server/routes/workspaces.js +19 -3
  20. package/dist/server/services/agent/engines/claude-code/resolve-binary.js +1 -1
  21. package/dist/server/services/agent/orchestrator.js +27 -17
  22. package/dist/server/services/dev-server-service.js +68 -4
  23. package/dist/server/services/environment-check-service.js +3 -2
  24. package/dist/server/services/external-mcp-credentials-service.js +110 -0
  25. package/dist/server/services/forge/github/provider.js +15 -3
  26. package/dist/server/services/forge/types.js +8 -0
  27. package/dist/server/services/internal-mcp-capability-service.js +98 -0
  28. package/dist/server/services/mcp-connection-info-service.js +1 -1
  29. package/dist/server/services/memory-mcp-service.js +9 -7
  30. package/dist/server/services/memory-service.js +15 -0
  31. package/dist/server/services/network-access-service.js +43 -73
  32. package/dist/server/services/network-session-service.js +182 -0
  33. package/dist/server/services/settings-service.js +29 -7
  34. package/dist/server/services/terminal-websocket-service.js +5 -0
  35. package/dist/server/services/websocket-service.js +5 -0
  36. package/dist/server/utils/paths.js +2 -1
  37. package/dist/server/utils/startup-display.js +1 -2
  38. package/dist/shared/models.js +6 -0
  39. package/docker-compose.vps.yml +6 -0
  40. package/docs/deployment-vps.md +24 -12
  41. package/docs/getting-started.md +1 -1
  42. package/docs/troubleshooting.md +1 -1
  43. package/package.json +14 -14
  44. package/src/client/dist/pwa/assets/ActivityFeed-CiTe2VUQ.js +2 -0
  45. package/src/client/dist/pwa/assets/{AutoLoopFinalReviewSettings-DOr41E2Y.js → AutoLoopFinalReviewSettings-v3jXoH_Q.js} +1 -1
  46. package/src/client/dist/pwa/assets/{ChangelogPage-DOCgZMpn.js → ChangelogPage-C5KjAOMG.js} +1 -1
  47. package/src/client/dist/pwa/assets/{ClosePopup-CRV2hxHS.js → ClosePopup-rU8Bm-mC.js} +1 -1
  48. package/src/client/dist/pwa/assets/ComparisonPanel-XXyzPEzW.js +2 -0
  49. package/src/client/dist/pwa/assets/CreatePage-MYCexRJu.js +2 -0
  50. package/src/client/dist/pwa/assets/{DashboardPage-dBpJaQSb.js → DashboardPage-DyhUu26C.js} +1 -1
  51. package/src/client/dist/pwa/assets/DiffViewer-CvsmC9d5.js +8 -0
  52. package/src/client/dist/pwa/assets/{DocumentsPanel-AqLx0MzB.js → DocumentsPanel-ItndGxeB.js} +1 -1
  53. package/src/client/dist/pwa/assets/{DrawerToggleButton-BLWaFgh0.js → DrawerToggleButton-DfzS_CD8.js} +1 -1
  54. package/src/client/dist/pwa/assets/GitPanel-C1ybi9bZ.js +3 -0
  55. package/src/client/dist/pwa/assets/{GitPanel-CRaLJzjc.css → GitPanel-yHvU38dp.css} +1 -1
  56. package/src/client/dist/pwa/assets/{HealthPage-DeryVLyz.js → HealthPage-C1u35ANL.js} +1 -1
  57. package/src/client/dist/pwa/assets/MainLayout-BEAGPqei.js +2 -0
  58. package/src/client/dist/pwa/assets/{MemoryPanel-B34DENtz.js → MemoryPanel-CZzI7_nY.js} +1 -1
  59. package/src/client/dist/pwa/assets/MemoryProposalList-DqNTycY1.js +1 -0
  60. package/src/client/dist/pwa/assets/{QBadge-CW3Lw-kL.js → QBadge-DiwbRpte.js} +1 -1
  61. package/src/client/dist/pwa/assets/{QBanner-BT2ZemYu.js → QBanner-Cw89HSjk.js} +1 -1
  62. package/src/client/dist/pwa/assets/QBtn-kfhHFy7Q.js +1 -0
  63. package/src/client/dist/pwa/assets/{QBtnDropdown-BPcK_rVs.js → QBtnDropdown-D7ml7zHo.js} +1 -1
  64. package/src/client/dist/pwa/assets/{QBtnGroup-CnM4vSJA.js → QBtnGroup-Bioxjp9q.js} +1 -1
  65. package/src/client/dist/pwa/assets/{QBtnToggle-DccOOtbh.js → QBtnToggle-Bod-x9im.js} +1 -1
  66. package/src/client/dist/pwa/assets/{QChip-Be16M4Ig.js → QChip-DvhtPv_L.js} +1 -1
  67. package/src/client/dist/pwa/assets/{QExpansionItem-CpwQ8L_F.js → QExpansionItem-BVb067oc.js} +1 -1
  68. package/src/client/dist/pwa/assets/{QItemSection-MdJ1_ZK7.js → QItemSection-ahqRK4JF.js} +1 -1
  69. package/src/client/dist/pwa/assets/{QList-Cx1UfaUm.js → QList-CAUl0v66.js} +1 -1
  70. package/src/client/dist/pwa/assets/{QMarkupTable-UhP7fVUp.js → QMarkupTable-B4LLAly-.js} +1 -1
  71. package/src/client/dist/pwa/assets/{QPage-CqkGHokx.js → QPage-DIy_mRoT.js} +1 -1
  72. package/src/client/dist/pwa/assets/{QScrollArea-BkNSVl7s.js → QScrollArea-CUDWkEHh.js} +1 -1
  73. package/src/client/dist/pwa/assets/QSelect-DOnv_ry0.js +1 -0
  74. package/src/client/dist/pwa/assets/{QSpinnerDots-xdWyaE1i.js → QSpinnerDots-Bea6hAuJ.js} +1 -1
  75. package/src/client/dist/pwa/assets/{QTooltip-Dp8fitHV.js → QTooltip-B5QJIU3p.js} +1 -1
  76. package/src/client/dist/pwa/assets/{SchedulePanel-Ck8LKDCa.js → SchedulePanel-IJOcrHgk.js} +1 -1
  77. package/src/client/dist/pwa/assets/{SearchPage-BAUZg1zq.js → SearchPage-BpTRWtne.js} +1 -1
  78. package/src/client/dist/pwa/assets/{SettingsPage-Dcq1Jeoo.css → SettingsPage-B4rmd2WX.css} +1 -1
  79. package/src/client/dist/pwa/assets/SettingsPage-CFaRN9Pk.js +9 -0
  80. package/src/client/dist/pwa/assets/{SplitWorkspacePage-CH8OHmtn.js → SplitWorkspacePage-BZ8wN4ii.js} +1 -1
  81. package/src/client/dist/pwa/assets/{TerminalPanel-Dz9EE-Ph.css → TerminalPanel-BcbwuFVR.css} +1 -1
  82. package/src/client/dist/pwa/assets/TerminalPanel-pY-RGBb6.js +16 -0
  83. package/src/client/dist/pwa/assets/{ToolCallItem-CLtGTV6w.js → ToolCallItem-C9ksVseJ.js} +4 -4
  84. package/src/client/dist/pwa/assets/{TouchPan-9SH2dx6m.js → TouchPan-DRynDIve.js} +1 -1
  85. package/src/client/dist/pwa/assets/{TourReplayButton-Dw4sGf9B.js → TourReplayButton-D4h1QR6C.js} +1 -1
  86. package/src/client/dist/pwa/assets/WorkspaceAttentionLabels-Bkj2DhOn.js +1 -0
  87. package/src/client/dist/pwa/assets/WorkspacePage-BTJnap9a.js +4 -0
  88. package/src/client/dist/pwa/assets/{browser-CC4W8r76.js → browser-BcOi2ES2.js} +2 -2
  89. package/src/client/dist/pwa/assets/build-path-tree-mDco3pi7.js +1 -0
  90. package/src/client/dist/pwa/assets/custom-sounds-T5HKzrWh.js +1 -0
  91. package/src/client/dist/pwa/assets/de-suqWUSXa.js +17 -0
  92. package/src/client/dist/pwa/assets/detached-fullscreen-CHqMxB-8.js +1 -0
  93. package/src/client/dist/pwa/assets/dom-CRPVbmiy.js +2 -0
  94. package/src/client/dist/pwa/assets/ecl-94qehhXe.js +1 -0
  95. package/src/client/dist/pwa/assets/{editor.api-BwfxvQ88.js → editor.api-CRrihXg2.js} +9 -9
  96. package/src/client/dist/pwa/assets/editor.worker-xrHNhPSq.js +33 -0
  97. package/src/client/dist/pwa/assets/{efforts-B3I7w5bs.js → efforts-C-p9DzkL.js} +2 -2
  98. package/src/client/dist/pwa/assets/es-Dcptbz5U.js +17 -0
  99. package/src/client/dist/pwa/assets/expand-template-gIwrPbeS.js +1 -0
  100. package/src/client/dist/pwa/assets/{format-CJIzi3Vf.js → format-DeBnwmgW.js} +48 -48
  101. package/src/client/dist/pwa/assets/{formatters-DQ8cP7nO.js → formatters-CxZZ_WTb.js} +1 -1
  102. package/src/client/dist/pwa/assets/fr-Db70bqVq.js +17 -0
  103. package/src/client/dist/pwa/assets/{freemarker2-DbpVO-cL.js → freemarker2-h7OLlqTn.js} +1 -1
  104. package/src/client/dist/pwa/assets/{handlebars-BHve0PpA.js → handlebars-BCWmi1-q.js} +1 -1
  105. package/src/client/dist/pwa/assets/{html-BhUkdFPz.js → html-MUMpfUBd.js} +1 -1
  106. package/src/client/dist/pwa/assets/i18n-DLprfNLs.js +1 -0
  107. package/src/client/dist/pwa/assets/index-C0MApoXN.css +1 -0
  108. package/src/client/dist/pwa/assets/index-DLoUeovT.js +20 -0
  109. package/src/client/dist/pwa/assets/it-Cloo58Nn.js +17 -0
  110. package/src/client/dist/pwa/assets/{javascript-BLAorPa3.js → javascript-CHDwijEy.js} +1 -1
  111. package/src/client/dist/pwa/assets/{liquid-CJGF-h-f.js → liquid-dFrnLevt.js} +1 -1
  112. package/src/client/dist/pwa/assets/{mdx-Cm0oNz_l.js → mdx-BHS7JVD4.js} +1 -1
  113. package/src/client/dist/pwa/assets/monaco-contributions-DCWZe7nX.js +53 -0
  114. package/src/client/dist/pwa/assets/{monaco.contribution-hr9s26Ud.js → monaco.contribution-Dx0gcAn4.js} +2 -2
  115. package/src/client/dist/pwa/assets/network-auth-DDMAzwYH.js +1 -0
  116. package/src/client/dist/pwa/assets/{notify-theme-EcbxuH_8.js → notify-theme-JuVT96rq.js} +1 -1
  117. package/src/client/dist/pwa/assets/{permissionModes-DLWiBfdb.js → permissionModes-D-Nexnoo.js} +1 -1
  118. package/src/client/dist/pwa/assets/{private.use-form-DY1BkePL.js → private.use-form-BcspuzM9.js} +1 -1
  119. package/src/client/dist/pwa/assets/{project-color-vDuTaTJn.js → project-color-DihsE4yN.js} +1 -1
  120. package/src/client/dist/pwa/assets/{python-BEjbzFAQ.js → python-BtjZnK9y.js} +1 -1
  121. package/src/client/dist/pwa/assets/{razor-BJPT7Wxx.js → razor-DBcOGz0a.js} +1 -1
  122. package/src/client/dist/pwa/assets/reactivity.esm-bundler-Dplx1ILy.js +1 -0
  123. package/src/client/dist/pwa/assets/render-chat-markdown-C9ihAYBt.js +67 -0
  124. package/src/client/dist/pwa/assets/runtime-core.esm-bundler-BAUgNzMK.js +1 -0
  125. package/src/client/dist/pwa/assets/{typescript-DPqM46Ef.js → typescript-B320c7wU.js} +1 -1
  126. package/src/client/dist/pwa/assets/{use-checkbox-v4aAbE6K.js → use-checkbox-DUxgXR4e.js} +1 -1
  127. package/src/client/dist/pwa/assets/{use-id-BrdYXce0.js → use-id-B7ieAx2m.js} +1 -1
  128. package/src/client/dist/pwa/assets/use-is-mobile-CHmaewI1.js +1 -0
  129. package/src/client/dist/pwa/assets/{use-search-index-status-BwwrB410.js → use-search-index-status-B95GPaJP.js} +1 -1
  130. package/src/client/dist/pwa/assets/use-slide-transition-BJh9xGMa.js +1 -0
  131. package/src/client/dist/pwa/assets/use-tours-CU5oA7cz.css +1 -0
  132. package/src/client/dist/pwa/assets/use-tours-C_5R1Y5D.js +2 -0
  133. package/src/client/dist/pwa/assets/{use-worktree-restore-tzd2FESU.js → use-worktree-restore-Cj8eGE9s.js} +1 -1
  134. package/src/client/dist/pwa/assets/{workspace-templates-CubzsZUU.js → workspace-templates-B9UETKER.js} +1 -1
  135. package/src/client/dist/pwa/assets/{xml-C6SVzw5_.js → xml-uK8LK4OB.js} +1 -1
  136. package/src/client/dist/pwa/assets/{yaml-OpCqnv4g.js → yaml-B2D1l_tz.js} +1 -1
  137. package/src/client/dist/pwa/index.html +10 -10
  138. package/src/client/dist/pwa/sw.js +1 -1
  139. package/src/mcp-server/README.md +13 -19
  140. package/src/mcp-server/kobo-tasks-server.ts +42 -1146
  141. package/src/mcp-server/managed-mcp-dispatcher.ts +1071 -0
  142. package/src/mcp-server/workspace-dialogue-client.ts +26 -1
  143. package/src/client/dist/pwa/assets/ActivityFeed-DfYrVNEh.js +0 -2
  144. package/src/client/dist/pwa/assets/ComparisonPanel-b5nTBjK6.js +0 -2
  145. package/src/client/dist/pwa/assets/CreatePage-B7VPHEqm.js +0 -2
  146. package/src/client/dist/pwa/assets/DiffViewer-BLQz2Qyx.js +0 -8
  147. package/src/client/dist/pwa/assets/GitPanel-Ba7ltCsM.js +0 -3
  148. package/src/client/dist/pwa/assets/MainLayout-B2XvjdWi.js +0 -2
  149. package/src/client/dist/pwa/assets/MemoryProposalList-BpN_OU8J.js +0 -1
  150. package/src/client/dist/pwa/assets/QBtn-BUSdPaf4.js +0 -1
  151. package/src/client/dist/pwa/assets/QSelect-DrLpbvcr.js +0 -1
  152. package/src/client/dist/pwa/assets/SettingsPage-Cj6kSVT5.js +0 -9
  153. package/src/client/dist/pwa/assets/TerminalPanel-CBmZ0IzQ.js +0 -16
  154. package/src/client/dist/pwa/assets/WorkspaceAttentionLabels-wd_sObJa.js +0 -1
  155. package/src/client/dist/pwa/assets/WorkspacePage-D4ODPOG-.js +0 -4
  156. package/src/client/dist/pwa/assets/build-path-tree-BBuyB-my.js +0 -1
  157. package/src/client/dist/pwa/assets/custom-sounds-BbPNRzAs.js +0 -1
  158. package/src/client/dist/pwa/assets/de-D9VzPDGF.js +0 -17
  159. package/src/client/dist/pwa/assets/detached-fullscreen-Du3TcYdX.js +0 -1
  160. package/src/client/dist/pwa/assets/dom-BtYTvLeN.js +0 -2
  161. package/src/client/dist/pwa/assets/ecl-D05T4iGw.js +0 -1
  162. package/src/client/dist/pwa/assets/editor.worker-BLJF_oQx.js +0 -33
  163. package/src/client/dist/pwa/assets/es-3R4MJuTt.js +0 -17
  164. package/src/client/dist/pwa/assets/expand-template-Ci9UjnxZ.js +0 -1
  165. package/src/client/dist/pwa/assets/fr-DXwDVnJ4.js +0 -17
  166. package/src/client/dist/pwa/assets/i18n-CGLQAADf.js +0 -1
  167. package/src/client/dist/pwa/assets/index-C8zAZ-k-.css +0 -1
  168. package/src/client/dist/pwa/assets/index-CNcXBpzz.js +0 -20
  169. package/src/client/dist/pwa/assets/it-T9AjnG42.js +0 -17
  170. package/src/client/dist/pwa/assets/monaco-contributions-nBg6wf05.js +0 -53
  171. package/src/client/dist/pwa/assets/network-auth-C850jGfI.js +0 -1
  172. package/src/client/dist/pwa/assets/reactivity.esm-bundler-DY1JkAEA.js +0 -1
  173. package/src/client/dist/pwa/assets/render-chat-markdown-CafzCcmu.js +0 -69
  174. package/src/client/dist/pwa/assets/runtime-core.esm-bundler-Bqg71Vll.js +0 -1
  175. package/src/client/dist/pwa/assets/use-is-mobile-CDB_rzM9.js +0 -1
  176. package/src/client/dist/pwa/assets/use-slide-transition-B_Nu6u-t.js +0 -1
  177. package/src/client/dist/pwa/assets/use-tours-B0KtCS9w.js +0 -2
  178. package/src/client/dist/pwa/assets/use-tours-CTZuBZOi.css +0 -1
@@ -0,0 +1,182 @@
1
+ import { createHash, randomBytes } from 'node:crypto';
2
+ import { isLoopbackAddress, resolveProxyOrigin } from './network-access-service.js';
3
+ const sessions = new Map();
4
+ const tickets = new Map();
5
+ const sockets = new Map();
6
+ const revokedSockets = new WeakSet();
7
+ const hash = (value) => createHash('sha256').update(value).digest('hex');
8
+ const fingerprint = (p) => hash(JSON.stringify([p.networkAccessEnabled, !!p.networkAccessBehindProxy, p.networkAccessToken]));
9
+ const lifetime = 12 * 60 * 60 * 1000;
10
+ export function isNetworkTransportAllowed(address, policy) {
11
+ return policy.networkAccessEnabled || (!policy.networkAccessBehindProxy && isLoopbackAddress(address));
12
+ }
13
+ function prune() {
14
+ const now = Date.now();
15
+ for (const [id, session] of sessions)
16
+ if (session.expiresAt <= now)
17
+ sessions.delete(id);
18
+ for (const [id, ticket] of tickets)
19
+ if (ticket.expiresAt <= now)
20
+ tickets.delete(id);
21
+ }
22
+ export function createBrowserSession(policy) {
23
+ prune();
24
+ if (sessions.size >= 256)
25
+ throw new Error('Session capacity reached');
26
+ const session = {
27
+ token: randomBytes(32).toString('base64url'),
28
+ expiresAt: Date.now() + lifetime,
29
+ policy: fingerprint(policy),
30
+ };
31
+ const { token, ...record } = session;
32
+ sessions.set(hash(token), record);
33
+ return session;
34
+ }
35
+ export function getBrowserSession(token, policy) {
36
+ if (!token || token.length > 128)
37
+ return undefined;
38
+ const record = sessions.get(hash(token));
39
+ if (!record || record.expiresAt <= Date.now() || record.policy !== fingerprint(policy))
40
+ return undefined;
41
+ return { ...record, token };
42
+ }
43
+ function sessionValid(session, policy) {
44
+ return !session || !!getBrowserSession(session.token, policy);
45
+ }
46
+ export function createWebSocketTicket(path, policy, session) {
47
+ if (!/^\/ws(?:\/terminal\/[A-Za-z0-9_-]{1,200})?$/.test(path))
48
+ throw new Error('Invalid WebSocket path');
49
+ prune();
50
+ if (tickets.size >= 1024)
51
+ throw new Error('Ticket capacity reached');
52
+ if (!sessionValid(session, policy))
53
+ throw new Error('Session expired');
54
+ const token = randomBytes(32).toString('base64url');
55
+ tickets.set(hash(token), { path, policy: fingerprint(policy), expiresAt: Date.now() + 60_000, session });
56
+ return token;
57
+ }
58
+ export function consumeWebSocketTicket(token, path, policy) {
59
+ if (!token || token.length > 128)
60
+ return undefined;
61
+ const id = hash(token);
62
+ const ticket = tickets.get(id);
63
+ tickets.delete(id);
64
+ if (!ticket ||
65
+ ticket.path !== path ||
66
+ ticket.expiresAt <= Date.now() ||
67
+ ticket.policy !== fingerprint(policy) ||
68
+ !sessionValid(ticket.session, policy))
69
+ return undefined;
70
+ return ticket;
71
+ }
72
+ function terminate(ws) {
73
+ const grant = sockets.get(ws);
74
+ if (grant) {
75
+ grant.revoked = true;
76
+ clearTimeout(grant.timer);
77
+ sockets.delete(ws);
78
+ }
79
+ if (!revokedSockets.has(ws)) {
80
+ revokedSockets.add(ws);
81
+ ws.terminate();
82
+ }
83
+ }
84
+ export function registerNetworkSocket(ws, address, policy, current, session) {
85
+ let currentPolicy;
86
+ try {
87
+ currentPolicy = current();
88
+ }
89
+ catch {
90
+ terminate(ws);
91
+ return false;
92
+ }
93
+ if (!isNetworkTransportAllowed(address, currentPolicy) ||
94
+ sockets.size >= 256 ||
95
+ [...sockets.values()].filter((g) => g.address === address).length >= 64 ||
96
+ !sessionValid(session, currentPolicy) ||
97
+ fingerprint(policy) !== fingerprint(currentPolicy)) {
98
+ terminate(ws);
99
+ return false;
100
+ }
101
+ const expiresAt = session?.expiresAt ?? Date.now() + lifetime;
102
+ const timer = setTimeout(() => terminate(ws), Math.max(0, expiresAt - Date.now()));
103
+ timer.unref?.();
104
+ sockets.set(ws, {
105
+ address,
106
+ policy: fingerprint(policy),
107
+ current,
108
+ session,
109
+ expiresAt,
110
+ revoked: false,
111
+ timer,
112
+ count: 0,
113
+ window: Date.now(),
114
+ });
115
+ ws.once('close', () => {
116
+ clearTimeout(timer);
117
+ sockets.delete(ws);
118
+ revokedSockets.add(ws);
119
+ });
120
+ return true;
121
+ }
122
+ export function isNetworkSocketAuthorized(ws) {
123
+ if (revokedSockets.has(ws))
124
+ return false;
125
+ const grant = sockets.get(ws);
126
+ if (!grant)
127
+ return true; // Unregistered service-level sockets do not participate in network authentication.
128
+ let policy;
129
+ try {
130
+ policy = grant.current();
131
+ }
132
+ catch {
133
+ terminate(ws);
134
+ return false;
135
+ }
136
+ if (grant.revoked ||
137
+ grant.expiresAt <= Date.now() ||
138
+ !isNetworkTransportAllowed(grant.address, policy) ||
139
+ grant.policy !== fingerprint(policy) ||
140
+ !sessionValid(grant.session, policy)) {
141
+ terminate(ws);
142
+ return false;
143
+ }
144
+ return true;
145
+ }
146
+ export function acceptNetworkSocketMessage(ws) {
147
+ if (!isNetworkSocketAuthorized(ws))
148
+ return false;
149
+ const grant = sockets.get(ws);
150
+ if (!grant)
151
+ return true;
152
+ if (Date.now() - grant.window >= 1000) {
153
+ grant.window = Date.now();
154
+ grant.count = 0;
155
+ }
156
+ if (++grant.count > 240) {
157
+ terminate(ws);
158
+ return false;
159
+ }
160
+ return true;
161
+ }
162
+ export function revokeBrowserSession(token) {
163
+ sessions.delete(hash(token));
164
+ for (const [id, ticket] of tickets)
165
+ if (ticket.session?.token === token)
166
+ tickets.delete(id);
167
+ for (const [ws, grant] of sockets)
168
+ if (grant.session?.token === token)
169
+ terminate(ws);
170
+ }
171
+ export function revokeNetworkSessions() {
172
+ sessions.clear();
173
+ tickets.clear();
174
+ for (const ws of sockets.keys())
175
+ terminate(ws);
176
+ }
177
+ export function browserCookieName(policy) {
178
+ return secureBrowserCookie(policy) ? '__Host-kobo-session' : 'kobo-session';
179
+ }
180
+ export function secureBrowserCookie(policy) {
181
+ return !!policy.networkAccessBehindProxy && !resolveProxyOrigin()?.startsWith('http:');
182
+ }
@@ -12,6 +12,7 @@ import { getSettingsPath } from '../utils/paths.js';
12
12
  import { InvalidWorktreesPathError, resolveGlobalWorktreesRoot, sanitizeWorktreesPath, validateWorktreesPath, } from '../utils/worktree-paths.js';
13
13
  import { DEFAULT_NOTION_INITIAL_PROMPT, DEFAULT_SENTRY_INITIAL_PROMPT } from './initial-prompt-template-service.js';
14
14
  import { LEGACY_AGNOSTIC_AUTO_LOOP_REVIEW_GATE, LEGACY_AGNOSTIC_REVIEW_TEMPLATE, LEGACY_CI_FIX_PROMPT_TEMPLATE, LEGACY_FINALIZATION_PROMPT, LEGACY_NOTION_INITIAL_PROMPT, LEGACY_PR_PROMPT_TEMPLATE, LEGACY_SENTRY_INITIAL_PROMPT, } from './legacy-public-prompts.js';
15
+ import { revokeNetworkSessions } from './network-session-service.js';
15
16
  import { DEFAULT_REVIEW_PROMPT_TEMPLATE } from './review-template-service.js';
16
17
  import { DEFAULT_CHANGE_SOURCE_BRANCH_SCRIPT, DEFAULT_MODEL_BY_ENGINE, FRESH_MODEL_BY_ENGINE, } from './settings-defaults.js';
17
18
  import { AGNOSTIC_PROMPTS } from './skill-suite-prompts.js';
@@ -1293,6 +1294,7 @@ function readSettings() {
1293
1294
  }
1294
1295
  function readSettingsFromDisk() {
1295
1296
  ensureSettingsPathInitialized();
1297
+ protectSettingsFiles();
1296
1298
  if (!fs.existsSync(settingsFilePath)) {
1297
1299
  const defaults = defaultSettings();
1298
1300
  writeSettings(defaults);
@@ -1334,6 +1336,22 @@ function readSettingsFromDisk() {
1334
1336
  }
1335
1337
  /** Number of `settings.json.backup-*` files kept; older ones are rotated out. */
1336
1338
  const SETTINGS_BACKUP_KEEP = 5;
1339
+ /** Repair legacy permissions before reading secrets, without following file symlinks. */
1340
+ function protectSettingsFiles() {
1341
+ const dir = path.dirname(settingsFilePath);
1342
+ fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
1343
+ fs.chmodSync(dir, 0o700);
1344
+ const basename = path.basename(settingsFilePath);
1345
+ for (const name of fs.readdirSync(dir)) {
1346
+ if (name !== basename && name !== `${basename}.tmp` && !name.startsWith(`${basename}.backup-`))
1347
+ continue;
1348
+ const file = path.join(dir, name);
1349
+ const stat = fs.lstatSync(file);
1350
+ if (!stat.isFile() || stat.nlink !== 1)
1351
+ throw new Error('[settings] Refusing unsafe settings file');
1352
+ fs.chmodSync(file, 0o600);
1353
+ }
1354
+ }
1337
1355
  /**
1338
1356
  * Keep only the `SETTINGS_BACKUP_KEEP` most recent `settings.json.backup-*`
1339
1357
  * files in `dir`, deleting the rest. Best-effort — never throws.
@@ -1368,7 +1386,7 @@ function createSettingsBackupIfPresent() {
1368
1386
  const stamp = new Date().toISOString().replace(/[:.]/g, '-');
1369
1387
  settingsBackupSequence += 1;
1370
1388
  const backupPath = path.join(dir, `settings.json.backup-${stamp}-${settingsBackupSequence}`);
1371
- fs.copyFileSync(settingsFilePath, backupPath);
1389
+ fs.writeFileSync(backupPath, fs.readFileSync(settingsFilePath), { mode: 0o600, flag: 'wx' });
1372
1390
  pruneSettingsBackups(dir);
1373
1391
  }
1374
1392
  function writeSettings(settings, options) {
@@ -1376,14 +1394,11 @@ function writeSettings(settings, options) {
1376
1394
  invalidateSettingsCache();
1377
1395
  ensureSettingsPathInitialized();
1378
1396
  const tmpPath = `${settingsFilePath}.tmp`;
1379
- const dir = path.dirname(settingsFilePath);
1380
- if (!fs.existsSync(dir)) {
1381
- fs.mkdirSync(dir, { recursive: true });
1382
- }
1397
+ protectSettingsFiles();
1383
1398
  if (options?.backup) {
1384
1399
  createSettingsBackupIfPresent();
1385
1400
  }
1386
- fs.writeFileSync(tmpPath, JSON.stringify(settings, null, 2), 'utf-8');
1401
+ fs.writeFileSync(tmpPath, JSON.stringify(settings, null, 2), { encoding: 'utf-8', mode: 0o600 });
1387
1402
  fs.renameSync(tmpPath, settingsFilePath);
1388
1403
  }
1389
1404
  /** Read and return the full settings object, creating defaults if missing. */
@@ -1823,7 +1838,9 @@ export function updateGlobalSettings(input) {
1823
1838
  * (which would leave the server bound wide but unauthenticatable after a restart).
1824
1839
  */
1825
1840
  export function updateNetworkAccessSettings(patch) {
1826
- const settings = readSettings();
1841
+ const current = readSettings();
1842
+ // Keep cached policy intact until persistence succeeds.
1843
+ const settings = { ...current, global: { ...current.global } };
1827
1844
  if (typeof patch.networkAccessEnabled === 'boolean') {
1828
1845
  settings.global.networkAccessEnabled = patch.networkAccessEnabled;
1829
1846
  }
@@ -1834,6 +1851,11 @@ export function updateNetworkAccessSettings(patch) {
1834
1851
  settings.global.networkAccessBehindProxy = patch.networkAccessBehindProxy;
1835
1852
  }
1836
1853
  writeSettings(settings, { backup: true });
1854
+ if (current.global.networkAccessEnabled !== settings.global.networkAccessEnabled ||
1855
+ current.global.networkAccessToken !== settings.global.networkAccessToken ||
1856
+ current.global.networkAccessBehindProxy !== settings.global.networkAccessBehindProxy) {
1857
+ revokeNetworkSessions();
1858
+ }
1837
1859
  return settings.global;
1838
1860
  }
1839
1861
  function ensureGlobalWorktreesRootExists(worktreesPath) {
@@ -1,4 +1,5 @@
1
1
  import { assertWorkspaceLifecycleAvailable, isWorkspaceLifecycleBusy } from '../utils/workspace-lifecycle-guard.js';
2
+ import { acceptNetworkSocketMessage, isNetworkSocketAuthorized } from './network-session-service.js';
2
3
  import { createTerminal, getTerminal } from './terminal-service.js';
3
4
  import { getWorkspace } from './workspace-service.js';
4
5
  const MAX_BUFFERED_BYTES = 1024 * 1024;
@@ -31,6 +32,8 @@ export function handleTerminalConnection(ws, workspaceId, isShuttingDown = () =>
31
32
  ws.terminate();
32
33
  }
33
34
  function send(data) {
35
+ if (!isNetworkSocketAuthorized(ws))
36
+ return false;
34
37
  if (disposed || ws.readyState !== 1)
35
38
  return false;
36
39
  if (ws.bufferedAmount + Buffer.byteLength(data) > MAX_BUFFERED_BYTES) {
@@ -72,6 +75,8 @@ export function handleTerminalConnection(ws, workspaceId, isShuttingDown = () =>
72
75
  ws.on('close', dispose);
73
76
  ws.on('error', disconnect);
74
77
  ws.on('message', (data, isBinary) => {
78
+ if (!acceptNetworkSocketMessage(ws))
79
+ return;
75
80
  if (disposed || ws.readyState !== 1 || isShuttingDown())
76
81
  return;
77
82
  if (isBinary) {
@@ -1,6 +1,7 @@
1
1
  import { nanoid } from 'nanoid';
2
2
  import { getDb } from '../db/index.js';
3
3
  import { recordActivity } from './activity-service.js';
4
+ import { acceptNetworkSocketMessage, isNetworkSocketAuthorized } from './network-session-service.js';
4
5
  import { getAllPersistedSnapshots } from './usage/db.js';
5
6
  // ── State ──────────────────────────────────────────────────────────────────────
6
7
  /** Maps each WS client to the set of workspaceIds they are subscribed to */
@@ -11,6 +12,8 @@ const clients = new Map();
11
12
  const MAX_BUFFERED_BYTES = 1024 * 1024;
12
13
  /** A dropped frame must end the connection so its replay cursor cannot skip it. */
13
14
  function sendFrame(ws, message) {
15
+ if (!isNetworkSocketAuthorized(ws))
16
+ return;
14
17
  if (ws.readyState !== 1)
15
18
  return;
16
19
  if (ws.bufferedAmount > MAX_BUFFERED_BYTES) {
@@ -92,6 +95,8 @@ export function handleConnection(ws) {
92
95
  console.error('[ws] usage hydration failed:', err);
93
96
  }
94
97
  ws.on('message', (data) => {
98
+ if (!acceptNetworkSocketMessage(ws))
99
+ return;
95
100
  let msg;
96
101
  try {
97
102
  const parsed = JSON.parse(data.toString());
@@ -67,8 +67,9 @@ export function getKoboHome() {
67
67
  export function ensureKoboHome() {
68
68
  const dir = getKoboHome();
69
69
  if (!fs.existsSync(dir)) {
70
- fs.mkdirSync(dir, { recursive: true });
70
+ fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
71
71
  }
72
+ fs.chmodSync(dir, 0o700);
72
73
  return dir;
73
74
  }
74
75
  /**
@@ -32,8 +32,7 @@ export function formatStartupBanner(options) {
32
32
  if (options.networkEnabled) {
33
33
  for (const lanUrl of options.lanUrls ?? [])
34
34
  lines.push(` LAN: ${lanUrl}`);
35
- if (options.token)
36
- lines.push(` Token: ${options.token}`);
35
+ lines.push(' Authentication required; retrieve the token from local settings.');
37
36
  }
38
37
  const release = parseChangelog(options.changelog ?? '').find((entry) => entry.version === options.version);
39
38
  const highlights = (release?.notes ?? '')
@@ -35,6 +35,12 @@ export const CLAUDE_MODELS = [
35
35
  i18nLabelKey: 'model.sonnet55',
36
36
  i18nDescriptionKey: 'model.sonnet55Description',
37
37
  },
38
+ {
39
+ id: 'claude-haiku-5-5',
40
+ label: 'Haiku 5.5',
41
+ i18nLabelKey: 'model.haiku55',
42
+ i18nDescriptionKey: 'model.haiku55Description',
43
+ },
38
44
  {
39
45
  id: 'claude-haiku-4-5-20251001',
40
46
  label: 'Haiku 4.5',
@@ -46,6 +46,12 @@ services:
46
46
  traefik.http.routers.kobo.entrypoints: ${TRAEFIK_ENTRYPOINT:-websecure}
47
47
  traefik.http.routers.kobo.tls: "true"
48
48
  traefik.http.routers.kobo.tls.certresolver: ${TRAEFIK_CERTRESOLVER:-letsencrypt}
49
+ traefik.http.routers.kobo.middlewares: kobo-security
50
+ traefik.http.middlewares.kobo-security.headers.stsseconds: "31536000"
51
+ traefik.http.middlewares.kobo-security.headers.contenttypenosniff: "true"
52
+ traefik.http.middlewares.kobo-security.headers.referrerpolicy: no-referrer
53
+ traefik.http.middlewares.kobo-security.headers.customframeoptionsvalue: SAMEORIGIN
54
+ traefik.http.middlewares.kobo-security.headers.contentsecuritypolicy: "frame-ancestors 'self'"
49
55
  traefik.http.services.kobo.loadbalancer.server.port: "3000"
50
56
  # No published HTTP or SSH ports. Traefik handles HTTP and WebSockets.
51
57
  stop_grace_period: 180s
@@ -191,13 +191,21 @@ From `/opt/kobo`:
191
191
  docker network inspect web # use your TRAEFIK_NETWORK value
192
192
  docker compose config --quiet
193
193
  docker compose up -d --wait --wait-timeout 180 kobo
194
- docker compose logs kobo | grep Token
194
+ docker compose exec kobo node -e 'const fs = require("node:fs"); console.log(JSON.parse(fs.readFileSync(process.env.KOBO_HOME + "/settings.json", "utf8")).global.networkAccessToken)'
195
195
  ```
196
196
 
197
- Open `https://kobo.example.com/?token=<printed-token>` on your PC. The browser
198
- remembers the token; treat both that URL and startup logs as credentials.
197
+ Open `https://kobo.example.com/` on your PC and paste the token into the login
198
+ dialog. The retrieval command deliberately displays a secret in your local
199
+ terminal; do not copy its output into logs or shared transcripts. Startup logs
200
+ never contain the token. After upgrading from a version that logged it, rotate
201
+ the token and apply your existing log-retention policy to old copies.
199
202
  `KOBO_NETWORK_ACCESS_PROXY_HOST` is populated from `KOBO_DOMAIN` and must match
200
203
  the public hostname. Both network access and behind-proxy mode are enabled.
204
+ Missing or invalid proxy hostname configuration rejects public traffic. Browser
205
+ origins must match the configured scheme and port (HTTPS is the default).
206
+ The Compose example attaches HSTS and browser security headers at the TLS router;
207
+ same-origin split panes remain supported. Keep the Docker socket unmounted unless
208
+ project workflows need its root-equivalent host access.
201
209
 
202
210
  This example publishes no HTTP or SSH ports. Use SSH to the VPS followed by
203
211
  `docker compose exec kobo kobo-vps-entrypoint --exec bash` for a container shell. The VPS launcher prepares the home permissions, then drops root before starting Node. It does not start the image's optional SSH daemon. `--exec` runs administrative shell/CLI commands as the same non-root user without repeating home migration.
@@ -211,15 +219,19 @@ curl -s -o /dev/null -w '%{http_code}\n' https://kobo.example.com/api/workspaces
211
219
 
212
220
  Expect health JSON with `status: "ok"` and the installed version, then `401`
213
221
  without a token. `/api/health` is intentionally public. Ordinary authenticated
214
- REST requests use `X-Kobo-Token`; the external MCP endpoint also supports Bearer
215
- authentication. No additional Traefik route is needed for WebSocket upgrades.
216
-
217
- Behind-proxy mode also protects loopback requests. Kōbō supplies the current
218
- network token to its internal workspace MCP process when launching Claude or
219
- Codex, so memory, progress bars and other backend calls can authenticate without
220
- manual MCP configuration. Memory and progress calls still require their separate
221
- per-launch capabilities. After rotating the network token, stop and resume active
222
- agent sessions so their MCP processes receive the new token.
222
+ REST scripts use the operator token in `X-Kobo-Token`; browser login creates a
223
+ 12-hour HttpOnly session cookie. External MCP clients require a dedicated key,
224
+ with explicitly granted tools/workspaces, even on loopback. See
225
+ [external MCP credentials](../CONFIGURATION.md#external-llm-access-through-mcp).
226
+ No additional Traefik route is needed for WebSocket upgrades: the browser obtains
227
+ a 60-second, single-use ticket instead of placing the operator token in the URL.
228
+
229
+ Rotating the operator token, disabling network access or changing proxy mode
230
+ revokes browser sessions and existing WebSockets, including terminal connections.
231
+ Managed agent MCP processes use a revocable per-launch capability through a
232
+ loopback-only internal endpoint, not the operator token. Plan/read-only launches
233
+ cannot mutate workspace state; only their narrow progress/report exceptions
234
+ remain. Agent processes are not stopped by browser credential revocation.
223
235
 
224
236
  ## 3. Authenticate and choose a project
225
237
 
@@ -4,7 +4,7 @@ Kōbō is a local, single-user application that runs Claude Code or OpenAI Codex
4
4
 
5
5
  ## Requirements
6
6
 
7
- - Node.js 24.15 or later, Git and Bash, with a writable local Git repository containing at least one commit.
7
+ - Node.js 26.12.1 or later, Git and Bash, with a writable local Git repository containing at least one commit.
8
8
  - A working login or API credential for the engine you choose. Kōbō ships neither credentials nor a provider subscription. Provider usage can incur charges.
9
9
  - Linux is the locally exercised platform for this readiness change. CI is configured to exercise Linux and macOS; a configured job is not a completed compatibility test. Windows users need a Linux environment such as WSL; native Windows and WSL have not been validated in this release rehearsal.
10
10
 
@@ -6,7 +6,7 @@ Open the first-run setup from Help and run the check again after changing the se
6
6
 
7
7
  | Check | Action |
8
8
  |---|---|
9
- | Node | Use Node 24.15 or later; contributors should use `.nvmrc`. |
9
+ | Node | Use Node 26.12.1 or later; contributors should use `.nvmrc`. |
10
10
  | Git / Bash | Install the missing executable and make it available on the server's `PATH`. |
11
11
  | Project | Select a writable Git repository containing at least one commit. |
12
12
  | Storage / worktrees | Check permissions on `KOBO_HOME`, the project and configured worktree root. |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@loicngr/kobo",
3
- "version": "2.1.26",
3
+ "version": "2.1.29",
4
4
  "description": "Kōbō — local multi-workspace orchestrator for Claude Code and OpenAI Codex, with isolated Git worktrees, live review and optional automation.",
5
5
  "type": "module",
6
6
  "license": "GPL-3.0-or-later",
@@ -61,7 +61,7 @@
61
61
  "quasar"
62
62
  ],
63
63
  "engines": {
64
- "node": ">=24.15.0"
64
+ "node": ">=26.12.1"
65
65
  },
66
66
  "scripts": {
67
67
  "dev": "KOBO_ENFORCE_LOCAL_HOME=1 KOBO_HOME=./data PORT=3300 KOBO_DEV_CLIENT_ORIGIN=http://localhost:8080 tsx watch src/server/index.ts",
@@ -88,32 +88,32 @@
88
88
  "security:scan": "node scripts/scan-secrets.mjs"
89
89
  },
90
90
  "dependencies": {
91
- "@anthropic-ai/claude-agent-sdk": "^0.3.287",
91
+ "@anthropic-ai/claude-agent-sdk": "^0.3.308",
92
92
  "@emnapi/core": "^1.11.3",
93
93
  "@emnapi/runtime": "^1.11.3",
94
- "@hono/node-server": "^2.1.3",
95
- "@modelcontextprotocol/sdk": "^1.31.0",
96
- "@openai/codex": "^0.159.3",
94
+ "@hono/node-server": "^2.1.5",
95
+ "@modelcontextprotocol/sdk": "^1.32.1",
96
+ "@openai/codex": "^0.163.0",
97
97
  "better-sqlite3": "^13.0.3",
98
- "cron-parser": "^5.10.1",
99
- "hono": "^4.13.12",
100
- "nanoid": "^6.0.1",
98
+ "cron-parser": "^5.10.2",
99
+ "hono": "^4.13.13",
100
+ "nanoid": "^6.0.2",
101
101
  "node-pty": "^1.1.0",
102
102
  "ws": "^8.22.0"
103
103
  },
104
104
  "devDependencies": {
105
105
  "@biomejs/biome": "2.5.14",
106
106
  "@types/better-sqlite3": "^9.6.0",
107
- "@types/node": "^26.6.4",
107
+ "@types/node": "^26.6.5",
108
108
  "@types/ws": "^8.18.2",
109
109
  "@vitest/runner": "^4.1.11",
110
- "concurrently": "^10.0.5",
110
+ "concurrently": "^10.0.6",
111
111
  "tsx": "^4.23.15",
112
112
  "typescript": "^7.0.2",
113
- "vitest": "^5.0.3"
113
+ "vitest": "^5.0.5"
114
114
  },
115
115
  "overrides": {
116
- "@hono/node-server": "^2.1.3",
117
- "shell-quote": "^1.11.0"
116
+ "@hono/node-server": "^2.1.5",
117
+ "shell-quote": "^1.12.0"
118
118
  }
119
119
  }
@@ -0,0 +1,2 @@
1
+ import{N as e,S as t,ft as n,mt as r,p as i,ut as a,v as o}from"./reactivity.esm-bundler-Dplx1ILy.js";import{C as s,F as c,J as l,L as u,N as d,O as f,P as p,T as m,Y as h,_ as g,d as _,f as v,g as y,j as ee,k as b,l as x,p as S,r as C,u as w,y as T,z as te}from"./runtime-core.esm-bundler-BAUgNzMK.js";import{$ as ne,K as re,M as E,f as ie,z as ae}from"./dom-CRPVbmiy.js";import{l as oe,t as D,u as se}from"./QBtn-kfhHFy7Q.js";import{Gt as ce,Jt as O,O as le,_t as ue,at as de,en as fe,j as pe,m as me,mt as he,pn as ge,pt as _e,rn as k,st as ve,un as A,v as ye}from"./index-DLoUeovT.js";import{t as be}from"./QSpinnerDots-Bea6hAuJ.js";import{t as xe}from"./QList-CAUl0v66.js";import{t as Se}from"./QMarkupTable-B4LLAly-.js";import{c as j,l as M,s as N}from"./QSelect-DOnv_ry0.js";import{t as P}from"./QItemSection-ahqRK4JF.js";import{t as Ce}from"./QExpansionItem-BVb067oc.js";import{a as F,c as we,r as I,t as L}from"./ToolCallItem-C9ksVseJ.js";import{t as Te}from"./QScrollArea-CUDWkEHh.js";import{a as R}from"./use-tours-C_5R1Y5D.js";import{f as Ee,g as z,m as De,p as Oe,u as ke}from"./use-worktree-restore-Cj8eGE9s.js";import{t as B}from"./QTooltip-B5QJIU3p.js";import{n as V}from"./render-chat-markdown-C9ihAYBt.js";import{n as H}from"./WorkspacePage-BTJnap9a.js";function Ae(e,t){return s(`div`,e,[s(`table`,{class:`q-table`},t)])}var je={list:xe,table:Se},U=[`list`,`table`,`__qtable`],Me=ae({name:`QVirtualScroll`,props:{...M,type:{type:String,default:`list`,validator:e=>U.includes(e)},items:{type:Array,default:()=>[]},itemsFn:Function,itemsSize:Number,scrollTarget:ge},setup(e,{slots:n,attrs:r}){let i,a=t(null),o=x(()=>e.itemsSize>=0&&e.itemsFn!==void 0?Number.parseInt(e.itemsSize,10):Array.isArray(e.items)?e.items.length:0),{virtualScrollSliceRange:c,localResetVirtualScroll:u,padVirtualScroll:f,onVirtualScrollEvt:m}=j({virtualScrollLength:o,getVirtualScrollTarget:y,getVirtualScrollEl:v}),h=x(()=>{if(o.value===0)return[];let t=(e,t)=>({index:c.value.from+t,item:e});return e.itemsFn===void 0?e.items.slice(c.value.from,c.value.to).map(t):e.itemsFn(c.value.from,c.value.to-c.value.from).map(t)}),g=x(()=>`q-virtual-scroll q-virtual-scroll`+(e.virtualScrollHorizontal?`--horizontal`:`--vertical`)+(e.scrollTarget===void 0?` scroll`:``)),_=x(()=>e.scrollTarget===void 0?{tabindex:0}:{});l(o,()=>{u()}),l(()=>e.scrollTarget,()=>{C(),S()});function v(){let e=a.value;return e===null?null:e.$el||e}function y(){return i}function S(){let t=v();t!==null&&(i=A(t,e.scrollTarget),i.addEventListener(`scroll`,m,E.passive))}function C(){i!==void 0&&(i.removeEventListener(`scroll`,m,E.passive),i=void 0)}function w(){let t=f(e.type===`list`?`div`:`tbody`,h.value.map(n.default));return n.before!==void 0&&(t=n.before().concat(t)),ie(n.after,t)}return u(),p(()=>{S()}),b(()=>{S()}),d(()=>{C()}),ee(()=>{C()}),()=>{if(n.default===void 0)console.error(`QVirtualScroll: default scoped slot is required for rendering`);else return e.type===`__qtable`?Ae({ref:a,class:`q-table__middle `+g.value,tabindex:0},w()):s(je[e.type],{...r,ref:a,class:[r.class,g.value],..._.value},w)}}}),Ne=new Set([`setup`,`cleanup`,`archive`]);function Pe(e){switch(e.type){case`user`:return e.sender===`system-prompt`?`system-prompt`:Ne.has(e.sender)||le(e.sender)?`script`:`user`;case`session`:return`session`;default:return`agent`}}function Fe(e){let t=[],n=null,r=null;for(let i of e){let e=Pe(i),a=e===`session`||e===`system-prompt`,o=e===`script`&&i.type===`user`?`script:${i.sender}`:e===`user`&&i.type===`user`&&i.source?JSON.stringify([`mcp`,i.source.transport,i.source.clientName]):e;!n||r!==o||a?(n={speaker:e,ts:i.ts,items:[i]},r=o,t.push(n),a&&(n=null)):n.items.push(i)}return t}function W(e){switch(e.type){case`text`:return`text:${e.messageId}`;case`thinking`:return`thinking:${e.messageId}:${e.eventIds?.[0]??e.ts??``}`;case`tool`:return`tool:${e.toolCallId}`;case`session`:return`session:${e.kind}:${e.eventIds?.[0]??e.ts??``}`;case`user`:return`user:${e.eventIds?.[0]??`${e.sender}:${e.ts??``}`}`;case`error`:return`error:${e.eventIds?.[0]??`${e.category}:${e.ts??``}`}`}}function Ie(e){let t=e.items[0];return`${e.speaker}:${t?W(t):`empty`}`}function Le(e,t){let n=Math.min(t,e.length)-1;for(let t=n;t>=0;t--)if(e[t]?.speaker===`user`)return t;return-1}function Re(e,t,n){return new Promise(r=>{let i=!1,a=null,o=null,s=e=>{i||(i=!0,a!==null&&clearTimeout(a),a=null,o?.(),r(e))};o=l(e,e=>{t(e)&&s(!0)},{immediate:!0}),i?o():a=setTimeout(()=>s(!1),n)})}var ze={class:`agent-error-item`},Be={class:`agent-error-item__body`},Ve={class:`agent-error-item__title`},G={class:`agent-error-item__message`},He=R(T({__name:`AgentErrorItem`,props:{item:{}},setup(e){let t=e,{te:n}=fe(),i=x(()=>{let e=`agent.error.${t.item.category}`;return n(e)?e:`agent.error.other`});return(t,n)=>(u(),S(`div`,ze,[g(oe,{name:`error_outline`,size:`16px`,class:`agent-error-item__icon`}),w(`div`,Be,[w(`div`,Ve,r(t.$t(i.value)),1),w(`div`,G,r(e.item.message),1)])]))}}),[[`__scopeId`,`data-v-e98c3c02`]]),Ue={class:`text-caption text-kobo-3`},We=T({__name:`SessionEventItem`,props:{item:{}},setup(e){let t=e,n=x(()=>{switch(t.item.kind){case`started`:return`session.started`;case`ended`:return z(t.item.detail);case`compacted`:return`session.compacted`;default:return`session.started`}});return(e,t)=>(u(),S(`span`,Ue,r(e.$t(n.value)),1))}}),K=new Map;function q(){for(let e of K.values())e.controller.abort(),e.blobUrl&&URL.revokeObjectURL(e.blobUrl);K.clear()}typeof window<`u`&&(window.addEventListener(`kobo:auth-token-changed`,q),window.addEventListener(`storage`,e=>{(e.key===`kobo:network-token`||e.key===null)&&q()}));function J(e){if(!/^\/api\/workspaces\/[^/]+\/images\/file\?path=/.test(e))throw Error(`Invalid workspace image URL`);let t=K.get(e);if(!t){let n=new AbortController,r={references:0,controller:n,ready:Promise.resolve(``)};r.ready=ce(e,{signal:n.signal}).then(async e=>{if(!e.ok)throw await O(e);let t=await e.blob();if(n.signal.aborted)throw new DOMException(`Aborted`,`AbortError`);return r.blobUrl=URL.createObjectURL(t),r.blobUrl}),t=r,K.set(e,t)}t.references++;let n=!1,r=t;return{ready:r.ready,release(){n||(n=!0,!(--r.references>0)&&(r.controller.abort(),r.blobUrl&&URL.revokeObjectURL(r.blobUrl),K.get(e)===r&&K.delete(e)))}}}function Ge(e,t){let n=o(``),r=o(0),a=()=>{r.value++},s=e=>{(e.key===`kobo:network-token`||e.key===null)&&a()};return window.addEventListener(`kobo:auth-token-changed`,a),window.addEventListener(`storage`,s),i(()=>{window.removeEventListener(`kobo:auth-token-changed`,a),window.removeEventListener(`storage`,s)}),l([e,r],([e],r,i)=>{let a=!0,o=[],s=document.createElement(`template`);s.innerHTML=e;for(let e of s.content.querySelectorAll(`img`)){let r=e.getAttribute(`src`)??``;if(e.removeAttribute(`data-kobo-image-url`),!/^\/api\/workspaces\/[^/]+\/images\/file\?path=/.test(r))continue;e.removeAttribute(`src`);let i=J(r);o.push(i),i.ready.then(t=>{a&&(e.setAttribute(`src`,t),e.setAttribute(`data-kobo-image-url`,r),n.value=s.innerHTML)}).catch(()=>{a&&(e.setAttribute(`title`,t()),n.value=s.innerHTML)})}n.value=s.innerHTML,i(()=>{a=!1;for(let e of o)e.release()})},{immediate:!0}),n}function Ke(e,t){return t?e.replace(/\[image:\s+([^\]]+)\]/g,(e,n)=>{let r=String(n).trim();return/^(\.ai\/images\/|images\/)/.test(r)?`![${r}](${`/api/workspaces/${encodeURIComponent(t)}/images/file?path=${encodeURIComponent(r)}`})`:e}):e}var qe=[`innerHTML`],Je=[`innerHTML`],Ye=[`src`],Y=R(T({__name:`UserMessageItem`,props:{item:{},workspaceId:{}},setup(t){let n=t,{t:r}=fe(),a=x(()=>n.item.sender===`system-prompt`),s=Ge(x(()=>{let e=Ke(n.item.content,n.workspaceId);return V(e)}),()=>r(`network.login.unreachable`)),c=o(null),d=o(!1),f;function p(){d.value=!1,c.value=null,f?.release(),f=void 0}l(d,e=>{e||p()}),l(()=>n.workspaceId,p);let m=e=>{(e.key===`kobo:network-token`||e.key===null)&&p()};window.addEventListener(`storage`,m),window.addEventListener(`kobo:auth-token-changed`,p),i(()=>{p(),window.removeEventListener(`kobo:auth-token-changed`,p),window.removeEventListener(`storage`,m)});function y(e){let t=e.target;if(t?.tagName!==`IMG`)return;let n=t;n.src&&(p(),n.dataset.koboImageUrl&&(f=J(n.dataset.koboImageUrl),f.ready.catch(p)),c.value=n.src,d.value=!0)}return(t,n)=>(u(),S(C,null,[a.value?(u(),_(Ce,{key:0,dense:``,"dense-toggle":``,label:t.$t(`chat.systemPrompt`),"header-class":`text-kobo-2 text-caption`},{default:h(()=>[w(`div`,{class:`q-py-xs markdown-message markdown-user-prompt`,innerHTML:e(s)},null,8,qe)]),_:1},8,[`label`])):(u(),S(`div`,{key:1,class:`markdown-message`,onClick:y},[w(`div`,{innerHTML:e(s)},null,8,Je)])),g(k,{modelValue:d.value,"onUpdate:modelValue":n[1]||=e=>d.value=e},{default:h(()=>[c.value?(u(),S(`img`,{key:0,src:c.value,alt:``,class:`image-lightbox-img`,onClick:n[0]||=e=>d.value=!1},null,8,Ye)):v(``,!0)]),_:1},8,[`modelValue`])],64))}}),[[`__scopeId`,`data-v-965e6297`]]),Xe=[`data-event-ids`],X={class:`turn-header`},Ze={key:0,class:`turn-time`},Qe={class:`turn-time turn-time-updated`},Z={key:2,class:`turn-actions`},$e={class:`turn-body`},et={class:`ellipsis`},tt={class:`q-px-sm q-pb-sm`},nt={key:0,class:`turn-scroll-top`},rt=R(T({__name:`TurnCard`,props:{turn:{},workspaceId:{},highlighted:{type:Boolean}},emits:[`scrollTo`],setup(t,{emit:i}){let s=t,c=i,{t:l}=fe(),d=de(),f=m(F),p=o(null);function ee(){let e=p.value;if(!e)return;let t=e.closest(`.q-scrollarea`)?.querySelector(`.q-scrollarea__content`);if(!t){e.scrollIntoView({behavior:`smooth`,block:`start`});return}let n=e.getBoundingClientRect().top-t.getBoundingClientRect().top;c(`scrollTo`,Math.max(0,n-8))}let b=x(()=>{switch(s.turn.speaker){case`user`:{let e=s.turn.items[0],t=e?.type===`user`?e.source:void 0;return{label:t?l(`chat.externalLlm`,{name:t.clientName}):l(`chat.you`),accent:`var(--kobo-turn-user)`,badgeClass:`turn-badge-user`}}case`agent`:return{label:l(`chat.agent`),accent:`var(--kobo-turn-agent)`,badgeClass:`turn-badge-agent`};case`system-prompt`:return{label:l(`chat.systemPrompt`),accent:`var(--kobo-text-3)`,badgeClass:`turn-badge-system`};case`session`:return{label:l(`chat.session`),accent:`var(--kobo-text-3)`,badgeClass:`turn-badge-session`};case`script`:{let e=s.turn.items[0],t=e?.type===`user`?e.sender:``;return{label:le(t)?l(`chat.hookScript`,{event:t.slice(5)}):l(t===`archive`?`chat.archiveScript`:t===`setup`?`chat.setupScript`:`chat.cleanupScript`),accent:`var(--kobo-success)`,badgeClass:`turn-badge-script`}}}});function T(e,t=!1){if(!e)return``;let n=new Date(e);return Number.isNaN(n.getTime())?``:n.toLocaleTimeString(void 0,t?{hour:`2-digit`,minute:`2-digit`,second:`2-digit`}:{hour:`2-digit`,minute:`2-digit`})}let re=x(()=>T(s.turn.ts)),E=x(()=>s.turn.items.flatMap(e=>e.eventIds??[]).join(` `)),ie=x(()=>{let e=s.turn.items;if(e.length===0)return null;for(let t=e.length-1;t>=0;t--){let n=e[t].ts;if(n)return n}return null}),ae=x(()=>{let e=s.turn.ts,t=ie.value;if(!t||!e||t===e)return``;let n=new Date(e).getTime(),r=new Date(t).getTime();return Number.isNaN(n)||Number.isNaN(r)||r<=n?``:T(t,r-n<6e4)}),se=x(()=>ae.value!==``);function ce(e){return we(e.origin,Object.values(d.subagents[s.workspaceId]??{}),e.type===`tool`?e.toolCallId:void 0)}function O(e){return e||l(`chat.subagentActivity`)}function ue(e){return(e.type===`text`||e.type===`thinking`||e.type===`tool`)&&e.origin?.kind===`subagent`}let pe=x(()=>{let e=[],t=new Map;for(let n of s.turn.items){if(!ue(n)){e.push({type:`item`,key:W(n),item:n});continue}let r=ce(n),i=r?.toolUseId??n.origin?.toolCallId??n.origin?.threadId??W(n),a=t.get(i);if(a){a.items.push(n);continue}let o={type:`subagent`,key:`subagent:${W(n)}`,items:[n],subagentKey:i,subagentName:r?.description||null};t.set(i,o),e.push(o)}return e});function me(e){e.origin&&f?.({origin:e.origin})}let he=x(()=>s.turn.items.filter(e=>e.type===`tool`).length);return(i,o)=>(u(),S(`div`,{ref_key:`cardEl`,ref:p,class:a([`turn-card`,{"turn-card--user":t.turn.speaker===`user`,"turn-card--highlighted":t.highlighted}]),"data-event-ids":E.value,style:n({"--turn-accent":b.value.accent})},[w(`div`,X,[w(`span`,{class:a([`turn-badge`,b.value.badgeClass])},r(b.value.label),3),re.value?(u(),S(`span`,Ze,r(re.value),1)):v(``,!0),se.value?(u(),S(C,{key:1},[g(oe,{name:`arrow_forward`,size:`10px`,color:`kobo-3`,class:`turn-time-arrow`}),w(`span`,Qe,[y(r(ae.value)+` `,1),g(B,null,{default:h(()=>[y(r(e(l)(`chat.lastUpdatedAt`,{time:ae.value})),1)]),_:1})])],64)):v(``,!0),he.value>0?(u(),S(`span`,Z,` · `+r(e(l)(`chat.nActions`,{n:he.value})),1)):v(``,!0)]),w(`div`,$e,[(u(!0),S(C,null,te(pe.value,n=>(u(),S(C,{key:n.key},[n.type===`subagent`?(u(),_(Ce,{key:0,class:`turn-subagent-activity`,dense:``,label:O(n.subagentName),caption:e(l)(`chat.nActions`,{n:n.items.length}),"default-opened":t.highlighted&&n.items.some(e=>e.eventIds?.some(e=>E.value.includes(e)))},{header:h(()=>[g(P,null,{default:h(()=>[g(N,{class:`row items-center no-wrap`,title:n.subagentName??void 0},{default:h(()=>[g(oe,{name:`hub`,size:`16px`,class:`q-mr-xs`},{default:h(()=>[g(B,null,{default:h(()=>[y(r(e(l)(`chat.subagentActivity`)),1)]),_:1})]),_:1}),w(`span`,et,r(O(n.subagentName)),1)]),_:2},1032,[`title`]),g(N,{caption:``},{default:h(()=>[y(r(e(l)(`chat.nActions`,{n:n.items.length})),1)]),_:2},1024)]),_:2},1024),g(P,{side:``},{default:h(()=>[g(D,{flat:``,round:``,dense:``,size:`sm`,icon:`open_in_new`,"aria-label":e(l)(`chat.openSubagentActivity`),onClick:ne(e=>me(n.items[0]),[`stop`])},{default:h(()=>[g(B,null,{default:h(()=>[y(r(e(l)(`chat.openSubagentActivity`)),1)]),_:1})]),_:1},8,[`aria-label`,`onClick`])]),_:2},1024)]),default:h(()=>[w(`div`,tt,[(u(!0),S(C,null,te(n.items,t=>(u(),S(C,{key:e(W)(t)},[t.type===`text`?(u(),_(I,{key:0,item:t},null,8,[`item`])):t.type===`thinking`?(u(),_(H,{key:1,item:t},null,8,[`item`])):t.type===`tool`?(u(),_(L,{key:2,item:t},null,8,[`item`])):v(``,!0)],64))),128))])]),_:2},1032,[`label`,`caption`,`default-opened`])):(u(),S(C,{key:1},[n.item.type===`text`?(u(),_(I,{key:0,item:n.item},null,8,[`item`])):n.item.type===`thinking`?(u(),_(H,{key:1,item:n.item},null,8,[`item`])):n.item.type===`tool`?(u(),_(L,{key:2,item:n.item},null,8,[`item`])):n.item.type===`user`?(u(),_(Y,{key:3,item:n.item,"workspace-id":t.workspaceId},null,8,[`item`,`workspace-id`])):n.item.type===`session`?(u(),_(We,{key:4,item:n.item},null,8,[`item`])):n.item.type===`error`?(u(),_(He,{key:5,item:n.item},null,8,[`item`])):v(``,!0)],64))],64))),128))]),t.turn.items.length>4?(u(),S(`div`,nt,[g(D,{flat:``,round:``,dense:``,size:`xs`,icon:`arrow_upward`,color:`kobo-3`,class:`turn-scroll-top-btn`,onClick:ee},{default:h(()=>[g(B,null,{default:h(()=>[y(r(e(l)(`chat.scrollToTurnTop`)),1)]),_:1})]),_:1})])):v(``,!0)],14,Xe))}}),[[`__scopeId`,`data-v-13bebb3d`]]),it={key:0,class:`activity-feed-switching`},at={key:1,class:`activity-feed-wrap`,"data-tour":`ws-chat`},ot={key:0,role:`alert`,class:`q-pa-sm text-negative`},st={key:1,"data-testid":`history-load-error`,role:`alert`,class:`q-pa-sm text-negative`},ct=[`aria-label`],lt={key:0,class:`activity-feed-compacting`},ut={class:`text-caption text-kobo-2`},dt={key:0,class:`text-center q-py-sm text-caption text-kobo-3`},ft={key:1,class:`activity-feed-empty q-pa-md`},pt={class:`activity-feed-empty__title`},mt={class:`activity-feed-empty__hint`},ht={key:2,class:`q-px-md q-pb-md`},gt=[`title`],_t={class:`ellipsis`},vt={class:`activity-feed-nav-cluster`},yt=300,bt=60,xt=200,St=180,Ct=255,wt=3,Tt=60,Et=R(T({__name:`ActivityFeed`,props:{workspaceId:{}},setup(t){let{t:n}=fe(),i=t,a=ue(),s=pe(),d=de(),m=me(),ee=x(()=>m.isCompacting(i.workspaceId)),b=x(()=>d.selectedSessionId),T=x(()=>d.sessions.find(e=>e.id===b.value)?.engineSessionId??null),ne=x(()=>{let e=d.sessions;return e.length!==0&&b.value===e[e.length-1].id});function E(e){return b.value?e?e===b.value||e===T.value:ne.value:!0}let ie=x(()=>(d.activityFeeds[i.workspaceId]??[]).filter(e=>e.type===`text`&&typeof e.content==`string`&&E(e.sessionId)).map(e=>({content:e.content,sender:e.meta?.sender??`user`,source:ye(e.meta?.source),ts:e.timestamp,sessionId:e.sessionId,eventIds:[e.id]}))),ae=x(()=>{let e=d.workspaces.find(e=>e.id===i.workspaceId);return ve(e?.status)}),le=ke(),ge=``,k=x(()=>{let e=a.eventsFor(i.workspaceId),t=a.timestampsFor(i.workspaceId),n=a.sessionIdsFor(i.workspaceId),r=a.eventIdsFor(i.workspaceId),o=[],c=[],l=[];for(let i=0;i<e.length;i++)E(n[i])&&(o.push(e[i]),c.push(t[i]),l.push(r[i]??null));let u=`${i.workspaceId}::${b.value??`*`}`;u!==ge&&(le=ke(),ge=u);let d=Ee(le,o,c,ae.value,l);return Fe(De(d,ie.value).filter(e=>e.type===`thinking`?!1:e.type===`session`?e.kind===`ended`?s.showVerboseSystemMessages||!Oe(e.detail):s.showVerboseSystemMessages:!0))}),A=x(()=>s.showVerboseSystemMessages?a.eventsFor(i.workspaceId).filter(e=>e.kind===`message:raw`).map(e=>e.content):[]),xe=x(()=>{for(let e=k.value.length-1;e>=0;e--){let t=k.value[e]?.items??[];for(let e=t.length-1;e>=0;e--){let r=t[e];if(r){if(r.type===`text`&&!r.streaming)return{key:`text:${r.messageId}`,text:r.text};if(r.type===`tool`)return r.result?{key:`tool:${r.toolCallId}:end`,text:r.result.isError?n(`activity.a11y.toolFailed`,{name:r.name}):n(`activity.a11y.toolFinished`,{name:r.name})}:{key:`tool:${r.toolCallId}:start`,text:n(`activity.a11y.toolStarted`,{name:r.name})}}}}return null}),Se=o(``);l(xe,(e,t)=>{if(!e||e.key===t?.key)return;let r=e.key.startsWith(`text:`)?n(`activity.a11y.agentSaid`,{text:e.text.slice(0,yt)}):e.text;Se.value=r});let j=o(null),M=o(!0),N=o(!1),P=o(``),F=_e(),we=_e(),I=_e(),L=o(null),R=!1,z=!0,B=null,V=o(new Set),H=new Map,Ae=x(()=>{if(k.value.length>0||A.value.length>0)return!1;let e=i.workspaceId,t=b.value;return!!d.loadingSessions[e]||m.isSyncPending(e)||t!==null&&V.value.has(W(e,t))}),je=o(new Map),U=x(()=>k.value.length>0||A.value.length>0),Ne=x(()=>!!d.loadingSessions[i.workspaceId]||m.isSyncPending(i.workspaceId)||b.value!==null&&V.value.has(W(i.workspaceId,b.value)));function Pe(e){et();let t=B;if(B=e.verticalSize,t!==null&&e.verticalSize>t&&M.value&&!N.value){q(0);return}let n=e.verticalSize-e.verticalPosition-e.verticalContainerSize;M.value=n<=bt,R&&e.verticalPosition<=xt&&!N.value&&!P.value&&ze()&&He()}function W(e,t){return`${e}:${t}`}function ze(){let e=b.value;return e?je.value.get(W(i.workspaceId,e))??!0:a.hasMoreOlderFor(i.workspaceId)}function Be(e,t,n){je.value.set(W(e,t),n)}function Ve(e){if(!b.value)return a.oldestIdFor(e);let t=a.eventIdsFor(e),n=a.sessionIdsFor(e);for(let e=0;e<t.length;e++){if(!E(n[e]))continue;let r=t[e];if(r)return r}}let G=null;function He(){if(G)return G;let e=K().finally(()=>{G===e&&(G=null)});return G=e,G}function Ue(){F.abort(),we.abort(),I.abort(),N.value=!1,G=null;for(let e of V.value)Ft.delete(e);H.clear(),V.value.clear(),P.value=``}function We(){P.value=``,Mt.value===0&&b.value?It():He()}l(()=>[i.workspaceId,b.value],Ue,{flush:`sync`});async function K(){let e=i.workspaceId,t=b.value,n=Ve(e);if(!n)return;let r=F.begin();P.value=``,N.value=!0;try{let i=j.value;if(await f(),!F.isCurrent(r))return;let o=i?.getScroll().verticalSize??0,s=i?.getScroll().verticalPosition??0,c=new URLSearchParams({before:n,limit:`200`});t&&c.set(`session`,t);let l=await ce(`/api/workspaces/${e}/events?${c.toString()}`,{signal:r});if(!l.ok)throw await O(l);let u=await l.json();if(!F.isCurrent(r))return;let p=u.events??[],m=p.filter(t=>t.type===`agent:event`&&t.workspaceId===e),h=p.filter(t=>t.type===`user:message`&&t.workspaceId===e),g=m.map(e=>e.payload),_=m.map(e=>e.createdAt),v=m.map(e=>e.sessionId??null),y=m.map(e=>e.id),ee=p.length>0?p[0].id:n;t&&Be(e,t,u.hasMore),a.prepend(e,g,_,{oldestId:ee,hasMoreOlder:t?a.hasMoreOlderFor(e):u.hasMore,sessionIds:v,eventIds:y});for(let t of h){let n=t.payload;typeof n.content==`string`&&d.addActivityItem(e,{id:t.id,type:`text`,content:n.content,timestamp:t.createdAt,sessionId:t.sessionId??void 0,meta:{sender:n.sender??`user`,source:ye(n.source)}})}if(await f(),i&&F.isCurrent(r)){let e=i.getScroll().verticalSize-o;if(e>0){let t=Math.max(s+e,250);i.setScrollPosition(`vertical`,t,0)}}}catch(e){if(he(e)||!F.isCurrent(r))return;P.value=e instanceof Error?e.message:String(e),console.error(`[ActivityFeed] failed to load older events:`,e)}finally{F.isCurrent(r)&&(N.value=!1)}}async function q(e=0){let t=Pt();if(await f(),t!==Pt())return;let n=j.value;if(!n)return;let r=n.getScroll();n.setScrollPosition(`vertical`,r.verticalSize,e)}let J=null,Ge=0;function Ke(){if(J!=null)return;let e=performance.now()-Ge<St,t=Pt();J=requestAnimationFrame(()=>{J=null,t===Pt()&&(Ge=performance.now(),q(e?0:180))})}function qe(e){$(),M.value=!1;let t=j.value;t&&t.setScrollPosition(`vertical`,Math.max(0,e),250)}function Je(){let e=window.location.hash,t=e.indexOf(`?`);if(t===-1||!new URLSearchParams(e.slice(t+1)).has(`eventId`))return;let n=e.slice(0,t);window.history.replaceState(window.history.state,``,`${window.location.pathname}${window.location.search}${n}`)}async function Ye(e){let t=e.detail;if(t?.workspaceId!==i.workspaceId||!t.eventId)return;$(),M.value=!1;let n=I.begin(),r=i.workspaceId;try{let e=new URLSearchParams({around:t.eventId,limit:`200`});t.sessionId&&e.set(`session`,t.sessionId);let o=await ce(`/api/workspaces/${r}/events?${e}`,{signal:n});if(!o.ok)throw await O(o);let s=await o.json();if(!I.isCurrent(n))return;let c=s.events??[],l=c.filter(e=>e.type===`agent:event`&&e.workspaceId===i.workspaceId),u=c.filter(e=>e.type===`user:message`&&e.workspaceId===i.workspaceId);a.reset(i.workspaceId,l.map(e=>e.payload),l.map(e=>e.createdAt),{oldestId:c[0]?.id,hasMoreOlder:s.hasMore??!1,sessionIds:l.map(e=>e.sessionId),eventIds:l.map(e=>e.id)});for(let e of u){let t=e.payload;typeof t.content==`string`&&d.addActivityItem(i.workspaceId,{id:e.id,type:`text`,content:t.content,timestamp:e.createdAt,sessionId:e.sessionId??void 0,meta:{sender:t.sender??`user`,source:ye(t.source)}})}if(await Re(Ae,e=>!e,5e3),await f(),await new Promise(e=>requestAnimationFrame(()=>requestAnimationFrame(()=>e()))),!I.isCurrent(n))return;let p=k.value.findIndex(e=>e.items.some(e=>e.eventIds?.includes(t.eventId)??!1));if(p<0)return;L.value=t.eventId,Y.value?.scrollTo(p,`center`),X.value=p,window.setTimeout(()=>{L.value===t.eventId&&(L.value=null)},1800)}catch(e){if(he(e)||!I.isCurrent(n))return;P.value=e instanceof Error?e.message:String(e),console.error(`[ActivityFeed] failed to focus history event:`,e)}}let Y=o(null),Xe=o(null),X=o(0),Ze=o(!1),Qe=x(()=>{for(let e=k.value.length-1;e>=0;e--)if(k.value[e]?.speaker===`user`)return e;return-1}),Z=null;function $e(){let e=Qe.value,t=j.value?.getScrollTarget(),n=t?.querySelector(`[data-turn-index="${e}"]`);if(!n||!t){Ze.value=!1;return}let r=t.getBoundingClientRect(),i=n.getBoundingClientRect();Ze.value=i.bottom>r.top&&i.top<r.bottom}function et(){Z===null&&(Z=requestAnimationFrame(()=>{Z=null,$e()}))}function tt(e){return e.length<=Ct?e:`${e.slice(0,252)}...`}let nt=x(()=>{let e=Qe.value,t=k.value[e];if(!t||X.value<=e||Ze.value)return null;let n=t.items.filter(e=>e.type===`user`).map(e=>e.content).join(`
2
+ `).trim();return n?{index:e,content:tt(n)}:null});function Et(e){X.value=e.index,et()}function Dt(){let e=nt.value;e&&($(),M.value=!1,Y.value?.scrollTo(e.index,`start`),X.value=e.index)}let Ot=o(!1);async function kt(){if(Ot.value)return;$(),M.value=!1,Ot.value=!0;let e=i.workspaceId;try{let t=Le(k.value,X.value);if(t<0)for(let n=0;n<15;n++){if(e!==i.workspaceId)return;if(!ze())break;let n=k.value.length;if(await He(),await f(),e!==i.workspaceId)return;if(X.value+=k.value.length-n,t=Le(k.value,X.value),t>=0)break}t>=0&&e===i.workspaceId&&(Y.value?.scrollTo(t,`start`),X.value=t)}finally{Ot.value=!1}}let Q=0;function At(){return new Promise(e=>requestAnimationFrame(()=>e()))}async function jt(){if(!z||!U.value||Ae.value)return;R=!1;let e=++Q;if(await f(),e!==Q||!z||(Xe.value=j.value?.getScrollTarget()??null,await f(),e!==Q||!z))return;k.value.length>0&&Y.value?.scrollTo(k.value.length-1,`end-force`),await At();let t=-1,n=0;for(let r=0;r<Tt&&n<wt;r++){if(e!==Q||!z)return;await q(0),await At();let r=j.value?.getScroll().verticalSize??0;B=r,n=r===t?n+1:0,t=r}e===Q&&!Ne.value&&(R=!0,z=!1)}function $(){z=!1,!R&&(Q++,R=!0)}let Mt=x(()=>{let e=a.sessionIdsFor(i.workspaceId);if(!b.value)return e.length;let t=0;for(let n of e)E(n)&&t++;return t});l(Ae,async e=>{!e&&U.value&&await jt(),e||f(()=>{Xe.value=j.value?.getScrollTarget()??null,et()})}),l(Ne,(e,t)=>{!e&&t&&z&&U.value&&jt()}),p(()=>{window.addEventListener(`kobo:focus-history-event`,Ye),f(()=>{Xe.value=j.value?.getScrollTarget()??null,et()}),U.value&&jt(),b.value&&It();let e=new URLSearchParams(window.location.hash.split(`?`)[1]??``).get(`eventId`);e&&(Ye(new CustomEvent(`kobo:focus-history-event`,{detail:{workspaceId:i.workspaceId,sessionId:b.value,eventId:e}})),Je())}),l([Mt,()=>ie.value.length,()=>A.value.length],async([e,t,n])=>{N.value||z&&e+t+n>0&&await jt()}),l([Qe,()=>k.value.length],()=>{f(et)});let Nt=x(()=>a.liveAppendCountFor(i.workspaceId,E)),Pt=()=>`${i.workspaceId}\u0000${d.selectedSessionId??``}`;l([Nt,Pt],([e,t],[n,r])=>{t===r&&e>n&&M.value&&!N.value&&Ke()}),c(()=>{Q++,z=!1,Ue(),window.removeEventListener(`kobo:focus-history-event`,Ye),J!=null&&(cancelAnimationFrame(J),J=null),Z!==null&&cancelAnimationFrame(Z)}),l([()=>i.workspaceId,()=>d.selectedSessionId],()=>{Q++,z=!0,B=null,M.value=!0,R=!1,It(),U.value&&jt()});let Ft=new Set;async function It(){let e=i.workspaceId,t=b.value;if(!t||Mt.value>0)return;let n=W(e,t);if(Ft.has(n))return;Ft.add(n),V.value.add(n);let r=we.begin();H.set(n,r),P.value=``;try{let n=await ce(`/api/workspaces/${e}/events?session=${encodeURIComponent(t)}&limit=500`,{signal:r});if(!n.ok)throw await O(n);let i=await n.json();if(!we.isCurrent(r))return;let o=i.events??[];if(o.length===0)return;let s=o.filter(t=>t.type===`agent:event`&&t.workspaceId===e),c=o.filter(t=>t.type===`user:message`&&t.workspaceId===e),l=s.map(e=>e.payload),u=s.map(e=>e.createdAt),f=s.map(e=>e.sessionId??null),p=s.map(e=>e.id);Be(e,t,i.hasMore),l.length>0&&a.prepend(e,l,u,{oldestId:o[0].id,hasMoreOlder:a.hasMoreOlderFor(e),sessionIds:f,eventIds:p});for(let t of c){let n=t.payload;typeof n.content==`string`&&d.addActivityItem(e,{id:t.id,type:`text`,content:n.content,timestamp:t.createdAt,sessionId:t.sessionId??void 0,meta:{sender:n.sender??`user`,source:ye(n.source)}})}}catch(e){if(he(e)||!we.isCurrent(r)){H.has(n)&&H.get(n)!==r||Ft.delete(n);return}P.value=e instanceof Error?e.message:String(e),console.error(`[ActivityFeed] fetchSessionIfMissing failed:`,e),Ft.delete(n)}finally{H.get(n)===r&&(H.delete(n),V.value.delete(n))}}let Lt=x(()=>ie.value.filter(e=>e.sender===`user`).length),Rt=x(()=>(d.activityFeeds[i.workspaceId]??[]).filter(e=>e.meta?.sender===`user`&&e.meta.pending&&E(e.sessionId)).length);l([Lt,Rt,Pt],async([e,t,n],[r,i,a])=>{let o=t>i;n===a&&(o||e>r&&M.value&&!N.value&&!Ne.value)&&(M.value=!0,await q(o?180:0))});async function zt(){M.value=!0,await q(250)}return(t,n)=>Ae.value?(u(),S(`div`,it,[g(be,{size:`40px`,color:`primary`})])):(u(),S(`div`,at,[e(d).sessionsErrors[i.workspaceId]?(u(),S(`div`,ot,[y(r(t.$t(`activity.sessionsError`,{message:e(d).sessionsErrors[i.workspaceId]}))+` `,1),g(D,{flat:``,dense:``,label:t.$t(`common.retry`),onClick:n[0]||=t=>e(d).fetchSessions(i.workspaceId)},null,8,[`label`])])):v(``,!0),P.value?(u(),S(`div`,st,[y(r(t.$t(`activity.historyError`,{message:P.value}))+` `,1),g(D,{flat:``,dense:``,label:t.$t(`common.retry`),onClick:We},null,8,[`label`])])):v(``,!0),w(`div`,{"data-testid":`activity-live-region`,class:`sr-only`,role:`log`,"aria-live":`polite`,"aria-atomic":`true`,"aria-label":t.$t(`activity.a11y.region`)},r(Se.value),9,ct),g(re,{name:`fade`},{default:h(()=>[ee.value?(u(),S(`div`,lt,[g(be,{size:`18px`,color:`primary`}),w(`span`,ut,r(t.$t(`activity.compacting`)),1)])):v(``,!0)]),_:1}),g(Te,{ref_key:`scrollRef`,ref:j,class:`activity-feed-scroll`,onScroll:Pe,onWheelPassive:$,onTouchstartPassive:$,onKeydown:$},{default:h(()=>[N.value?(u(),S(`div`,dt,[g(se,{size:`sm`}),y(` `+r(t.$t(`activity.loading_older`)),1)])):v(``,!0),g(Me,{ref_key:`virtualScrollRef`,ref:Y,class:`q-pa-md`,items:k.value,"scroll-target":Xe.value??void 0,"virtual-scroll-item-size":160,"virtual-scroll-slice-size":20,onVirtualScroll:Et},{default:h(({item:t,index:n})=>[(u(),_(rt,{key:e(Ie)(t),turn:t,"workspace-id":i.workspaceId,"data-turn-index":n,highlighted:t.items.some(e=>e.eventIds?.includes(L.value??``)??!1),onScrollTo:qe},null,8,[`turn`,`workspace-id`,`data-turn-index`,`highlighted`]))]),_:1},8,[`items`,`scroll-target`]),k.value.length===0&&A.value.length===0&&!N.value?(u(),S(`div`,ft,[g(oe,{name:`forum`,size:`28px`}),w(`div`,pt,r(t.$t(`activity.empty`)),1),w(`div`,mt,r(t.$t(`activity.emptyHint`)),1)])):v(``,!0),A.value.length?(u(),S(`div`,ht,[g(Ce,{label:t.$t(`activity.raw_lines`,{n:A.value.length}),dense:``},{default:h(()=>[(u(!0),S(C,null,te(A.value,(e,t)=>(u(),S(`div`,{key:t,class:`text-caption text-kobo-3 q-pa-xs`},r(e),1))),128))]),_:1},8,[`label`])])):v(``,!0)]),_:1},512),nt.value?(u(),S(`button`,{key:2,type:`button`,class:`activity-feed-last-user`,"data-testid":`latest-user-message`,title:nt.value.content,onClick:Dt},[g(oe,{name:`person`,size:`16px`}),w(`span`,_t,r(nt.value.content),1)],8,gt)):v(``,!0),w(`div`,vt,[M.value?v(``,!0):(u(),_(D,{key:0,round:``,dense:``,unelevated:``,color:`kobo-surface-2`,"text-color":`kobo-1`,icon:`arrow_downward`,size:`sm`,class:`activity-feed-nav-btn`,title:t.$t(`activity.scroll_to_bottom`),"aria-label":t.$t(`activity.scroll_to_bottom`),onClick:zt},null,8,[`title`,`aria-label`])),g(D,{round:``,dense:``,unelevated:``,color:`kobo-surface-2`,"text-color":`kobo-1`,icon:`arrow_upward`,size:`sm`,class:`activity-feed-nav-btn`,title:t.$t(`activity.prev_user_message`),"aria-label":t.$t(`activity.prev_user_message`),loading:Ot.value,disable:Ot.value,onClick:kt},null,8,[`title`,`aria-label`,`loading`,`disable`])])]))}}),[[`__scopeId`,`data-v-58324159`]]);export{Et as default};
@@ -1 +1 @@
1
- import{M as e,pt as t,v as n}from"./reactivity.esm-bundler-DY1JkAEA.js";import{J as r,L as i,Y as a,_ as o,d as s,f as c,l,p as u,r as d,u as f,y as p}from"./runtime-core.esm-bundler-Bqg71Vll.js";import{t as m}from"./QBtn-BUSdPaf4.js";import{Mt as h,Qt as g,f as _,gn as v,j as y,jt as b,l as x,tn as S,vn as C}from"./index-CNcXBpzz.js";import{t as w}from"./QSelect-DrLpbvcr.js";import{a as T}from"./use-tours-B0KtCS9w.js";import{i as E,t as D}from"./permissionModes-DLWiBfdb.js";import{t as O}from"./efforts-B3I7w5bs.js";function k(e,t){return e.engine!==t.engine||e.model!==t.model||e.reasoningEffort!==t.reasoningEffort||e.agentPermissionMode!==t.agentPermissionMode}var A={class:`text-h6`},j={class:`text-body2 text-kobo-2 q-mt-xs`},ee={class:`row q-col-gutter-sm q-mb-md`},te={class:`col-12 col-sm-6`},ne={class:`col-12 col-sm-6`},M={class:`col-12 col-sm-6`},N={class:`col-12 col-sm-6`},P={key:0,class:`text-caption text-kobo-3 q-mt-xs`},F={class:`text-caption text-kobo-3 q-mt-xs`},I={class:`text-caption text-kobo-3 q-mt-xs`},L=T(p({__name:`StartReviewDialog`,props:{modelValue:{type:Boolean},loading:{type:Boolean},workspace:{},canReturnToSession:{type:Boolean},currentSession:{},mode:{},scheduledConfiguration:{}},emits:[`update:modelValue`,`submit`],setup(e,{emit:p}){let T=e,L=p,{t:R}=g(),z=y(),B=l(()=>T.mode===`auto-loop`),V=n(``),H=n(!1),U=n(!1),W=n(`claude-code`),G=n(`auto`),K=n(`auto`),q=n(`bypass`),J=l(()=>B.value||U.value?`plan`:q.value),Y=l(()=>({engine:W.value,model:G.value,reasoningEffort:K.value,agentPermissionMode:J.value})),X=l(()=>{let e=T.currentSession;return B.value||U.value||!!T.workspace&&k(Y.value,T.workspace)||!!e?.engine&&e.engine!==W.value||G.value!==`auto`&&!!e?.model&&e.model!==G.value}),Z=l({get:()=>H.value||X.value,set:e=>{H.value=e}}),re=l(()=>[{value:`claude-code`,label:R(`workspacePage.engineClaude`)},{value:`codex`,label:R(`workspacePage.engineCodex`)}]),Q=l(()=>{let e=(E[W.value]??[]).map(e=>({value:e.value,label:R(e.i18nLabelKey)}));T.workspace?.engine===W.value&&!e.some(e=>e.value===T.workspace?.model)&&e.push({value:T.workspace.model,label:T.workspace.model});let t=T.scheduledConfiguration;return t?.engine===W.value&&!e.some(e=>e.value===t.model)&&e.push({value:t.model,label:t.model}),e}),ie=l(()=>(O[W.value]??[]).map(e=>({value:e.value,label:R(e.i18nLabelKey)}))),$=l(()=>(D[W.value]??[]).map(e=>({value:e,label:R(`agentPermissionMode.${e}`)})));r(W,()=>{let e=T.workspace,t=z.global.defaultModelByEngine[W.value];G.value=e?.engine===W.value?e.model:Q.value.some(e=>e.value===t)?t:`auto`,K.value=e?.engine===W.value?e.reasoningEffort:`auto`,$.value.some(e=>e.value===q.value)||(q.value=`plan`)},{flush:`sync`}),r([()=>T.modelValue,()=>T.workspace?.id],([e])=>{if(!e)return;let t=B.value?T.scheduledConfiguration:null;V.value=t?.additionalInstructions??``,H.value=!1,U.value=B.value,T.workspace&&(W.value=T.workspace.engine,G.value=T.workspace.model,K.value=T.workspace.reasoningEffort,q.value=T.workspace.agentPermissionMode),t&&(W.value=t.engine,G.value=t.model,K.value=t.reasoningEffort)},{immediate:!0}),r(()=>T.canReturnToSession,e=>{!e&&!B.value&&(U.value=!1)});function ae(){T.loading||L(`update:modelValue`,!1)}function oe(){!T.loading&&T.workspace&&L(`submit`,{...Y.value,additionalInstructions:V.value,newSession:Z.value,returnToSession:B.value||U.value&&T.canReturnToSession})}return(n,r)=>(i(),s(S,{"model-value":e.modelValue,persistent:e.loading,"onUpdate:modelValue":r[7]||=e=>L(`update:modelValue`,e)},{default:a(()=>[o(v,{class:`review-card text-kobo-1`},{default:a(()=>[o(C,null,{default:a(()=>[f(`div`,A,t(n.$t(B.value?`autoLoop.finalReview.title`:`review.title`)),1),f(`div`,j,t(n.$t(B.value?`autoLoop.finalReview.description`:`review.subtitle`)),1)]),_:1}),o(_,{dark:``}),o(C,{class:`q-pt-md`},{default:a(()=>[f(`div`,ee,[f(`div`,te,[o(w,{modelValue:W.value,"onUpdate:modelValue":r[0]||=e=>W.value=e,options:re.value,label:n.$t(`engine.select`),disable:e.loading,"emit-value":``,"map-options":``,outlined:``,dark:``,dense:``},null,8,[`modelValue`,`options`,`label`,`disable`])]),f(`div`,ne,[o(w,{modelValue:G.value,"onUpdate:modelValue":r[1]||=e=>G.value=e,options:Q.value,label:n.$t(`engine.model`),disable:e.loading,"emit-value":``,"map-options":``,outlined:``,dark:``,dense:``},null,8,[`modelValue`,`options`,`label`,`disable`])]),f(`div`,M,[o(w,{modelValue:K.value,"onUpdate:modelValue":r[2]||=e=>K.value=e,options:ie.value,label:n.$t(`engine.effort`),disable:e.loading,"emit-value":``,"map-options":``,outlined:``,dark:``,dense:``},null,8,[`modelValue`,`options`,`label`,`disable`])]),f(`div`,N,[o(w,{"model-value":J.value,options:$.value,label:n.$t(`agentPermissionMode.label`),disable:e.loading||U.value,"emit-value":``,"map-options":``,outlined:``,dark:``,dense:``,"onUpdate:modelValue":r[3]||=e=>q.value=e},null,8,[`model-value`,`options`,`label`,`disable`]),U.value?(i(),u(`div`,P,t(n.$t(`review.readOnlyForced`)),1)):c(``,!0)])]),o(h,{modelValue:V.value,"onUpdate:modelValue":r[4]||=e=>V.value=e,type:`textarea`,disable:e.loading,label:n.$t(`review.additionalInstructions`),placeholder:n.$t(`review.additionalInstructionsPlaceholder`),rows:4,outlined:``,dark:``,color:`primary`},null,8,[`modelValue`,`disable`,`label`,`placeholder`])]),_:1}),B.value?c(``,!0):(i(),s(C,{key:0,class:`q-pt-none`},{default:a(()=>[o(x,{modelValue:Z.value,"onUpdate:modelValue":r[5]||=e=>Z.value=e,disable:e.loading||X.value,label:n.$t(`review.newSession`),color:`primary`,dark:``},null,8,[`modelValue`,`disable`,`label`]),f(`div`,F,t(n.$t(X.value?`review.newSessionRequired`:`review.newSessionHint`)),1),Z.value?(i(),u(d,{key:0},[o(x,{modelValue:U.value,"onUpdate:modelValue":r[6]||=e=>U.value=e,disable:e.loading||!e.canReturnToSession,label:n.$t(`review.returnToSession`),color:`primary`,dark:``,class:`q-mt-md`},null,8,[`modelValue`,`disable`,`label`]),f(`div`,I,t(n.$t(e.canReturnToSession?`review.returnToSessionHint`:`review.returnUnavailable`)),1)],64)):c(``,!0)]),_:1})),o(b,{align:`right`,class:`q-pa-md`},{default:a(()=>[o(m,{flat:``,"no-caps":``,label:n.$t(`review.cancel`),color:`kobo-2`,disable:e.loading,onClick:ae},null,8,[`label`,`disable`]),o(m,{"no-caps":``,color:`primary`,label:n.$t(B.value?`autoLoop.finalReview.save`:`review.start`),loading:e.loading,disable:!e.workspace||e.loading,onClick:oe},null,8,[`label`,`loading`,`disable`])]),_:1})]),_:1})]),_:1},8,[`model-value`,`persistent`]))}}),[[`__scopeId`,`data-v-a4419350`]]),R={class:`final-review-settings`},z={class:`text-caption text-kobo-3`},B=T(p({__name:`AutoLoopFinalReviewSettings`,props:{modelValue:{},workspace:{},disabled:{type:Boolean},loading:{type:Boolean}},emits:[`update:modelValue`],setup(a,{emit:l}){let d=a,p=l,{t:h}=g(),_=n(!1),v=n(null);r(()=>d.modelValue,e=>{JSON.stringify(e)===JSON.stringify(v.value)&&(v.value=null)}),r(()=>d.workspace.id,()=>{_.value=!1,v.value=null});function y(e){d.disabled||d.loading||(e?_.value=!0:p(`update:modelValue`,null))}function b(e){!d.disabled&&!d.loading&&e.engine&&e.model&&e.reasoningEffort&&(v.value={engine:e.engine,model:e.model,reasoningEffort:e.reasoningEffort,additionalInstructions:e.additionalInstructions??``},p(`update:modelValue`,v.value),_.value=!1)}return(n,r)=>(i(),u(`div`,R,[o(x,{"model-value":!!a.modelValue,disable:a.disabled||a.loading,color:`primary`,label:e(h)(`autoLoop.finalReview.enabled`),"onUpdate:modelValue":y},null,8,[`model-value`,`disable`,`label`]),f(`p`,z,t(e(h)(`autoLoop.finalReview.hint`)),1),a.modelValue?(i(),s(m,{key:0,flat:``,dense:``,"no-caps":``,disable:a.disabled||a.loading,label:e(h)(`autoLoop.finalReview.configure`),onClick:r[0]||=e=>_.value=!0},null,8,[`disable`,`label`])):c(``,!0),o(L,{modelValue:_.value,"onUpdate:modelValue":r[1]||=e=>_.value=e,mode:`auto-loop`,loading:a.loading??!1,workspace:a.workspace,"scheduled-configuration":v.value??a.modelValue,"can-return-to-session":!0,onSubmit:b},null,8,[`modelValue`,`loading`,`workspace`,`scheduled-configuration`])]))}}),[[`__scopeId`,`data-v-b7f36f83`]]);export{L as n,B as t};
1
+ import{N as e,mt as t,v as n}from"./reactivity.esm-bundler-Dplx1ILy.js";import{J as r,L as i,Y as a,_ as o,d as s,f as c,l,p as u,r as d,u as f,y as p}from"./runtime-core.esm-bundler-BAUgNzMK.js";import{t as m}from"./QBtn-kfhHFy7Q.js";import{Nt as h,Pt as g,bn as _,en as v,f as y,j as b,l as x,rn as S,vn as C}from"./index-DLoUeovT.js";import{t as w}from"./QSelect-DOnv_ry0.js";import{a as T}from"./use-tours-C_5R1Y5D.js";import{i as E,t as D}from"./permissionModes-D-Nexnoo.js";import{t as O}from"./efforts-C-p9DzkL.js";function k(e,t){return e.engine!==t.engine||e.model!==t.model||e.reasoningEffort!==t.reasoningEffort||e.agentPermissionMode!==t.agentPermissionMode}var A={class:`text-h6`},j={class:`text-body2 text-kobo-2 q-mt-xs`},ee={class:`row q-col-gutter-sm q-mb-md`},te={class:`col-12 col-sm-6`},ne={class:`col-12 col-sm-6`},M={class:`col-12 col-sm-6`},N={class:`col-12 col-sm-6`},P={key:0,class:`text-caption text-kobo-3 q-mt-xs`},F={class:`text-caption text-kobo-3 q-mt-xs`},I={class:`text-caption text-kobo-3 q-mt-xs`},L=T(p({__name:`StartReviewDialog`,props:{modelValue:{type:Boolean},loading:{type:Boolean},workspace:{},canReturnToSession:{type:Boolean},currentSession:{},mode:{},scheduledConfiguration:{}},emits:[`update:modelValue`,`submit`],setup(e,{emit:p}){let T=e,L=p,{t:R}=v(),z=b(),B=l(()=>T.mode===`auto-loop`),V=n(``),H=n(!1),U=n(!1),W=n(`claude-code`),G=n(`auto`),K=n(`auto`),q=n(`bypass`),J=l(()=>B.value||U.value?`plan`:q.value),Y=l(()=>({engine:W.value,model:G.value,reasoningEffort:K.value,agentPermissionMode:J.value})),X=l(()=>{let e=T.currentSession;return B.value||U.value||!!T.workspace&&k(Y.value,T.workspace)||!!e?.engine&&e.engine!==W.value||G.value!==`auto`&&!!e?.model&&e.model!==G.value}),Z=l({get:()=>H.value||X.value,set:e=>{H.value=e}}),re=l(()=>[{value:`claude-code`,label:R(`workspacePage.engineClaude`)},{value:`codex`,label:R(`workspacePage.engineCodex`)}]),Q=l(()=>{let e=(E[W.value]??[]).map(e=>({value:e.value,label:R(e.i18nLabelKey)}));T.workspace?.engine===W.value&&!e.some(e=>e.value===T.workspace?.model)&&e.push({value:T.workspace.model,label:T.workspace.model});let t=T.scheduledConfiguration;return t?.engine===W.value&&!e.some(e=>e.value===t.model)&&e.push({value:t.model,label:t.model}),e}),ie=l(()=>(O[W.value]??[]).map(e=>({value:e.value,label:R(e.i18nLabelKey)}))),$=l(()=>(D[W.value]??[]).map(e=>({value:e,label:R(`agentPermissionMode.${e}`)})));r(W,()=>{let e=T.workspace,t=z.global.defaultModelByEngine[W.value];G.value=e?.engine===W.value?e.model:Q.value.some(e=>e.value===t)?t:`auto`,K.value=e?.engine===W.value?e.reasoningEffort:`auto`,$.value.some(e=>e.value===q.value)||(q.value=`plan`)},{flush:`sync`}),r([()=>T.modelValue,()=>T.workspace?.id],([e])=>{if(!e)return;let t=B.value?T.scheduledConfiguration:null;V.value=t?.additionalInstructions??``,H.value=!1,U.value=B.value,T.workspace&&(W.value=T.workspace.engine,G.value=T.workspace.model,K.value=T.workspace.reasoningEffort,q.value=T.workspace.agentPermissionMode),t&&(W.value=t.engine,G.value=t.model,K.value=t.reasoningEffort)},{immediate:!0}),r(()=>T.canReturnToSession,e=>{!e&&!B.value&&(U.value=!1)});function ae(){T.loading||L(`update:modelValue`,!1)}function oe(){!T.loading&&T.workspace&&L(`submit`,{...Y.value,additionalInstructions:V.value,newSession:Z.value,returnToSession:B.value||U.value&&T.canReturnToSession})}return(n,r)=>(i(),s(S,{"model-value":e.modelValue,persistent:e.loading,"onUpdate:modelValue":r[7]||=e=>L(`update:modelValue`,e)},{default:a(()=>[o(C,{class:`review-card text-kobo-1`},{default:a(()=>[o(_,null,{default:a(()=>[f(`div`,A,t(n.$t(B.value?`autoLoop.finalReview.title`:`review.title`)),1),f(`div`,j,t(n.$t(B.value?`autoLoop.finalReview.description`:`review.subtitle`)),1)]),_:1}),o(y,{dark:``}),o(_,{class:`q-pt-md`},{default:a(()=>[f(`div`,ee,[f(`div`,te,[o(w,{modelValue:W.value,"onUpdate:modelValue":r[0]||=e=>W.value=e,options:re.value,label:n.$t(`engine.select`),disable:e.loading,"emit-value":``,"map-options":``,outlined:``,dark:``,dense:``},null,8,[`modelValue`,`options`,`label`,`disable`])]),f(`div`,ne,[o(w,{modelValue:G.value,"onUpdate:modelValue":r[1]||=e=>G.value=e,options:Q.value,label:n.$t(`engine.model`),disable:e.loading,"emit-value":``,"map-options":``,outlined:``,dark:``,dense:``},null,8,[`modelValue`,`options`,`label`,`disable`])]),f(`div`,M,[o(w,{modelValue:K.value,"onUpdate:modelValue":r[2]||=e=>K.value=e,options:ie.value,label:n.$t(`engine.effort`),disable:e.loading,"emit-value":``,"map-options":``,outlined:``,dark:``,dense:``},null,8,[`modelValue`,`options`,`label`,`disable`])]),f(`div`,N,[o(w,{"model-value":J.value,options:$.value,label:n.$t(`agentPermissionMode.label`),disable:e.loading||U.value,"emit-value":``,"map-options":``,outlined:``,dark:``,dense:``,"onUpdate:modelValue":r[3]||=e=>q.value=e},null,8,[`model-value`,`options`,`label`,`disable`]),U.value?(i(),u(`div`,P,t(n.$t(`review.readOnlyForced`)),1)):c(``,!0)])]),o(g,{modelValue:V.value,"onUpdate:modelValue":r[4]||=e=>V.value=e,type:`textarea`,disable:e.loading,label:n.$t(`review.additionalInstructions`),placeholder:n.$t(`review.additionalInstructionsPlaceholder`),rows:4,outlined:``,dark:``,color:`primary`},null,8,[`modelValue`,`disable`,`label`,`placeholder`])]),_:1}),B.value?c(``,!0):(i(),s(_,{key:0,class:`q-pt-none`},{default:a(()=>[o(x,{modelValue:Z.value,"onUpdate:modelValue":r[5]||=e=>Z.value=e,disable:e.loading||X.value,label:n.$t(`review.newSession`),color:`primary`,dark:``},null,8,[`modelValue`,`disable`,`label`]),f(`div`,F,t(n.$t(X.value?`review.newSessionRequired`:`review.newSessionHint`)),1),Z.value?(i(),u(d,{key:0},[o(x,{modelValue:U.value,"onUpdate:modelValue":r[6]||=e=>U.value=e,disable:e.loading||!e.canReturnToSession,label:n.$t(`review.returnToSession`),color:`primary`,dark:``,class:`q-mt-md`},null,8,[`modelValue`,`disable`,`label`]),f(`div`,I,t(n.$t(e.canReturnToSession?`review.returnToSessionHint`:`review.returnUnavailable`)),1)],64)):c(``,!0)]),_:1})),o(h,{align:`right`,class:`q-pa-md`},{default:a(()=>[o(m,{flat:``,"no-caps":``,label:n.$t(`review.cancel`),color:`kobo-2`,disable:e.loading,onClick:ae},null,8,[`label`,`disable`]),o(m,{"no-caps":``,color:`primary`,label:n.$t(B.value?`autoLoop.finalReview.save`:`review.start`),loading:e.loading,disable:!e.workspace||e.loading,onClick:oe},null,8,[`label`,`loading`,`disable`])]),_:1})]),_:1})]),_:1},8,[`model-value`,`persistent`]))}}),[[`__scopeId`,`data-v-a4419350`]]),R={class:`final-review-settings`},z={class:`text-caption text-kobo-3`},B=T(p({__name:`AutoLoopFinalReviewSettings`,props:{modelValue:{},workspace:{},disabled:{type:Boolean},loading:{type:Boolean}},emits:[`update:modelValue`],setup(a,{emit:l}){let d=a,p=l,{t:h}=v(),g=n(!1),_=n(null);r(()=>d.modelValue,e=>{JSON.stringify(e)===JSON.stringify(_.value)&&(_.value=null)}),r(()=>d.workspace.id,()=>{g.value=!1,_.value=null});function y(e){d.disabled||d.loading||(e?g.value=!0:p(`update:modelValue`,null))}function b(e){!d.disabled&&!d.loading&&e.engine&&e.model&&e.reasoningEffort&&(_.value={engine:e.engine,model:e.model,reasoningEffort:e.reasoningEffort,additionalInstructions:e.additionalInstructions??``},p(`update:modelValue`,_.value),g.value=!1)}return(n,r)=>(i(),u(`div`,R,[o(x,{"model-value":!!a.modelValue,disable:a.disabled||a.loading,color:`primary`,label:e(h)(`autoLoop.finalReview.enabled`),"onUpdate:modelValue":y},null,8,[`model-value`,`disable`,`label`]),f(`p`,z,t(e(h)(`autoLoop.finalReview.hint`)),1),a.modelValue?(i(),s(m,{key:0,flat:``,dense:``,"no-caps":``,disable:a.disabled||a.loading,label:e(h)(`autoLoop.finalReview.configure`),onClick:r[0]||=e=>g.value=!0},null,8,[`disable`,`label`])):c(``,!0),o(L,{modelValue:g.value,"onUpdate:modelValue":r[1]||=e=>g.value=e,mode:`auto-loop`,loading:a.loading??!1,workspace:a.workspace,"scheduled-configuration":_.value??a.modelValue,"can-return-to-session":!0,onSubmit:b},null,8,[`modelValue`,`loading`,`workspace`,`scheduled-configuration`])]))}}),[[`__scopeId`,`data-v-b7f36f83`]]);export{L as n,B as t};