@openchambery/web 2.0.0-beta.1 → 2.0.0-beta.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -11
- package/bin/cli-opencode.test.js +4 -4
- package/bin/cli.js +1 -1
- package/bin/lib/cli-startup.js +1 -1
- package/bin/lib/cli-startup.test.js +18 -4
- package/dist/assets/{AgentAvatar-CLWWNu_Q.js → AgentAvatar-pLaqWWmB.js} +1 -1
- package/dist/assets/AssistantView-C4GkABnI.js +3 -0
- package/dist/assets/{DiagramView-xuSJTnv7.js → DiagramView-ByYLIfWo.js} +1 -1
- package/dist/assets/{FadeInOnReveal-Ov0D96d0.js → FadeInOnReveal-MuJAzJq_.js} +1 -1
- package/dist/assets/Icon-Dz2kO9RN.js +1 -0
- package/dist/assets/MarkdownRenderer-CPU6kSHd.js +2 -0
- package/dist/assets/{MarkdownRendererImpl-SKi4oG0c.js → MarkdownRendererImpl-2hw5cdqQ.js} +3 -3
- package/dist/assets/MarkstreamRendererImpl-DXZQkLTa.js +3 -0
- package/dist/assets/{MobileDetailNavigation-Bnx59U_J.js → MobileDetailNavigation-C9BzieQY.js} +1 -1
- package/dist/assets/{MobileShareBridge-BQlP4J-F.js → MobileShareBridge-2_iOfzqO.js} +2 -2
- package/dist/assets/MobileSurface-DNVA7K_1.js +2 -0
- package/dist/assets/{MultiRunWindow-ChZGob0v.js → MultiRunWindow-QA6pMrJ9.js} +1 -1
- package/dist/assets/{OnboardingScreen-DtQJxZG-.js → OnboardingScreen-XWkr7MjT.js} +1 -1
- package/dist/assets/OverlayScrollbar-DCWyGseO.js +2 -0
- package/dist/assets/SettingsView-dWuu0VIb.js +25 -0
- package/dist/assets/{SettingsWindow-DfnsFa8y.js → SettingsWindow-CetgiJMX.js} +1 -1
- package/dist/assets/TerminalView-BuXuo7gm.js +8 -0
- package/dist/assets/{ToolOutputDialog-BejmjrRk.js → ToolOutputDialog-DNSSOaCt.js} +2 -2
- package/dist/assets/appThemeRegistry-Cb7qFBsc.js +1 -0
- package/dist/assets/{assistant-attachment-cache-C0aRnFkj.js → assistant-attachment-cache-DphJleHr.js} +1 -1
- package/dist/assets/{button-D-rATN7w.js → button-Doq9FI1I.js} +1 -1
- package/dist/assets/{collapsible-BXRd5_Hr.js → collapsible-CYUeu3_N.js} +1 -1
- package/dist/assets/{context-menu-jGtH6fR9.js → context-menu-C8kOMVtC.js} +1 -1
- package/dist/assets/{desktopHosts-B1ixEylr.js → desktopHosts-DbxnIQdS.js} +1 -1
- package/dist/assets/{desktopRelayRestore-4JmFUw8r.js → desktopRelayRestore-dvxF4V2p.js} +1 -1
- package/dist/assets/{diagram-DQf7Y_uR.js → diagram-B1p9Yn1h.js} +1 -1
- package/dist/assets/{dialog-bfbvR4VH.js → dialog-D08-Lg-e.js} +1 -1
- package/dist/assets/es-Dsmc0A_3.js +15 -0
- package/dist/assets/fileMentionTouchSelection-BCAKb-NS.js +3 -0
- package/dist/assets/{fileQueries-DQc8EJUf.js → fileQueries-X773xxvX.js} +1 -1
- package/dist/assets/fr-C_ZlZDI2.js +13 -0
- package/dist/assets/gitApi-3VBj3fbR.js +10 -0
- package/dist/assets/{imageSource-BlAJJUEI.js → imageSource-BMztRu0c.js} +1 -1
- package/dist/assets/{input-8r6F_Fza.js → input-DZZuQ1IL.js} +1 -1
- package/dist/assets/insertionBoundaries-BMuCcvPy.js +1 -0
- package/dist/assets/ja-BqqTaWTi.js +15 -0
- package/dist/assets/ko-DP2XPM37.js +15 -0
- package/dist/assets/main-COEILjRF.js +169 -0
- package/dist/assets/main-D2BzpnUt.css +1 -0
- package/dist/assets/{main-CCE_4y5F.js → main-DsUewWPt.js} +2 -2
- package/dist/assets/markdown-worker-bS5OgGO3.js +2 -0
- package/dist/assets/{markdownHeightCache-1Qo6y6IJ.js → markdownHeightCache-DBgXGze8.js} +1 -1
- package/dist/assets/markdownTheme-DM5h1i18.js +1 -0
- package/dist/assets/mcpQueries-B05QR67V.js +1 -0
- package/dist/assets/miniChat-D7IPMm-k.js +2 -0
- package/dist/assets/mobile-BnCoDxnv.js +2 -0
- package/dist/assets/multirun-BDmFjX9N.js +4 -0
- package/dist/assets/persistence-DznVIKwz.js +3 -0
- package/dist/assets/pl-BCf5uLlK.js +15 -0
- package/dist/assets/platform-DSTtx-DI.js +6 -0
- package/dist/assets/projectMeta-C5gcAd5f.js +3 -0
- package/dist/assets/pt-BR-W09Q2pvn.js +15 -0
- package/dist/assets/{relay-image-stream-DM7atqJs.js → relay-image-stream-DrdVj99c.js} +1 -1
- package/dist/assets/renderElectronMiniChatApp-DsAaWCss.js +2 -0
- package/dist/assets/renderMobileApp-DOaoohlp.js +28 -0
- package/dist/assets/runtime-fetch-Fs9ZV2V8.js +1 -0
- package/dist/assets/{runtimeConfig-BMvqa5we.js → runtimeConfig-CzPK1uMj.js} +1 -1
- package/dist/assets/runtimeEndpointReset-DKjVJCdt.js +149 -0
- package/dist/assets/sessionChangeSummary-CktSWasa.js +2 -0
- package/dist/assets/{sessionLookup-gh-hVRIc.js → sessionLookup-CDA1aAcI.js} +1 -1
- package/dist/assets/{sortable-tabs-strip-CudoSgV4.js → sortable-tabs-strip-Q3bb6MVj.js} +1 -1
- package/dist/assets/src-lS1keDVV.css +2 -0
- package/dist/assets/store-jaP79mqI.js +15 -0
- package/dist/assets/{terminalApi-DiF6JmBP.js → terminalApi-Da_DO_Uh.js} +1 -1
- package/dist/assets/textarea--CYehp07.js +1 -0
- package/dist/assets/{tooltip-CQvpK-UV.js → tooltip-CGLzBwEK.js} +1 -1
- package/dist/assets/uk-BgFzz8m2.js +15 -0
- package/dist/assets/{url-_z7evzdK.js → url-Byz3kd29.js} +1 -1
- package/dist/assets/{useAppFontEffects-Cpmpo7Wb.js → useAppFontEffects-2zzPMQF2.js} +64 -45
- package/dist/assets/{useAssistantUIStore-CMutVh9B.js → useAssistantUIStore-Bz4tRflF.js} +1 -1
- package/dist/assets/useDesktopWindowControlsLayout-B-2YooUd.js +1 -0
- package/dist/assets/useEffectiveDirectory-K3U4eL5F.js +1 -0
- package/dist/assets/{useFontPreferences-DcbXQOkc.js → useFontPreferences-D9OIZs1p.js} +1 -1
- package/dist/assets/useMobileNavigationStore-B0zSdCwP.js +414 -0
- package/dist/assets/useSessionAutoCleanup-BCXHAcbX.js +2 -0
- package/dist/assets/useUIStore-syVmbuDA.js +1 -0
- package/dist/assets/{useWorkerHighlightedLines-Bqn4AiBL.js → useWorkerHighlightedLines-CsoMQNik.js} +2 -2
- package/dist/assets/vendor-opencode-client-C12gfjag.js +12 -0
- package/dist/assets/{vendor-reactuses-core-BnW6GxJt.js → vendor-reactuses-core-Dq5ocTF8.js} +1 -1
- package/dist/assets/zh-CN-BhqSLgUY.js +15 -0
- package/dist/assets/zh-TW-BsaBgY-p.js +15 -0
- package/dist/index.html +12 -12
- package/dist/mini-chat.html +12 -12
- package/dist/mobile.html +12 -12
- package/package.json +3 -2
- package/server/index.js +359 -63
- package/server/lib/assistants/DOCUMENTATION.md +14 -9
- package/server/lib/assistants/contracts.js +11 -1
- package/server/lib/assistants/projection-history.test.js +265 -18
- package/server/lib/assistants/routes.js +6 -0
- package/server/lib/assistants/service.js +417 -231
- package/server/lib/assistants/service.test.js +392 -148
- package/server/lib/browser-provider/DOCUMENTATION.md +141 -0
- package/server/lib/browser-provider/contract.js +312 -0
- package/server/lib/browser-provider/host.js +421 -0
- package/server/lib/browser-provider/host.test.js +304 -0
- package/server/lib/browser-provider/routes.js +122 -0
- package/server/lib/browser-provider/routes.test.js +120 -0
- package/server/lib/browser-provider/selection-store.js +43 -0
- package/server/lib/browser-provider/selection-store.test.js +35 -0
- package/server/lib/conversations/DOCUMENTATION.md +1 -1
- package/server/lib/conversations/service.js +1 -0
- package/server/lib/conversations/service.test.js +16 -0
- package/server/lib/conversations/validation.js +11 -1
- package/server/lib/event-stream/DOCUMENTATION.md +3 -1
- package/server/lib/event-stream/directory-ws-bridge.js +13 -2
- package/server/lib/event-stream/global-hub.js +49 -4
- package/server/lib/event-stream/reasoning-projection.js +16 -1
- package/server/lib/event-stream/reasoning-projection.test.js +15 -0
- package/server/lib/event-stream/runtime.js +57 -20
- package/server/lib/event-stream/runtime.test.js +112 -0
- package/server/lib/llm/DOCUMENTATION.md +33 -9
- package/server/lib/llm/catalog.js +1 -1
- package/server/lib/llm/completions.js +5 -1
- package/server/lib/llm/completions.test.js +3 -3
- package/server/lib/llm/generate.js +237 -145
- package/server/lib/llm/generate.test.js +201 -46
- package/server/lib/llm/routes.js +2 -0
- package/server/lib/message-queue/runtime.js +15 -2
- package/server/lib/notifications/DOCUMENTATION.md +3 -1
- package/server/lib/notifications/runtime.js +133 -31
- package/server/lib/notifications/runtime.test.js +103 -0
- package/server/lib/notifications/template-runtime.js +130 -38
- package/server/lib/notifications/template-runtime.test.js +96 -0
- package/server/lib/opencode/DOCUMENTATION.md +89 -17
- package/server/lib/opencode/agent-document.d.ts +28 -0
- package/server/lib/opencode/agent-document.js +280 -0
- package/server/lib/opencode/agent-document.test.js +137 -0
- package/server/lib/opencode/agents.js +96 -0
- package/server/lib/opencode/auth-state-runtime.js +5 -0
- package/server/lib/opencode/bootstrap-runtime.js +2 -0
- package/server/lib/opencode/config-entity-routes.js +42 -99
- package/server/lib/opencode/config-entity-routes.test.js +107 -0
- package/server/lib/opencode/config-hot-reload.integration.test.js +105 -0
- package/server/lib/opencode/ensure-cli.d.ts +8 -0
- package/server/lib/opencode/ensure-cli.js +1 -2
- package/server/lib/opencode/env-runtime.js +40 -26
- package/server/lib/opencode/env-runtime.test.js +10 -17
- package/server/lib/opencode/feature-routes-runtime.js +53 -4
- package/server/lib/opencode/feature-routes-runtime.test.js +25 -0
- package/server/lib/opencode/http-server-connections.js +98 -0
- package/server/lib/opencode/http-server-connections.test.js +321 -0
- package/server/lib/opencode/lifecycle.js +349 -39
- package/server/lib/opencode/lifecycle.test.js +322 -0
- package/server/lib/opencode/managed-capabilities-runtime.js +15 -2
- package/server/lib/opencode/managed-capabilities-runtime.test.js +1 -0
- package/server/lib/opencode/openchamber-routes.js +7 -1
- package/server/lib/opencode/opencode2-pin.js +4 -4
- package/server/lib/opencode/opencode2-pin.test.js +1 -0
- package/server/lib/opencode/opencode2-sidecar.e2e.test.js +127 -4
- package/server/lib/opencode/owned-runtime-upgrade.js +431 -0
- package/server/lib/opencode/owned-runtime-upgrade.test.js +160 -0
- package/server/lib/opencode/plugin-routes.js +3 -18
- package/server/lib/opencode/plugin-routes.test.js +9 -9
- package/server/lib/opencode/provider-catalog.js +58 -12
- package/server/lib/opencode/provider-catalog.test.js +48 -0
- package/server/lib/opencode/proxy.js +254 -46
- package/server/lib/opencode/proxy.test.js +238 -5
- package/server/lib/opencode/routes.js +326 -62
- package/server/lib/opencode/routes.test.js +278 -12
- package/server/lib/opencode/runtime-contract.js +401 -0
- package/server/lib/opencode/runtime-contract.test.js +214 -0
- package/server/lib/opencode/server-opencode-fetch.js +242 -0
- package/server/lib/opencode/server-opencode-fetch.test.js +130 -0
- package/server/lib/opencode/server-utils-runtime.js +33 -0
- package/server/lib/opencode/shared-service.integration.test.js +66 -0
- package/server/lib/opencode/shared-service.js +147 -0
- package/server/lib/opencode/shared-service.test.js +93 -0
- package/server/lib/opencode/shared.js +3 -3
- package/server/lib/opencode/shutdown-runtime.js +15 -6
- package/server/lib/opencode/shutdown-runtime.test.js +49 -1
- package/server/lib/opencode/skill-routes.js +20 -33
- package/server/lib/opencode/skill-routes.test.js +35 -2
- package/server/lib/opencode/skills.test.js +15 -0
- package/server/lib/opencode/startup-pipeline-runtime.js +4 -0
- package/server/lib/opencode/static-routes-runtime.js +3 -1
- package/server/lib/opencode/static-routes-runtime.test.js +33 -0
- package/server/lib/opencode/upgrade-screen.d.ts +30 -0
- package/server/lib/opencode/upgrade-screen.js +299 -0
- package/server/lib/opencode/upgrade-screen.test.js +196 -0
- package/server/lib/opencode/v2-client.js +14 -3
- package/server/lib/opencode/v2-plugin-host-shim/index.mjs +155 -0
- package/server/lib/opencode/v2-plugin-host-shim/package.json +4 -0
- package/server/lib/permission-auto-accept/DOCUMENTATION.md +5 -5
- package/server/lib/permission-auto-accept/runtime.js +14 -7
- package/server/lib/permission-auto-accept/runtime.test.js +27 -12
- package/server/lib/relay/reasoning-projection.e2e.test.ts +22 -7
- package/server/lib/scheduled-tasks/DOCUMENTATION.md +8 -0
- package/server/lib/scheduled-tasks/runtime.js +52 -4
- package/server/lib/scheduled-tasks/runtime.test.js +80 -1
- package/server/lib/session-goal/DOCUMENTATION.md +113 -13
- package/server/lib/session-goal/routes.js +9 -0
- package/server/lib/session-goal/runtime.js +1579 -117
- package/server/lib/session-goal/runtime.test.js +1453 -84
- package/server/lib/session-index/DOCUMENTATION.md +18 -15
- package/server/lib/session-index/event-ingest.js +89 -16
- package/server/lib/session-index/event-ingest.test.js +73 -0
- package/server/lib/session-index/host-authority-snapshot.js +92 -0
- package/server/lib/session-index/routes.host-archive.test.js +201 -0
- package/server/lib/session-index/routes.js +63 -10
- package/server/lib/session-index/routes.test.js +8 -8
- package/server/lib/session-index/service.js +7 -1
- package/server/lib/session-index/sync-runtime.js +161 -34
- package/server/lib/session-index/sync-runtime.test.js +155 -1
- package/server/lib/session-metadata/DOCUMENTATION.md +73 -18
- package/server/lib/session-metadata/opencode-session-record.d.ts +57 -0
- package/server/lib/session-metadata/opencode-session-record.js +316 -0
- package/server/lib/session-metadata/opencode-session-record.test.js +242 -0
- package/server/lib/session-metadata/routes.js +112 -9
- package/server/lib/session-metadata/routes.test.js +47 -2
- package/server/lib/session-metadata/session-archive.d.ts +73 -0
- package/server/lib/session-metadata/session-archive.js +654 -0
- package/server/lib/session-metadata/session-archive.test.js +359 -0
- package/server/lib/session-metadata/session-metadata-store.d.ts +38 -0
- package/server/lib/session-metadata/session-metadata-store.js +270 -83
- package/server/lib/session-metadata/session-metadata-store.test.js +205 -10
- package/server/lib/session-metadata/session-projection.d.ts +41 -0
- package/server/lib/session-metadata/session-projection.js +279 -0
- package/server/lib/session-metadata/session-projection.test.js +64 -0
- package/server/lib/session-metadata/system-session.js +55 -0
- package/server/lib/session-metadata/system-session.test.js +55 -0
- package/server/lib/session-title/DOCUMENTATION.md +40 -7
- package/server/lib/session-title/native-runtime.test.js +167 -0
- package/server/lib/session-title/runtime.js +69 -44
- package/server/lib/session-title/session-access.js +46 -0
- package/server/lib/session-title/session-access.test.js +91 -0
- package/server/lib/session-turn-pages/DOCUMENTATION.md +9 -8
- package/server/lib/session-turn-pages/routes.js +17 -102
- package/server/lib/session-turn-pages/routes.test.js +281 -0
- package/server/lib/session-turn-pages/session-message-projection.d.ts +13 -0
- package/server/lib/session-turn-pages/session-message-projection.js +281 -0
- package/server/lib/small-model/DOCUMENTATION.md +101 -147
- package/server/lib/small-model/catalog.js +31 -51
- package/server/lib/small-model/catalog.test.js +65 -10
- package/server/lib/small-model/custom-api.js +68 -0
- package/server/lib/small-model/custom-api.test.js +64 -0
- package/server/lib/small-model/index.js +124 -240
- package/server/lib/small-model/resolve.js +35 -120
- package/server/lib/small-model/resolve.test.js +20 -110
- package/server/lib/small-model/routes.js +1 -3
- package/server/lib/small-model/summary-settings.test.js +157 -253
- package/server/opencode-proxy.test.js +2 -0
- package/dist/assets/AssistantView-BTCkm8yk.js +0 -3
- package/dist/assets/Icon-DD-BuMfZ.js +0 -1
- package/dist/assets/MarkdownRenderer-bc_p1H7l.js +0 -2
- package/dist/assets/MarkstreamRendererImpl-CnkodGJW.js +0 -3
- package/dist/assets/MobileSurface-BXLeiZ4d.js +0 -2
- package/dist/assets/OverlayScrollbar-pMwxaCca.js +0 -2
- package/dist/assets/SettingsView-B5I0KDTZ.js +0 -25
- package/dist/assets/TerminalView-Dh9MWCpS.js +0 -8
- package/dist/assets/appThemeRegistry-COGfxTFV.js +0 -1
- package/dist/assets/es-c52LTHHe.js +0 -15
- package/dist/assets/fileMentionTouchSelection-BvrNCroG.js +0 -4
- package/dist/assets/fr-PisFbUE8.js +0 -13
- package/dist/assets/gitApi-ButwzQ0N.js +0 -9
- package/dist/assets/insertionBoundaries-DGs7S5zv.js +0 -1
- package/dist/assets/ja-C8Ikun3Z.js +0 -15
- package/dist/assets/ko-ClFys5o2.js +0 -15
- package/dist/assets/main-Dn9rpsgl.js +0 -169
- package/dist/assets/main-qSeeJRW1.css +0 -1
- package/dist/assets/markdown-worker-CptoICmq.js +0 -2
- package/dist/assets/markdownTheme-DOfGg-A7.js +0 -1
- package/dist/assets/miniChat-BpjxVqgg.js +0 -2
- package/dist/assets/mobile-nMJCaNq1.js +0 -2
- package/dist/assets/multirun-BvSG94mW.js +0 -4
- package/dist/assets/persistence-Ce3AnpEr.js +0 -3
- package/dist/assets/pl-CnaKWN5F.js +0 -15
- package/dist/assets/platform-CWTIp02q.js +0 -4
- package/dist/assets/projectMeta-DOe49dM_.js +0 -3
- package/dist/assets/pt-BR-s8OpfRI6.js +0 -15
- package/dist/assets/renderElectronMiniChatApp-R6dKh79f.js +0 -2
- package/dist/assets/renderMobileApp-y-S8y8v_.js +0 -28
- package/dist/assets/runtime-fetch-zIalUnbb.js +0 -1
- package/dist/assets/runtimeEndpointReset-B6BxhvR0.js +0 -147
- package/dist/assets/sessionChangeSummary-VVBlEXuh.js +0 -2
- package/dist/assets/src-BzX5UI-c.css +0 -2
- package/dist/assets/store-DJczmczv.js +0 -15
- package/dist/assets/textarea-3XR8sUfy.js +0 -1
- package/dist/assets/uk-CaX0iqkB.js +0 -15
- package/dist/assets/useDesktopWindowControlsLayout-B1GX59-L.js +0 -1
- package/dist/assets/useEffectiveDirectory-CN6JoCFw.js +0 -1
- package/dist/assets/useMobileNavigationStore-BVjFtXSy.js +0 -414
- package/dist/assets/useSessionAutoCleanup-BLjymaFt.js +0 -2
- package/dist/assets/useUIStore-Bxv3f23A.js +0 -1
- package/dist/assets/vendor-opencode-client-BOe2qpQb.js +0 -12
- package/dist/assets/zh-CN-20MYEjha.js +0 -15
- package/dist/assets/zh-TW-DiN04qTw.js +0 -15
- package/server/lib/small-model/call.js +0 -427
- package/server/lib/small-model/call.test.js +0 -382
- package/server/lib/small-model/opencode-session.js +0 -183
- package/server/lib/small-model/opencode-session.test.js +0 -147
|
@@ -1,3 +1,41 @@
|
|
|
1
|
+
import { readNotificationSessionRecord } from './template-runtime.js';
|
|
2
|
+
|
|
3
|
+
const trimmedText = (value) => (typeof value === 'string' && value.trim() ? value.trim() : '');
|
|
4
|
+
|
|
5
|
+
const readNotificationEventFields = (payload) => {
|
|
6
|
+
const properties = payload?.properties && typeof payload.properties === 'object' && !Array.isArray(payload.properties)
|
|
7
|
+
? payload.properties
|
|
8
|
+
: {};
|
|
9
|
+
const data = payload?.data && typeof payload.data === 'object' && !Array.isArray(payload.data)
|
|
10
|
+
? payload.data
|
|
11
|
+
: {};
|
|
12
|
+
const form = (properties.form && typeof properties.form === 'object' ? properties.form : null)
|
|
13
|
+
|| (data.form && typeof data.form === 'object' ? data.form : null);
|
|
14
|
+
const questions = Array.isArray(properties.questions)
|
|
15
|
+
? properties.questions
|
|
16
|
+
: (Array.isArray(data.questions) ? data.questions : []);
|
|
17
|
+
const firstQuestion = questions[0] && typeof questions[0] === 'object' ? questions[0] : null;
|
|
18
|
+
const field = Array.isArray(form?.fields)
|
|
19
|
+
? form.fields.find((item) => item && typeof item === 'object' && item.hidden !== true)
|
|
20
|
+
: null;
|
|
21
|
+
return {
|
|
22
|
+
requestId: trimmedText(properties.id)
|
|
23
|
+
|| trimmedText(properties.requestID)
|
|
24
|
+
|| trimmedText(properties.requestId)
|
|
25
|
+
|| trimmedText(data.id)
|
|
26
|
+
|| trimmedText(data.requestID)
|
|
27
|
+
|| trimmedText(data.requestId)
|
|
28
|
+
|| trimmedText(form?.id),
|
|
29
|
+
permission: trimmedText(properties.permission) || trimmedText(data.permission) || trimmedText(data.action),
|
|
30
|
+
message: trimmedText(data.message) || trimmedText(properties.message),
|
|
31
|
+
header: trimmedText(firstQuestion?.header) || trimmedText(field?.title) || trimmedText(form?.title),
|
|
32
|
+
questionText: trimmedText(firstQuestion?.question)
|
|
33
|
+
|| trimmedText(field?.description)
|
|
34
|
+
|| trimmedText(field?.title)
|
|
35
|
+
|| trimmedText(form?.title),
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
|
|
1
39
|
export const createNotificationTriggerRuntime = (deps) => {
|
|
2
40
|
const {
|
|
3
41
|
readSettingsFromDisk,
|
|
@@ -255,14 +293,14 @@ export const createNotificationTriggerRuntime = (deps) => {
|
|
|
255
293
|
if (!response.ok) {
|
|
256
294
|
return cached;
|
|
257
295
|
}
|
|
258
|
-
const session = await response.json().catch(() => null);
|
|
259
|
-
if (!session
|
|
296
|
+
const session = readNotificationSessionRecord(await response.json().catch(() => null));
|
|
297
|
+
if (!session) {
|
|
260
298
|
return cached;
|
|
261
299
|
}
|
|
262
300
|
|
|
263
301
|
const meta = {
|
|
264
302
|
parentID: normalizeParentID(session.parentID),
|
|
265
|
-
title:
|
|
303
|
+
title: trimmedText(session.title) || null,
|
|
266
304
|
complete: true,
|
|
267
305
|
...metaFromOpenchamber(session.metadata?.openchamber),
|
|
268
306
|
};
|
|
@@ -346,13 +384,24 @@ export const createNotificationTriggerRuntime = (deps) => {
|
|
|
346
384
|
|
|
347
385
|
const extractSessionIdFromPayload = (payload) => {
|
|
348
386
|
if (!payload || typeof payload !== 'object') return null;
|
|
349
|
-
const props = payload.properties
|
|
387
|
+
const props = payload.properties && typeof payload.properties === 'object'
|
|
388
|
+
? payload.properties
|
|
389
|
+
: (payload.data && typeof payload.data === 'object' ? payload.data : null);
|
|
350
390
|
const info = props?.info;
|
|
391
|
+
const data = payload.data && typeof payload.data === 'object' && !Array.isArray(payload.data)
|
|
392
|
+
? payload.data
|
|
393
|
+
: null;
|
|
351
394
|
const sessionId =
|
|
352
395
|
info?.sessionID ??
|
|
353
396
|
info?.sessionId ??
|
|
354
397
|
props?.sessionID ??
|
|
355
398
|
props?.sessionId ??
|
|
399
|
+
props?.form?.sessionID ??
|
|
400
|
+
props?.form?.sessionId ??
|
|
401
|
+
data?.sessionID ??
|
|
402
|
+
data?.sessionId ??
|
|
403
|
+
data?.form?.sessionID ??
|
|
404
|
+
data?.form?.sessionId ??
|
|
356
405
|
props?.session ??
|
|
357
406
|
null;
|
|
358
407
|
return typeof sessionId === 'string' && sessionId.length > 0 ? sessionId : null;
|
|
@@ -360,11 +409,32 @@ export const createNotificationTriggerRuntime = (deps) => {
|
|
|
360
409
|
|
|
361
410
|
const extractDirectoryFromPayload = (payload) => {
|
|
362
411
|
if (!payload || typeof payload !== 'object') return undefined;
|
|
363
|
-
const props = payload.properties;
|
|
364
|
-
const
|
|
365
|
-
|
|
366
|
-
const
|
|
367
|
-
|
|
412
|
+
const props = payload.properties && typeof payload.properties === 'object' ? payload.properties : {};
|
|
413
|
+
const data = payload.data && typeof payload.data === 'object' && !Array.isArray(payload.data) ? payload.data : {};
|
|
414
|
+
const info = props.info && typeof props.info === 'object' ? props.info : {};
|
|
415
|
+
const dataInfo = data.info && typeof data.info === 'object' ? data.info : {};
|
|
416
|
+
const candidates = [
|
|
417
|
+
props.directory,
|
|
418
|
+
info.directory,
|
|
419
|
+
info.location?.directory,
|
|
420
|
+
payload.location?.directory,
|
|
421
|
+
data.directory,
|
|
422
|
+
data.location?.directory,
|
|
423
|
+
dataInfo.directory,
|
|
424
|
+
dataInfo.location?.directory,
|
|
425
|
+
];
|
|
426
|
+
for (const value of candidates) {
|
|
427
|
+
const directory = trimmedText(value);
|
|
428
|
+
if (directory) return directory;
|
|
429
|
+
}
|
|
430
|
+
return undefined;
|
|
431
|
+
};
|
|
432
|
+
|
|
433
|
+
const resolvePushSessionName = async (variables, sessionId, directory) => {
|
|
434
|
+
const fromTemplate = trimmedText(variables?.session_name);
|
|
435
|
+
if (fromTemplate) return fromTemplate;
|
|
436
|
+
const meta = await fetchSessionMeta(sessionId, directory);
|
|
437
|
+
return trimmedText(meta?.title);
|
|
368
438
|
};
|
|
369
439
|
|
|
370
440
|
// A session with an ACTIVE goal suppresses per-turn ready notifications;
|
|
@@ -381,7 +451,7 @@ export const createNotificationTriggerRuntime = (deps) => {
|
|
|
381
451
|
signal: AbortSignal.timeout(2000),
|
|
382
452
|
});
|
|
383
453
|
if (!response.ok) return false;
|
|
384
|
-
const session = await response.json().catch(() => null);
|
|
454
|
+
const session = readNotificationSessionRecord(await response.json().catch(() => null));
|
|
385
455
|
const goal = session?.metadata?.openchamber?.goal;
|
|
386
456
|
return Boolean(goal && typeof goal === 'object' && goal.status === 'active');
|
|
387
457
|
} catch {
|
|
@@ -412,20 +482,36 @@ export const createNotificationTriggerRuntime = (deps) => {
|
|
|
412
482
|
}
|
|
413
483
|
}
|
|
414
484
|
}
|
|
415
|
-
|
|
416
|
-
|
|
485
|
+
// Ticket 07: shutdown interrupt is not a completion terminal — no ready push.
|
|
486
|
+
if (payload.type === 'session.execution.interrupted') {
|
|
487
|
+
const reason = payload.properties?.reason ?? payload.data?.reason;
|
|
488
|
+
if (reason === 'shutdown') return;
|
|
489
|
+
}
|
|
490
|
+
// v2 completion is session.execution.succeeded. session.idle is a deprecated
|
|
491
|
+
// fallback; the ready-notification cooldown below collapses the pair.
|
|
492
|
+
if ((
|
|
493
|
+
payload.type === 'session.idle'
|
|
494
|
+
|| payload.type === 'session.execution.succeeded'
|
|
495
|
+
|| payload.type === 'session.error'
|
|
496
|
+
|| payload.type === 'session.execution.failed'
|
|
497
|
+
) && sessionId) {
|
|
498
|
+
const body = payload.properties && typeof payload.properties === 'object'
|
|
499
|
+
? payload.properties
|
|
500
|
+
: (payload.data && typeof payload.data === 'object' ? payload.data : {});
|
|
501
|
+
const error = body?.error;
|
|
417
502
|
const errorText = typeof error?.message === 'string'
|
|
418
503
|
? error.message
|
|
419
504
|
: typeof error === 'string' ? error : '';
|
|
505
|
+
const failed = payload.type === 'session.error' || payload.type === 'session.execution.failed';
|
|
420
506
|
await maybeSendPushForTrigger({
|
|
421
507
|
...payload,
|
|
422
508
|
type: 'message.updated',
|
|
423
509
|
properties: {
|
|
424
|
-
...payload.properties,
|
|
510
|
+
...(payload.properties && typeof payload.properties === 'object' ? payload.properties : body),
|
|
425
511
|
info: {
|
|
426
512
|
sessionID: sessionId,
|
|
427
513
|
role: 'assistant',
|
|
428
|
-
finish:
|
|
514
|
+
finish: failed ? 'error' : 'stop',
|
|
429
515
|
...(errorText ? { parts: [{ type: 'text', text: errorText }] } : {}),
|
|
430
516
|
},
|
|
431
517
|
},
|
|
@@ -487,12 +573,12 @@ export const createNotificationTriggerRuntime = (deps) => {
|
|
|
487
573
|
const completionTemplate = templates.completion || { title: 'Task completed', message: '{session_name}' };
|
|
488
574
|
|
|
489
575
|
const variables = await buildTemplateVariables(payload, sessionId);
|
|
490
|
-
sessionName =
|
|
576
|
+
sessionName = await resolvePushSessionName(variables, sessionId, notificationDirectory);
|
|
491
577
|
|
|
492
578
|
const messageId = info?.id;
|
|
493
579
|
let lastMessage = extractLastMessageText(payload);
|
|
494
580
|
if (!lastMessage) {
|
|
495
|
-
lastMessage = await fetchLastAssistantMessageText(sessionId, messageId);
|
|
581
|
+
lastMessage = await fetchLastAssistantMessageText(sessionId, messageId, undefined, notificationDirectory);
|
|
496
582
|
}
|
|
497
583
|
|
|
498
584
|
variables.last_message = await prepareNotificationLastMessage({
|
|
@@ -543,7 +629,21 @@ export const createNotificationTriggerRuntime = (deps) => {
|
|
|
543
629
|
return;
|
|
544
630
|
}
|
|
545
631
|
|
|
546
|
-
if (
|
|
632
|
+
if ((
|
|
633
|
+
payload.type === 'form.replied'
|
|
634
|
+
|| payload.type === 'form.cancelled'
|
|
635
|
+
|| payload.type === 'question.replied'
|
|
636
|
+
|| payload.type === 'question.rejected'
|
|
637
|
+
) && sessionId) {
|
|
638
|
+
const existingTimer = pushQuestionDebounceTimers.get(sessionId);
|
|
639
|
+
if (existingTimer) {
|
|
640
|
+
clearTimeout(existingTimer);
|
|
641
|
+
pushQuestionDebounceTimers.delete(sessionId);
|
|
642
|
+
}
|
|
643
|
+
return;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
if ((payload.type === 'question.asked' || payload.type === 'form.created') && sessionId) {
|
|
547
647
|
if (await shouldSkipSystemSessionNotification(sessionId, notificationDirectory)) {
|
|
548
648
|
return;
|
|
549
649
|
}
|
|
@@ -564,9 +664,9 @@ export const createNotificationTriggerRuntime = (deps) => {
|
|
|
564
664
|
return;
|
|
565
665
|
}
|
|
566
666
|
|
|
567
|
-
const
|
|
568
|
-
const header =
|
|
569
|
-
const questionText =
|
|
667
|
+
const prompt = readNotificationEventFields(payload);
|
|
668
|
+
const header = prompt.header;
|
|
669
|
+
const questionText = prompt.questionText;
|
|
570
670
|
|
|
571
671
|
let title = 'Needs your answer';
|
|
572
672
|
let body = '';
|
|
@@ -574,7 +674,7 @@ export const createNotificationTriggerRuntime = (deps) => {
|
|
|
574
674
|
|
|
575
675
|
try {
|
|
576
676
|
const variables = await buildTemplateVariables(payload, sessionId);
|
|
577
|
-
sessionName =
|
|
677
|
+
sessionName = await resolvePushSessionName(variables, sessionId, notificationDirectory);
|
|
578
678
|
variables.last_message = questionText || header || '';
|
|
579
679
|
|
|
580
680
|
const templates = settings.notificationTemplates || {};
|
|
@@ -625,8 +725,8 @@ export const createNotificationTriggerRuntime = (deps) => {
|
|
|
625
725
|
}
|
|
626
726
|
|
|
627
727
|
if (payload.type === 'permission.replied' && sessionId) {
|
|
628
|
-
const requestId = payload.
|
|
629
|
-
const requestKey =
|
|
728
|
+
const requestId = readNotificationEventFields(payload).requestId;
|
|
729
|
+
const requestKey = requestId ? `${sessionId}:${requestId}` : null;
|
|
630
730
|
const pendingNotification = pushPermissionDebounceTimers.get(sessionId);
|
|
631
731
|
if (!pendingNotification) {
|
|
632
732
|
return;
|
|
@@ -646,9 +746,10 @@ export const createNotificationTriggerRuntime = (deps) => {
|
|
|
646
746
|
if (await shouldSkipSystemSessionNotification(sessionId, notificationDirectory)) {
|
|
647
747
|
return;
|
|
648
748
|
}
|
|
649
|
-
const
|
|
650
|
-
const
|
|
651
|
-
const
|
|
749
|
+
const permissionFields = readNotificationEventFields(payload);
|
|
750
|
+
const requestId = permissionFields.requestId;
|
|
751
|
+
const permission = permissionFields.permission;
|
|
752
|
+
const requestKey = requestId ? `${sessionId}:${requestId}` : null;
|
|
652
753
|
if (requestKey && notifiedPermissionRequests.has(requestKey)) {
|
|
653
754
|
return;
|
|
654
755
|
}
|
|
@@ -686,11 +787,12 @@ export const createNotificationTriggerRuntime = (deps) => {
|
|
|
686
787
|
return;
|
|
687
788
|
}
|
|
688
789
|
|
|
689
|
-
const sessionTitle = payload.properties?.sessionTitle;
|
|
790
|
+
const sessionTitle = payload.properties?.sessionTitle ?? payload.data?.sessionTitle;
|
|
690
791
|
const permissionText = typeof permission === 'string' && permission.length > 0 ? permission : '';
|
|
792
|
+
const detail = permissionFields.message;
|
|
691
793
|
const fallbackMessage = typeof sessionTitle === 'string' && sessionTitle.trim().length > 0
|
|
692
794
|
? sessionTitle.trim()
|
|
693
|
-
: permissionText || 'Agent is waiting for your approval';
|
|
795
|
+
: detail || permissionText || 'Agent is waiting for your approval';
|
|
694
796
|
|
|
695
797
|
let title = 'Needs permission';
|
|
696
798
|
let body = '';
|
|
@@ -698,7 +800,7 @@ export const createNotificationTriggerRuntime = (deps) => {
|
|
|
698
800
|
|
|
699
801
|
try {
|
|
700
802
|
const variables = await buildTemplateVariables(payload, sessionId);
|
|
701
|
-
sessionName =
|
|
803
|
+
sessionName = await resolvePushSessionName(variables, sessionId, notificationDirectory);
|
|
702
804
|
variables.last_message = fallbackMessage;
|
|
703
805
|
|
|
704
806
|
const templates = settings.notificationTemplates || {};
|
|
@@ -766,8 +868,8 @@ export const createNotificationTriggerRuntime = (deps) => {
|
|
|
766
868
|
signal: AbortSignal.timeout(2000),
|
|
767
869
|
});
|
|
768
870
|
if (response.ok) {
|
|
769
|
-
const session = await response.json().catch(() => null);
|
|
770
|
-
|
|
871
|
+
const session = readNotificationSessionRecord(await response.json().catch(() => null));
|
|
872
|
+
sessionName = trimmedText(session?.title);
|
|
771
873
|
}
|
|
772
874
|
} catch {
|
|
773
875
|
// Session name is presentation sugar for the mobile push — never block on it.
|
|
@@ -457,6 +457,17 @@ describe('contact turn notifications', () => {
|
|
|
457
457
|
});
|
|
458
458
|
|
|
459
459
|
describe('notification trigger live activity end', () => {
|
|
460
|
+
it('treats session.execution.succeeded as completion, including a v2 data envelope', async () => {
|
|
461
|
+
vi.stubGlobal('fetch', vi.fn(async () => rootSessionResponse('ses_done')));
|
|
462
|
+
const { runtime, sendLiveActivityEnd, emitDesktopNotification } = createRuntime();
|
|
463
|
+
await runtime.maybeSendPushForTrigger({
|
|
464
|
+
type: 'session.execution.succeeded',
|
|
465
|
+
data: { sessionID: 'ses_done' },
|
|
466
|
+
});
|
|
467
|
+
expect(sendLiveActivityEnd).toHaveBeenCalledWith({ sessionId: 'ses_done', status: 'complete' });
|
|
468
|
+
expect(emitDesktopNotification).toHaveBeenCalled();
|
|
469
|
+
});
|
|
470
|
+
|
|
460
471
|
it('ends the live activity on top-level completion and error', async () => {
|
|
461
472
|
vi.stubGlobal('fetch', vi.fn(async () => rootSessionResponse()));
|
|
462
473
|
const { runtime, sendLiveActivityEnd } = createRuntime();
|
|
@@ -615,6 +626,97 @@ describe('notification event toggles', () => {
|
|
|
615
626
|
expect(sendPushToAllUiSessions).not.toHaveBeenCalled();
|
|
616
627
|
});
|
|
617
628
|
|
|
629
|
+
it('notifies on an OpenCode 2 question form and a permission request', async () => {
|
|
630
|
+
vi.useFakeTimers();
|
|
631
|
+
vi.stubGlobal('fetch', vi.fn(async () => jsonResponse({
|
|
632
|
+
data: { id: 'ses_ask', parentID: null, title: '发布说明', metadata: {} },
|
|
633
|
+
})));
|
|
634
|
+
const resolveNotificationTemplate = vi.fn((template, variables) => (
|
|
635
|
+
typeof template === 'string'
|
|
636
|
+
? template.replace(/\{(\w+)\}/g, (_match, key) => variables?.[key] ?? '')
|
|
637
|
+
: template
|
|
638
|
+
));
|
|
639
|
+
const { runtime, emitDesktopNotification, sendApnsToAllUiSessions } = createRuntime({
|
|
640
|
+
buildTemplateVariables: vi.fn(async () => ({ session_name: '' })),
|
|
641
|
+
resolveNotificationTemplate,
|
|
642
|
+
readSettingsFromDisk: vi.fn(async () => ({
|
|
643
|
+
...defaultSettings,
|
|
644
|
+
notificationTemplates: {
|
|
645
|
+
question: { title: 'Needs your answer', message: '{last_message}' },
|
|
646
|
+
},
|
|
647
|
+
})),
|
|
648
|
+
});
|
|
649
|
+
|
|
650
|
+
await runtime.maybeSendPushForTrigger({
|
|
651
|
+
type: 'form.created',
|
|
652
|
+
data: {
|
|
653
|
+
form: {
|
|
654
|
+
id: 'frm_1',
|
|
655
|
+
sessionID: 'ses_ask',
|
|
656
|
+
title: 'Questions',
|
|
657
|
+
metadata: { kind: 'question' },
|
|
658
|
+
fields: [{ key: 'q0', title: '确认入口', description: '从哪个入口触发?' }],
|
|
659
|
+
},
|
|
660
|
+
},
|
|
661
|
+
});
|
|
662
|
+
await vi.runAllTimersAsync();
|
|
663
|
+
|
|
664
|
+
expect(emitDesktopNotification).toHaveBeenCalledWith(expect.objectContaining({
|
|
665
|
+
kind: 'question',
|
|
666
|
+
body: '从哪个入口触发?',
|
|
667
|
+
}));
|
|
668
|
+
expect(sendApnsToAllUiSessions).toHaveBeenCalledWith(
|
|
669
|
+
expect.objectContaining({ type: 'question', sessionName: '发布说明' }),
|
|
670
|
+
{ requireNoSse: true },
|
|
671
|
+
);
|
|
672
|
+
|
|
673
|
+
emitDesktopNotification.mockClear();
|
|
674
|
+
sendApnsToAllUiSessions.mockClear();
|
|
675
|
+
await runtime.maybeSendPushForTrigger({
|
|
676
|
+
type: 'permission.asked',
|
|
677
|
+
data: {
|
|
678
|
+
id: 'per_1',
|
|
679
|
+
sessionID: 'ses_ask',
|
|
680
|
+
action: 'edit',
|
|
681
|
+
message: '修改 README',
|
|
682
|
+
resources: ['README.md'],
|
|
683
|
+
},
|
|
684
|
+
});
|
|
685
|
+
await vi.runAllTimersAsync();
|
|
686
|
+
|
|
687
|
+
expect(emitDesktopNotification).toHaveBeenCalledWith(expect.objectContaining({
|
|
688
|
+
kind: 'permission',
|
|
689
|
+
body: '修改 README',
|
|
690
|
+
}));
|
|
691
|
+
expect(sendApnsToAllUiSessions).toHaveBeenCalledWith(
|
|
692
|
+
expect.objectContaining({ type: 'permission', sessionName: '发布说明' }),
|
|
693
|
+
{ requireNoSse: true },
|
|
694
|
+
);
|
|
695
|
+
});
|
|
696
|
+
|
|
697
|
+
it('uses the wrapped OpenCode 2 session title when the template has none', async () => {
|
|
698
|
+
vi.stubGlobal('fetch', vi.fn(async () => jsonResponse({
|
|
699
|
+
data: {
|
|
700
|
+
id: 'ses_done',
|
|
701
|
+
parentID: null,
|
|
702
|
+
title: '发布说明',
|
|
703
|
+
metadata: {},
|
|
704
|
+
},
|
|
705
|
+
})));
|
|
706
|
+
const { runtime, sendApnsToAllUiSessions } = createRuntime({
|
|
707
|
+
buildTemplateVariables: vi.fn(async () => ({ session_name: '' })),
|
|
708
|
+
});
|
|
709
|
+
await runtime.maybeSendPushForTrigger({
|
|
710
|
+
type: 'session.execution.succeeded',
|
|
711
|
+
location: { directory: '/repo' },
|
|
712
|
+
data: { sessionID: 'ses_done' },
|
|
713
|
+
});
|
|
714
|
+
expect(sendApnsToAllUiSessions).toHaveBeenCalledWith(
|
|
715
|
+
expect.objectContaining({ type: 'ready', sessionName: '发布说明' }),
|
|
716
|
+
{ requireNoSse: true },
|
|
717
|
+
);
|
|
718
|
+
});
|
|
719
|
+
|
|
618
720
|
it('still sends goal settle push by default', async () => {
|
|
619
721
|
vi.stubGlobal('fetch', vi.fn(async () => jsonResponse({ id: 'ses_goal', title: 'Goal run' })));
|
|
620
722
|
const { runtime, sendPushToAllUiSessions } = createRuntime();
|
|
@@ -626,6 +728,7 @@ describe('notification event toggles', () => {
|
|
|
626
728
|
});
|
|
627
729
|
expect(sendPushToAllUiSessions).toHaveBeenCalledTimes(1);
|
|
628
730
|
expect(sendPushToAllUiSessions.mock.calls[0][0]).toMatchObject({ tag: 'goal-ses_goal' });
|
|
731
|
+
expect(sendPushToAllUiSessions.mock.calls[0][0].data).toMatchObject({ sessionName: 'Goal run' });
|
|
629
732
|
});
|
|
630
733
|
});
|
|
631
734
|
|
|
@@ -1,5 +1,75 @@
|
|
|
1
|
+
import { projectSessionMessageRecords } from '../session-turn-pages/session-message-projection.js';
|
|
1
2
|
import { summarizeText as summarizeSharedText } from '../text/summarization.js';
|
|
2
3
|
|
|
4
|
+
const trimmedText = (value) => (typeof value === 'string' && value.trim() ? value.trim() : '');
|
|
5
|
+
|
|
6
|
+
const isSessionRecord = (value) => {
|
|
7
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return false;
|
|
8
|
+
return typeof value.id === 'string'
|
|
9
|
+
|| typeof value.sessionID === 'string'
|
|
10
|
+
|| typeof value.title === 'string'
|
|
11
|
+
|| typeof value.parentID === 'string'
|
|
12
|
+
|| (value.metadata && typeof value.metadata === 'object');
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* OpenCode 2 `GET /api/session/:id` returns `{ data: Session }`.
|
|
17
|
+
* Legacy responses and tests still return the session object itself.
|
|
18
|
+
* @param {unknown} payload
|
|
19
|
+
* @returns {object | null}
|
|
20
|
+
*/
|
|
21
|
+
export const readNotificationSessionRecord = (payload) => {
|
|
22
|
+
if (!payload || typeof payload !== 'object' || Array.isArray(payload)) return null;
|
|
23
|
+
if (isSessionRecord(payload.data)) return payload.data;
|
|
24
|
+
return isSessionRecord(payload) ? payload : null;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const TITLE_EVENT_TYPES = new Set(['session.created', 'session.updated', 'session.renamed']);
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Title carried by a session lifecycle event.
|
|
31
|
+
* v2 puts it on `data.title` (`session.created` / `session.renamed`).
|
|
32
|
+
* Legacy events put it on `properties.info.title`.
|
|
33
|
+
* @param {object | null | undefined} payload
|
|
34
|
+
* @returns {{ sessionId: string, title: string } | null}
|
|
35
|
+
*/
|
|
36
|
+
export const readSessionTitleFromEvent = (payload) => {
|
|
37
|
+
if (!payload || typeof payload !== 'object' || !TITLE_EVENT_TYPES.has(payload.type)) return null;
|
|
38
|
+
const bodies = [payload.properties, payload.data].filter((body) => body && typeof body === 'object' && !Array.isArray(body));
|
|
39
|
+
for (const body of bodies) {
|
|
40
|
+
const info = body.info && typeof body.info === 'object' ? body.info : null;
|
|
41
|
+
const title = trimmedText(info?.title) || trimmedText(body.title);
|
|
42
|
+
const sessionId = trimmedText(info?.id)
|
|
43
|
+
|| trimmedText(info?.sessionID)
|
|
44
|
+
|| trimmedText(body.sessionID)
|
|
45
|
+
|| trimmedText(body.id);
|
|
46
|
+
if (title && sessionId) return { sessionId, title };
|
|
47
|
+
}
|
|
48
|
+
return null;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const readNotificationDirectory = (payload) => {
|
|
52
|
+
const properties = payload?.properties && typeof payload.properties === 'object' ? payload.properties : {};
|
|
53
|
+
const data = payload?.data && typeof payload.data === 'object' && !Array.isArray(payload.data) ? payload.data : {};
|
|
54
|
+
const info = properties.info && typeof properties.info === 'object' ? properties.info : {};
|
|
55
|
+
const dataInfo = data.info && typeof data.info === 'object' ? data.info : {};
|
|
56
|
+
const candidates = [
|
|
57
|
+
properties.directory,
|
|
58
|
+
info.directory,
|
|
59
|
+
info.location?.directory,
|
|
60
|
+
payload?.location?.directory,
|
|
61
|
+
data.directory,
|
|
62
|
+
data.location?.directory,
|
|
63
|
+
dataInfo.directory,
|
|
64
|
+
dataInfo.location?.directory,
|
|
65
|
+
];
|
|
66
|
+
for (const value of candidates) {
|
|
67
|
+
const directory = trimmedText(value);
|
|
68
|
+
if (directory) return directory;
|
|
69
|
+
}
|
|
70
|
+
return '';
|
|
71
|
+
};
|
|
72
|
+
|
|
3
73
|
export const createNotificationTemplateRuntime = (deps) => {
|
|
4
74
|
const {
|
|
5
75
|
readSettingsFromDisk,
|
|
@@ -133,37 +203,45 @@ export const createNotificationTemplateRuntime = (deps) => {
|
|
|
133
203
|
return '';
|
|
134
204
|
};
|
|
135
205
|
|
|
136
|
-
const fetchLastAssistantMessageText = async (sessionId, messageId, maxLength = NOTIFICATION_BODY_MAX_CHARS) => {
|
|
206
|
+
const fetchLastAssistantMessageText = async (sessionId, messageId, maxLength = NOTIFICATION_BODY_MAX_CHARS, directory) => {
|
|
137
207
|
if (!sessionId) return '';
|
|
138
208
|
|
|
139
209
|
try {
|
|
140
|
-
const
|
|
141
|
-
const
|
|
210
|
+
const base = buildOpenCodeUrl(`/session/${encodeURIComponent(sessionId)}/message`, '');
|
|
211
|
+
const params = new URLSearchParams({ limit: '5', order: 'desc' });
|
|
212
|
+
const scopedDirectory = trimmedText(directory);
|
|
213
|
+
if (scopedDirectory) params.set('directory', scopedDirectory);
|
|
214
|
+
const response = await fetch(`${base}?${params.toString()}`, {
|
|
142
215
|
method: 'GET',
|
|
143
216
|
headers: {
|
|
144
217
|
Accept: 'application/json',
|
|
145
|
-
...getOpenCodeAuthHeaders(),
|
|
218
|
+
...(typeof getOpenCodeAuthHeaders === 'function' ? getOpenCodeAuthHeaders() : {}),
|
|
146
219
|
},
|
|
147
220
|
signal: AbortSignal.timeout(3000),
|
|
148
221
|
});
|
|
149
222
|
|
|
150
223
|
if (!response.ok) return '';
|
|
151
224
|
|
|
152
|
-
const
|
|
153
|
-
|
|
225
|
+
const payload = await response.json().catch(() => null);
|
|
226
|
+
// Legacy lists are a chronological array. OpenCode 2 returns `{ data }`
|
|
227
|
+
// and `order=desc` puts the newest turn first.
|
|
228
|
+
const newestFirst = !Array.isArray(payload);
|
|
229
|
+
const records = Array.isArray(payload)
|
|
230
|
+
? payload
|
|
231
|
+
: (Array.isArray(payload?.data) ? payload.data : null);
|
|
232
|
+
if (!records) return '';
|
|
233
|
+
const projected = projectSessionMessageRecords(records);
|
|
234
|
+
const messages = newestFirst ? projected : [...projected].reverse();
|
|
154
235
|
|
|
155
236
|
let target = null;
|
|
156
237
|
if (messageId) {
|
|
157
238
|
target = messages.find((message) => message?.info?.id === messageId && message?.info?.role === 'assistant');
|
|
158
239
|
}
|
|
159
240
|
if (!target) {
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
break;
|
|
165
|
-
}
|
|
166
|
-
}
|
|
241
|
+
target = messages.find((message) => (
|
|
242
|
+
message?.info?.role === 'assistant'
|
|
243
|
+
&& extractTextFromParts(message.parts, maxLength).length > 0
|
|
244
|
+
));
|
|
167
245
|
}
|
|
168
246
|
|
|
169
247
|
if (!target || !Array.isArray(target.parts)) return '';
|
|
@@ -175,9 +253,9 @@ export const createNotificationTemplateRuntime = (deps) => {
|
|
|
175
253
|
};
|
|
176
254
|
|
|
177
255
|
const cacheSessionTitle = (sessionId, title) => {
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
256
|
+
const id = trimmedText(sessionId);
|
|
257
|
+
const name = trimmedText(title);
|
|
258
|
+
if (id && name) sessionTitleCache.set(id, name);
|
|
181
259
|
};
|
|
182
260
|
|
|
183
261
|
const getCachedSessionTitle = (sessionId) => {
|
|
@@ -185,15 +263,12 @@ export const createNotificationTemplateRuntime = (deps) => {
|
|
|
185
263
|
};
|
|
186
264
|
|
|
187
265
|
const maybeCacheSessionInfoFromEvent = (payload) => {
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
const info = payload.properties?.info;
|
|
192
|
-
if (!info || typeof info !== 'object') return;
|
|
193
|
-
cacheSessionTitle(info.id, info.title);
|
|
266
|
+
const titled = readSessionTitleFromEvent(payload);
|
|
267
|
+
if (!titled) return;
|
|
268
|
+
cacheSessionTitle(titled.sessionId, titled.title);
|
|
194
269
|
};
|
|
195
270
|
|
|
196
|
-
const fetchSessionInfo = async (sessionId) => {
|
|
271
|
+
const fetchSessionInfo = async (sessionId, directory) => {
|
|
197
272
|
if (!sessionId) return null;
|
|
198
273
|
|
|
199
274
|
const cached = sessionInfoCache.get(sessionId);
|
|
@@ -202,20 +277,24 @@ export const createNotificationTemplateRuntime = (deps) => {
|
|
|
202
277
|
}
|
|
203
278
|
|
|
204
279
|
try {
|
|
205
|
-
const
|
|
280
|
+
const base = buildOpenCodeUrl(`/session/${encodeURIComponent(sessionId)}`, '');
|
|
281
|
+
const url = directory ? `${base}?directory=${encodeURIComponent(directory)}` : base;
|
|
206
282
|
const response = await fetch(url, {
|
|
207
283
|
method: 'GET',
|
|
208
|
-
headers: {
|
|
284
|
+
headers: {
|
|
285
|
+
Accept: 'application/json',
|
|
286
|
+
...(typeof getOpenCodeAuthHeaders === 'function' ? getOpenCodeAuthHeaders() : {}),
|
|
287
|
+
},
|
|
209
288
|
signal: AbortSignal.timeout(2000),
|
|
210
289
|
});
|
|
211
290
|
if (!response.ok) {
|
|
212
291
|
console.warn(`[Notification] fetchSessionInfo: ${response.status} for session ${sessionId}`);
|
|
213
292
|
return null;
|
|
214
293
|
}
|
|
215
|
-
const
|
|
216
|
-
if (
|
|
217
|
-
sessionInfoCache.set(sessionId, { data, at: Date.now() });
|
|
218
|
-
return
|
|
294
|
+
const session = readNotificationSessionRecord(await response.json().catch(() => null));
|
|
295
|
+
if (session) {
|
|
296
|
+
sessionInfoCache.set(sessionId, { data: session, at: Date.now() });
|
|
297
|
+
return session;
|
|
219
298
|
}
|
|
220
299
|
return null;
|
|
221
300
|
} catch (error) {
|
|
@@ -227,7 +306,11 @@ export const createNotificationTemplateRuntime = (deps) => {
|
|
|
227
306
|
const buildTemplateVariables = async (payload, sessionId) => {
|
|
228
307
|
const info = payload?.properties?.info || {};
|
|
229
308
|
|
|
230
|
-
|
|
309
|
+
const fromEvent = readSessionTitleFromEvent(payload);
|
|
310
|
+
let sessionTitle = trimmedText(payload?.properties?.sessionTitle)
|
|
311
|
+
|| trimmedText(payload?.properties?.session?.title)
|
|
312
|
+
|| trimmedText(info.sessionTitle)
|
|
313
|
+
|| (fromEvent && (!sessionId || fromEvent.sessionId === sessionId) ? fromEvent.title : '');
|
|
231
314
|
|
|
232
315
|
if (!sessionTitle && sessionId) {
|
|
233
316
|
const cached = getCachedSessionTitle(sessionId);
|
|
@@ -238,25 +321,34 @@ export const createNotificationTemplateRuntime = (deps) => {
|
|
|
238
321
|
|
|
239
322
|
let sessionInfo = null;
|
|
240
323
|
if (!sessionTitle && sessionId) {
|
|
241
|
-
sessionInfo = await fetchSessionInfo(sessionId);
|
|
242
|
-
|
|
243
|
-
|
|
324
|
+
sessionInfo = await fetchSessionInfo(sessionId, readNotificationDirectory(payload));
|
|
325
|
+
const fetchedTitle = trimmedText(sessionInfo?.title);
|
|
326
|
+
if (fetchedTitle) {
|
|
327
|
+
sessionTitle = fetchedTitle;
|
|
244
328
|
cacheSessionTitle(sessionId, sessionTitle);
|
|
245
329
|
}
|
|
246
330
|
}
|
|
247
331
|
|
|
332
|
+
const eventAgent = trimmedText(info.agent) || trimmedText(info.mode);
|
|
333
|
+
const eventModel = trimmedText(info.modelID)
|
|
334
|
+
|| trimmedText(info.model?.modelID)
|
|
335
|
+
|| trimmedText(info.model?.id);
|
|
336
|
+
if ((!eventAgent || !eventModel) && sessionId && !sessionInfo) {
|
|
337
|
+
sessionInfo = await fetchSessionInfo(sessionId, readNotificationDirectory(payload));
|
|
338
|
+
}
|
|
339
|
+
|
|
248
340
|
const agentName = (() => {
|
|
249
|
-
const mode =
|
|
250
|
-
? info.agent.trim()
|
|
251
|
-
: (typeof info.mode === 'string' ? info.mode.trim() : '');
|
|
341
|
+
const mode = eventAgent || trimmedText(sessionInfo?.agent) || trimmedText(sessionInfo?.mode);
|
|
252
342
|
if (!mode) return 'Agent';
|
|
253
343
|
return mode.split(/[-_\s]+/).filter(Boolean)
|
|
254
344
|
.map((token) => token.charAt(0).toUpperCase() + token.slice(1)).join(' ');
|
|
255
345
|
})();
|
|
256
346
|
|
|
257
347
|
const modelName = (() => {
|
|
258
|
-
const raw =
|
|
259
|
-
|
|
348
|
+
const raw = eventModel
|
|
349
|
+
|| trimmedText(sessionInfo?.model?.modelID)
|
|
350
|
+
|| trimmedText(sessionInfo?.model?.id)
|
|
351
|
+
|| trimmedText(sessionInfo?.modelID);
|
|
260
352
|
if (!raw) return 'Assistant';
|
|
261
353
|
return raw.split(/[-_]+/).filter(Boolean)
|
|
262
354
|
.map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(' ');
|