@openchambery/web 1.19.7-beta.2 → 1.19.7-beta.21
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/dist/assets/AgentAvatar-DI2zs2WH.js +1 -0
- package/dist/assets/AssistantView-sV1PVQiM.js +2 -0
- package/dist/assets/DiagramView-D2hqP5VV.js +1 -0
- package/dist/assets/FadeInOnReveal-BWJcUp5P.js +1 -0
- package/dist/assets/Icon-4My8YZwt.js +1 -0
- package/dist/assets/MarkdownLoadingSkeleton-Vezjvk9B.js +3 -0
- package/dist/assets/MarkdownRenderer-x091Z--3.js +2 -0
- package/dist/assets/MarkdownRendererImpl-DML8BSro.js +7 -0
- package/dist/assets/MarkstreamRendererImpl-BK-HM2k3.js +1 -0
- package/dist/assets/{MarkstreamRendererImpl-CR0fBG5w.css → MarkstreamRendererImpl-Ca7RATbs.css} +1 -1
- package/dist/assets/MobileDetailNavigation-DcxkWiks.js +1 -0
- package/dist/assets/MobileShareBridge-TAGcOiyb.js +4 -0
- package/dist/assets/MobileSurface-zqlZzEFQ.js +2 -0
- package/dist/assets/{MultiRunWindow-khpnWyZO.js → MultiRunWindow-iH1d9S14.js} +1 -1
- package/dist/assets/OnboardingScreen-BNEtPmvw.js +1 -0
- package/dist/assets/OverlayScrollbar-BZB21Yp_.js +2 -0
- package/dist/assets/SettingsView-CzW23DZA.js +25 -0
- package/dist/assets/{SettingsWindow-Dx5rU9v5.js → SettingsWindow-DZXCWZid.js} +1 -1
- package/dist/assets/TerminalView-pU0wpD8s.js +8 -0
- package/dist/assets/ToolOutputDialog-Bt7JChFG.js +10 -0
- package/dist/assets/appThemeRegistry-BI7WR2Wo.js +1 -0
- package/dist/assets/{button-z0GOBtWi.js → button-Or0daT9o.js} +1 -1
- package/dist/assets/clipboard-BXxLshVq.js +1 -0
- package/dist/assets/{collapsible-D9zJRpeB.js → collapsible-C-kzSGkq.js} +1 -1
- package/dist/assets/context-menu-Dpjla5p4.js +1 -0
- package/dist/assets/desktopHosts-BzSE0sK7.js +1 -0
- package/dist/assets/desktopRelayRestore-BaKjk1kQ.js +1 -0
- package/dist/assets/{diagram-CoEqmReY.js → diagram-D0mT3mo1.js} +1 -1
- package/dist/assets/{dialog-HCE4eLUW.js → dialog-BkhHBTd4.js} +1 -1
- package/dist/assets/dropdown-menu.styles-Bzo6L3FH.js +1 -0
- package/dist/assets/{es-D9KcV5TX.js → es-BHqnSjoZ.js} +1 -1
- package/dist/assets/fileQueries-DFY6Kiy1.js +1 -0
- package/dist/assets/fileReferenceActions-Di_DoDex.js +1 -0
- package/dist/assets/{fr-DPA9qUl3.js → fr-BfrlJY9i.js} +1 -1
- package/dist/assets/{gitApi-DNTMAa3F.js → gitApi-CeFszLvk.js} +3 -3
- package/dist/assets/{input-BG3VWTIF.js → input-BUsGnzwe.js} +1 -1
- package/dist/assets/insertionBoundaries-DDJNusn2.js +4 -0
- package/dist/assets/{ja-Dsif3NoD.js → ja-DP-7qt-x.js} +1 -1
- package/dist/assets/{ko-Dixx4Ctg.js → ko-E2B5vGQP.js} +1 -1
- package/dist/assets/main-CjnOsKDL.js +169 -0
- package/dist/assets/main-j-Ij13jc.js +2 -0
- package/dist/assets/markdown-worker-BuveaH3O.js +1 -0
- package/dist/assets/markdownTheme-CVS01wHf.js +2 -0
- package/dist/assets/miniChat-DgxavREE.js +2 -0
- package/dist/assets/mobile-C0rwPMPM.js +2 -0
- package/dist/assets/multirun-ot7-bKg7.js +4 -0
- package/dist/assets/outsideFileGrants-BB4GPvCq.js +1 -0
- package/dist/assets/path-utils-jGozTg9d.js +1 -0
- package/dist/assets/persistence-CSZp1CNB.js +3 -0
- package/dist/assets/{pl-Bj78ZGgf.js → pl-r8X5gRM5.js} +1 -1
- package/dist/assets/platform-CenlWqxu.js +2 -0
- package/dist/assets/projectMeta-3yOEc0oI.js +3 -0
- package/dist/assets/{pt-BR-CFL3xSAf.js → pt-BR-DcpdXCf5.js} +1 -1
- package/dist/assets/renderElectronMiniChatApp-BPNvFA_N.js +2 -0
- package/dist/assets/renderMobileApp-CNog29Xy.js +28 -0
- package/dist/assets/runtimeConfig-D28kNEYO.js +2 -0
- package/dist/assets/runtimeEndpointReset-CorvFIRk.js +147 -0
- package/dist/assets/sessionChangeSummary-CX2MJIkc.js +1 -0
- package/dist/assets/sessionLookup-CbatbQf7.js +1 -0
- package/dist/assets/sortable-tabs-strip-C5gGnikj.js +1 -0
- package/dist/assets/src-Af9cMFKF.css +2 -0
- package/dist/assets/{store-DzpbXWX1.js → store-BDFdnk6l.js} +2 -2
- package/dist/assets/terminalApi-CIIs9vFx.js +1 -0
- package/dist/assets/textarea-CIO34TZz.js +1 -0
- package/dist/assets/toolHelpers-DEvVP9U0.js +2 -0
- package/dist/assets/{tooltip-CtH61mQh.js → tooltip-BSrJYYYc.js} +1 -1
- package/dist/assets/{uk-BY60Ub3Z.js → uk-Di0vl9V-.js} +1 -1
- package/dist/assets/url-CE_gAfNa.js +1 -0
- package/dist/assets/{ChatContainer-CnOrdAgs.js → useAppFontEffects-CDpeZWK4.js} +44 -39
- package/dist/assets/useAssistantUIStore-BHacrxe_.js +1 -0
- package/dist/assets/useDesktopWindowControlsLayout-D-xPByG0.js +1 -0
- package/dist/assets/useEffectiveDirectory-Dp-tez_r.js +1 -0
- package/dist/assets/useFontPreferences-BxOHKuVw.js +1 -0
- package/dist/assets/useMobileNavigationStore-DE1Pe9A9.js +412 -0
- package/dist/assets/useSessionAutoCleanup-jNJ_WC38.js +1 -0
- package/dist/assets/useUIStore-CqqO11ZI.js +1 -0
- package/dist/assets/useWorkerHighlightedLines-MDnGj3yv.js +7 -0
- package/dist/assets/utils-B0mQXN9L.js +1 -0
- package/dist/assets/{vendor-markstream-react-CwvbEg9R.js → vendor-markstream-react-C3K8YH0d.js} +4 -4
- package/dist/assets/{zh-CN-Cox1LaOW.js → zh-CN-coFCfeQE.js} +1 -1
- package/dist/assets/{zh-TW-B6RU0mxa.js → zh-TW-oDRZ1-yb.js} +1 -1
- package/dist/index.html +9 -11
- package/dist/mini-chat.html +9 -11
- package/dist/mobile.html +15 -17
- package/package.json +2 -1
- package/server/index.js +2 -0
- package/server/lib/assistant-delivery-parts.js +3 -1
- package/server/lib/assistant-delivery-parts.test.js +11 -0
- package/server/lib/assistants/DOCUMENTATION.md +76 -3
- package/server/lib/assistants/assign.js +225 -0
- package/server/lib/assistants/assign.test.js +118 -0
- package/server/lib/assistants/bubbles.js +45 -0
- package/server/lib/assistants/bubbles.test.js +36 -0
- package/server/lib/assistants/cards.js +148 -0
- package/server/lib/assistants/cards.test.js +116 -0
- package/server/lib/assistants/contact-store.js +284 -0
- package/server/lib/assistants/contact-store.test.js +131 -0
- package/server/lib/assistants/contact-tools.js +761 -0
- package/server/lib/assistants/contact-tools.test.js +328 -0
- package/server/lib/assistants/contracts.js +20 -1
- package/server/lib/assistants/harness.js +551 -0
- package/server/lib/assistants/harness.test.js +607 -0
- package/server/lib/assistants/routes.js +44 -4
- package/server/lib/assistants/service.js +930 -17
- package/server/lib/assistants/service.test.js +1163 -45
- package/server/lib/llm/DOCUMENTATION.md +84 -0
- package/server/lib/llm/catalog.js +100 -0
- package/server/lib/llm/catalog.test.js +78 -0
- package/server/lib/llm/completions.js +151 -0
- package/server/lib/llm/completions.test.js +127 -0
- package/server/lib/llm/generate.js +570 -0
- package/server/lib/llm/generate.test.js +459 -0
- package/server/lib/llm/routes.js +47 -0
- package/server/lib/llm/temp-directory.js +37 -0
- package/server/lib/notifications/APNS.md +23 -17
- package/server/lib/notifications/DOCUMENTATION.md +5 -4
- package/server/lib/notifications/apns-runtime.js +139 -20
- package/server/lib/notifications/apns-runtime.test.js +66 -7
- package/server/lib/notifications/apns-titles.js +62 -62
- package/server/lib/notifications/apns-titles.test.js +3 -1
- package/server/lib/notifications/routes.js +1 -1
- package/server/lib/notifications/runtime.js +177 -86
- package/server/lib/notifications/runtime.test.js +122 -0
- package/server/lib/opencode/feature-routes-runtime.js +30 -0
- package/server/lib/opencode/feature-routes-runtime.test.js +20 -0
- package/server/lib/opencode/settings-runtime.js +18 -2
- package/server/lib/session-goal/DOCUMENTATION.md +8 -1
- package/server/lib/session-goal/runtime.js +20 -0
- package/server/lib/session-index/DOCUMENTATION.md +3 -1
- package/server/lib/session-index/service.js +10 -4
- package/server/lib/session-index/service.test.js +37 -0
- package/server/lib/session-title/runtime.js +1 -0
- package/server/lib/session-title/runtime.test.js +11 -0
- package/dist/assets/AgentAvatar-ziUs4sw3.js +0 -1
- package/dist/assets/AssistantView-DF4yXFgS.js +0 -1
- package/dist/assets/ContextPanel-Cg_uCHzJ.js +0 -285
- package/dist/assets/DiagramView-DYh3Zn2y.js +0 -1
- package/dist/assets/FadeInOnReveal-BAlsRFJH.js +0 -1
- package/dist/assets/Icon-CLpn1mIz.js +0 -1
- package/dist/assets/MarkdownRendererImpl-QzUEpBEm.js +0 -7
- package/dist/assets/MarkstreamRendererImpl-CXrfeUia.js +0 -1
- package/dist/assets/MobileDetailNavigation-DJXEYfq4.js +0 -1
- package/dist/assets/MobileShareBridge-CliVo1YH.js +0 -4
- package/dist/assets/MobileSurface-9Xh6WQkY.js +0 -2
- package/dist/assets/OnboardingScreen-CJYyR54B.js +0 -1
- package/dist/assets/OverlayScrollbar-C-jZ8ITG.js +0 -2
- package/dist/assets/SettingsView-DyCZehFG.js +0 -25
- package/dist/assets/TerminalView-XSALfUaH.js +0 -8
- package/dist/assets/ToolOutputDialog-BfuXazW9.js +0 -10
- package/dist/assets/appThemeRegistry-DDWrniMM.js +0 -2
- package/dist/assets/clipboard-Bz0Zm9VJ.js +0 -1
- package/dist/assets/desktop-CfLlmafK.js +0 -1
- package/dist/assets/desktopHosts-C1Ns3DUu.js +0 -1
- package/dist/assets/desktopRelayRestore-LBt0CBOj.js +0 -1
- package/dist/assets/fileQueries-Dnn15WsA.js +0 -1
- package/dist/assets/insertionBoundaries-DMxuUnL_.js +0 -4
- package/dist/assets/main-B9HVtgXE.js +0 -2
- package/dist/assets/main-ZNyIs3Sn.js +0 -29
- package/dist/assets/markdown-worker-y3ewaqAK.js +0 -4
- package/dist/assets/markdownTheme-TdhQOZSN.js +0 -2
- package/dist/assets/miniChat-q0T0WGkZ.js +0 -2
- package/dist/assets/mobile-DEduvLJj.js +0 -2
- package/dist/assets/multirun-DU-wrxlG.js +0 -4
- package/dist/assets/persistence-CmjKImz2.js +0 -3
- package/dist/assets/platform-skqcMARt.js +0 -2
- package/dist/assets/projectMeta-DNWx6Uva.js +0 -3
- package/dist/assets/radio-BEonIX1k.js +0 -1
- package/dist/assets/renderElectronMiniChatApp--JYWY41L.js +0 -2
- package/dist/assets/renderMobileApp-B0Ix_uNo.js +0 -25
- package/dist/assets/runtime-switch-BkpTTbEi.js +0 -1
- package/dist/assets/runtimeConfig-BTDIG7mX.js +0 -2
- package/dist/assets/runtimeEndpointReset-CZQCYtVI.js +0 -4
- package/dist/assets/shikiHighlight-C9Cx56Qg.js +0 -1
- package/dist/assets/sortable-tabs-strip-Bj8N0VWf.js +0 -1
- package/dist/assets/src-2eFAiBLq.css +0 -2
- package/dist/assets/terminalApi-CNqqwjfG.js +0 -1
- package/dist/assets/url-KeihbE0d.js +0 -1
- package/dist/assets/useAppFontEffects-CKLSamae.js +0 -7
- package/dist/assets/useAssistantUIStore-DaplPyb0.js +0 -1
- package/dist/assets/useDesktopWindowControlsLayout-DJSkFfhT.js +0 -1
- package/dist/assets/useEffectiveDirectory-B3rbY1Kv.js +0 -1
- package/dist/assets/useFontPreferences-D8emhcNv.js +0 -1
- package/dist/assets/useMobileNavigationStore-CAvVUItx.js +0 -412
- package/dist/assets/useSessionAutoCleanup-DxNgqJ8s.js +0 -1
- package/dist/assets/useUIStore-B7KJS5G7.js +0 -1
- package/dist/assets/useWorkerHighlightedLines-DiDF68LR.js +0 -7
- package/dist/assets/utils-CTL16t2f.js +0 -1
|
@@ -36,6 +36,72 @@ const LIVE_ACTIVITY_TOKEN_TTL_MS = 24 * 60 * 60 * 1000;
|
|
|
36
36
|
const LIVE_ACTIVITY_COMPLETE_DISMISSAL_SECONDS = 15 * 60;
|
|
37
37
|
const LIVE_ACTIVITY_ERROR_DISMISSAL_SECONDS = 60 * 60;
|
|
38
38
|
const LIVE_ACTIVITY_STALE_SECONDS = 20 * 60;
|
|
39
|
+
const LIVE_ACTIVITY_AGGREGATE_SESSION_ID = 'live';
|
|
40
|
+
const MAX_LIVE_ACTIVITY_SNAPSHOT_ITEMS = 4;
|
|
41
|
+
const LIVE_ACTIVITY_ITEM_STATUSES = new Set([
|
|
42
|
+
'working', 'tool', 'retry', 'input', 'permission', 'stale', 'complete', 'error',
|
|
43
|
+
]);
|
|
44
|
+
|
|
45
|
+
const parseLiveActivitySnapshot = (value) => {
|
|
46
|
+
if (!Array.isArray(value)) return undefined;
|
|
47
|
+
const items = [];
|
|
48
|
+
for (const raw of value.slice(0, MAX_LIVE_ACTIVITY_SNAPSHOT_ITEMS)) {
|
|
49
|
+
if (!raw || typeof raw !== 'object') continue;
|
|
50
|
+
const sessionId = typeof raw.sessionId === 'string'
|
|
51
|
+
? raw.sessionId.trim()
|
|
52
|
+
: (typeof raw.sessionID === 'string' ? raw.sessionID.trim() : '');
|
|
53
|
+
if (!sessionId || sessionId.length > MAX_LIVE_ACTIVITY_ID_CHARS) continue;
|
|
54
|
+
const status = typeof raw.status === 'string' && LIVE_ACTIVITY_ITEM_STATUSES.has(raw.status)
|
|
55
|
+
? raw.status
|
|
56
|
+
: 'working';
|
|
57
|
+
const startedAt = Number(raw.startedAt);
|
|
58
|
+
if (!Number.isFinite(startedAt)) continue;
|
|
59
|
+
const item = {
|
|
60
|
+
sessionId,
|
|
61
|
+
title: typeof raw.title === 'string' ? raw.title.slice(0, 80) : '',
|
|
62
|
+
status,
|
|
63
|
+
startedAt,
|
|
64
|
+
};
|
|
65
|
+
const endedAt = Number(raw.endedAt);
|
|
66
|
+
if (Number.isFinite(endedAt)) item.endedAt = endedAt;
|
|
67
|
+
items.push(item);
|
|
68
|
+
}
|
|
69
|
+
return items;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const liveActivityItemIsWorking = (status) => status !== 'complete' && status !== 'error';
|
|
73
|
+
|
|
74
|
+
const liveActivityEntryCoversSession = (entry, sessionId) => {
|
|
75
|
+
if (entry.sessionId === sessionId) return true;
|
|
76
|
+
if (Array.isArray(entry.snapshot) && entry.snapshot.length > 0) {
|
|
77
|
+
return entry.snapshot.some((item) => item.sessionId === sessionId);
|
|
78
|
+
}
|
|
79
|
+
return entry.sessionId === LIVE_ACTIVITY_AGGREGATE_SESSION_ID;
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const contentStateFromSnapshot = (snapshot, status, eventVersion, updatedAt, endedAt) => {
|
|
83
|
+
const working = snapshot.filter((item) => liveActivityItemIsWorking(item.status));
|
|
84
|
+
const primary = working[0] ?? snapshot[0];
|
|
85
|
+
const contentState = {
|
|
86
|
+
status,
|
|
87
|
+
eventVersion,
|
|
88
|
+
updatedAt,
|
|
89
|
+
workingCount: working.length,
|
|
90
|
+
items: snapshot.map((item) => {
|
|
91
|
+
const next = {
|
|
92
|
+
sessionID: item.sessionId,
|
|
93
|
+
title: item.title,
|
|
94
|
+
status: item.status,
|
|
95
|
+
startedAt: item.startedAt,
|
|
96
|
+
};
|
|
97
|
+
if (item.endedAt !== undefined) next.endedAt = item.endedAt;
|
|
98
|
+
return next;
|
|
99
|
+
}),
|
|
100
|
+
};
|
|
101
|
+
if (primary?.title) contentState.title = primary.title;
|
|
102
|
+
if (endedAt !== undefined) contentState.endedAt = endedAt;
|
|
103
|
+
return contentState;
|
|
104
|
+
};
|
|
39
105
|
const MAX_RELAY_SEND_TOKENS = 100;
|
|
40
106
|
const RELAY_REGISTER_CONCURRENCY = 16;
|
|
41
107
|
const APNS_COLLAPSE_ID_MAX_BYTES = 64;
|
|
@@ -356,7 +422,8 @@ export const createApnsRuntime = (deps) => {
|
|
|
356
422
|
const createdAt = typeof entry.createdAt === 'number' ? entry.createdAt : null;
|
|
357
423
|
const lastSeenAt = typeof entry.lastSeenAt === 'number' ? entry.lastSeenAt : createdAt;
|
|
358
424
|
if (typeof lastSeenAt === 'number' && now - lastSeenAt > LIVE_ACTIVITY_TOKEN_TTL_MS) return null;
|
|
359
|
-
|
|
425
|
+
const snapshot = parseLiveActivitySnapshot(entry.snapshot);
|
|
426
|
+
return { token, activityId, sessionId, snapshot, createdAt, lastSeenAt };
|
|
360
427
|
})
|
|
361
428
|
.filter(Boolean);
|
|
362
429
|
};
|
|
@@ -464,7 +531,7 @@ export const createApnsRuntime = (deps) => {
|
|
|
464
531
|
});
|
|
465
532
|
};
|
|
466
533
|
|
|
467
|
-
const addOrUpdateLiveActivityToken = async (uiSessionToken, token, activityId, sessionId) => {
|
|
534
|
+
const addOrUpdateLiveActivityToken = async (uiSessionToken, token, activityId, sessionId, snapshot) => {
|
|
468
535
|
if (!uiSessionToken) return;
|
|
469
536
|
const trimmedToken = typeof token === 'string' ? token.trim() : '';
|
|
470
537
|
const trimmedActivityId = typeof activityId === 'string' ? activityId.trim() : '';
|
|
@@ -486,10 +553,12 @@ export const createApnsRuntime = (deps) => {
|
|
|
486
553
|
const filtered = existing.filter(
|
|
487
554
|
(entry) => entry.activityId !== trimmedActivityId && entry.token !== trimmedToken,
|
|
488
555
|
);
|
|
556
|
+
const nextSnapshot = parseLiveActivitySnapshot(snapshot) ?? replaced?.snapshot;
|
|
489
557
|
filtered.unshift({
|
|
490
558
|
token: trimmedToken,
|
|
491
559
|
activityId: trimmedActivityId,
|
|
492
560
|
sessionId: trimmedSessionId,
|
|
561
|
+
snapshot: nextSnapshot,
|
|
493
562
|
createdAt: typeof replaced?.createdAt === 'number' ? replaced.createdAt : now,
|
|
494
563
|
lastSeenAt: now,
|
|
495
564
|
});
|
|
@@ -590,7 +659,7 @@ export const createApnsRuntime = (deps) => {
|
|
|
590
659
|
teamId,
|
|
591
660
|
p8,
|
|
592
661
|
bundleId: bundleId || DEFAULT_BUNDLE_ID,
|
|
593
|
-
environment: environment === 'production' ? 'production' : 'sandbox',
|
|
662
|
+
environment: (environment || 'production') === 'production' ? 'production' : 'sandbox',
|
|
594
663
|
};
|
|
595
664
|
};
|
|
596
665
|
|
|
@@ -710,7 +779,7 @@ export const createApnsRuntime = (deps) => {
|
|
|
710
779
|
liveActivityUnregisterUrl: url.replace(/\/send$/, '/unregister-live-activity-token'),
|
|
711
780
|
liveActivitySendUrl: url.replace(/\/send$/, '/live-activity'),
|
|
712
781
|
environment:
|
|
713
|
-
(trimmedEnv('OPENCHAMBER_APNS_ENVIRONMENT') || '
|
|
782
|
+
(trimmedEnv('OPENCHAMBER_APNS_ENVIRONMENT') || 'production').toLowerCase() === 'production'
|
|
714
783
|
? 'production'
|
|
715
784
|
: 'sandbox',
|
|
716
785
|
});
|
|
@@ -857,9 +926,13 @@ export const createApnsRuntime = (deps) => {
|
|
|
857
926
|
return { dismissalDate: liveActivityDismissalDate(status, nowMs) };
|
|
858
927
|
};
|
|
859
928
|
|
|
860
|
-
const liveActivityRelaySignMessage = ({ ts, tokens, event, contentState, dismissalDate, staleDate }) =>
|
|
861
|
-
`${ts}.${[...tokens].sort().join(',')}.${event}.${contentState.status}.${contentState.eventVersion}.${contentState.updatedAt}.${contentState.endedAt ?? ''}.${dismissalDate ?? ''}.${staleDate ?? ''}
|
|
862
|
-
|
|
929
|
+
const liveActivityRelaySignMessage = ({ ts, tokens, event, contentState, dismissalDate, staleDate }) => {
|
|
930
|
+
let message = `${ts}.${[...tokens].sort().join(',')}.${event}.${contentState.status}.${contentState.eventVersion}.${contentState.updatedAt}.${contentState.endedAt ?? ''}.${dismissalDate ?? ''}.${staleDate ?? ''}`;
|
|
931
|
+
if (contentState.title !== undefined || contentState.workingCount !== undefined || contentState.items !== undefined) {
|
|
932
|
+
message += `.${contentState.title ?? ''}.${contentState.workingCount ?? ''}.${JSON.stringify(contentState.items ?? [])}`;
|
|
933
|
+
}
|
|
934
|
+
return message;
|
|
935
|
+
};
|
|
863
936
|
|
|
864
937
|
const sendLiveActivityViaRelay = async (deviceTokens, payload, relay) => {
|
|
865
938
|
if (!Array.isArray(deviceTokens) || deviceTokens.length === 0) return [];
|
|
@@ -1039,7 +1112,7 @@ export const createApnsRuntime = (deps) => {
|
|
|
1039
1112
|
const seen = new Set();
|
|
1040
1113
|
for (const record of Object.values(pruneLiveActivityTokensBySession(store.liveActivityTokensBySession, now))) {
|
|
1041
1114
|
for (const entry of record) {
|
|
1042
|
-
if (entry
|
|
1115
|
+
if (!liveActivityEntryCoversSession(entry, sessionId) || seen.has(entry.token)) continue;
|
|
1043
1116
|
seen.add(entry.token);
|
|
1044
1117
|
entries.push(entry);
|
|
1045
1118
|
}
|
|
@@ -1047,14 +1120,18 @@ export const createApnsRuntime = (deps) => {
|
|
|
1047
1120
|
return entries;
|
|
1048
1121
|
};
|
|
1049
1122
|
|
|
1050
|
-
const sendLiveActivityEnd = async ({ sessionId, status, eventVersion, endedAt } = {}) => {
|
|
1123
|
+
const sendLiveActivityEnd = async ({ sessionId, status, eventVersion, endedAt, title } = {}) => {
|
|
1051
1124
|
const trimmedSessionId = typeof sessionId === 'string' ? sessionId.trim() : '';
|
|
1052
1125
|
if (!isLimitedId(trimmedSessionId, MAX_LIVE_ACTIVITY_ID_CHARS)) return;
|
|
1053
|
-
|
|
1126
|
+
const nextStatus = LIVE_ACTIVITY_TERMINAL_STATUSES.has(status) ? status : undefined;
|
|
1127
|
+
const nextTitle = typeof title === 'string' ? title.trim().slice(0, 80) : '';
|
|
1128
|
+
if (!nextStatus && !nextTitle) return;
|
|
1054
1129
|
|
|
1055
1130
|
const now = Date.now();
|
|
1056
1131
|
let pendingEntries = [];
|
|
1057
1132
|
let nextVersion = 0;
|
|
1133
|
+
let event = 'end';
|
|
1134
|
+
let snapshotForSend;
|
|
1058
1135
|
await persistTokenUpdate((current) => {
|
|
1059
1136
|
const liveActivityTokensBySession = pruneLiveActivityTokensBySession(
|
|
1060
1137
|
current.liveActivityTokensBySession,
|
|
@@ -1068,31 +1145,73 @@ export const createApnsRuntime = (deps) => {
|
|
|
1068
1145
|
if (pendingEntries.length === 0) {
|
|
1069
1146
|
return { ...current, liveActivityTokensBySession };
|
|
1070
1147
|
}
|
|
1071
|
-
const previous = current.liveActivityEventVersions?.[trimmedSessionId]
|
|
1148
|
+
const previous = current.liveActivityEventVersions?.[trimmedSessionId]
|
|
1149
|
+
?? current.liveActivityEventVersions?.[LIVE_ACTIVITY_AGGREGATE_SESSION_ID];
|
|
1072
1150
|
nextVersion = nextLiveActivityEventVersion(now, previous, eventVersion);
|
|
1151
|
+
const endedAtSeconds = toLiveActivityUnixSeconds(endedAt, now);
|
|
1152
|
+
for (const [uiSession, record] of Object.entries(liveActivityTokensBySession)) {
|
|
1153
|
+
liveActivityTokensBySession[uiSession] = record.map((entry) => {
|
|
1154
|
+
if (!liveActivityEntryCoversSession(entry, trimmedSessionId)) return entry;
|
|
1155
|
+
if (!Array.isArray(entry.snapshot) || entry.snapshot.length === 0) return entry;
|
|
1156
|
+
const snapshot = entry.snapshot.map((item) => {
|
|
1157
|
+
if (item.sessionId !== trimmedSessionId) return item;
|
|
1158
|
+
const patched = { ...item };
|
|
1159
|
+
if (nextTitle) patched.title = nextTitle;
|
|
1160
|
+
if (nextStatus) {
|
|
1161
|
+
patched.status = nextStatus;
|
|
1162
|
+
patched.endedAt = endedAtSeconds;
|
|
1163
|
+
}
|
|
1164
|
+
return patched;
|
|
1165
|
+
});
|
|
1166
|
+
snapshotForSend = snapshot;
|
|
1167
|
+
return { ...entry, snapshot };
|
|
1168
|
+
});
|
|
1169
|
+
}
|
|
1170
|
+
const working = Array.isArray(snapshotForSend)
|
|
1171
|
+
? snapshotForSend.filter((item) => liveActivityItemIsWorking(item.status))
|
|
1172
|
+
: [];
|
|
1173
|
+
event = nextStatus && working.length === 0 ? 'end' : 'update';
|
|
1174
|
+
if (!nextStatus) event = 'update';
|
|
1073
1175
|
return {
|
|
1074
1176
|
...current,
|
|
1075
1177
|
liveActivityTokensBySession,
|
|
1076
1178
|
liveActivityEventVersions: {
|
|
1077
1179
|
...(current.liveActivityEventVersions || {}),
|
|
1078
1180
|
[trimmedSessionId]: nextVersion,
|
|
1181
|
+
[LIVE_ACTIVITY_AGGREGATE_SESSION_ID]: nextVersion,
|
|
1079
1182
|
},
|
|
1080
1183
|
};
|
|
1081
1184
|
});
|
|
1082
1185
|
if (pendingEntries.length === 0) return;
|
|
1186
|
+
if (!nextStatus && !(Array.isArray(snapshotForSend) && snapshotForSend.length > 0)) return;
|
|
1083
1187
|
|
|
1084
1188
|
const updatedAt = now / 1000;
|
|
1085
1189
|
const endedAtSeconds = toLiveActivityUnixSeconds(endedAt, now);
|
|
1086
|
-
const
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1190
|
+
const aggregateStatus = event === 'end'
|
|
1191
|
+
? (nextStatus || 'complete')
|
|
1192
|
+
: (Array.isArray(snapshotForSend)
|
|
1193
|
+
? (snapshotForSend.some((item) => liveActivityItemIsWorking(item.status))
|
|
1194
|
+
? (snapshotForSend.find((item) => liveActivityItemIsWorking(item.status))?.status || 'working')
|
|
1195
|
+
: (nextStatus || 'working'))
|
|
1196
|
+
: (nextStatus || 'working'));
|
|
1197
|
+
const contentState = Array.isArray(snapshotForSend) && snapshotForSend.length > 0
|
|
1198
|
+
? contentStateFromSnapshot(
|
|
1199
|
+
snapshotForSend,
|
|
1200
|
+
aggregateStatus,
|
|
1201
|
+
nextVersion,
|
|
1202
|
+
updatedAt,
|
|
1203
|
+
event === 'end' ? endedAtSeconds : undefined,
|
|
1204
|
+
)
|
|
1205
|
+
: {
|
|
1206
|
+
status: nextStatus || 'complete',
|
|
1207
|
+
eventVersion: nextVersion,
|
|
1208
|
+
updatedAt,
|
|
1209
|
+
...(event === 'end' ? { endedAt: endedAtSeconds } : {}),
|
|
1210
|
+
};
|
|
1092
1211
|
const payload = {
|
|
1093
|
-
event
|
|
1212
|
+
event,
|
|
1094
1213
|
contentState,
|
|
1095
|
-
...liveActivityPushDates(
|
|
1214
|
+
...liveActivityPushDates(event, contentState.status, updatedAt, now),
|
|
1096
1215
|
};
|
|
1097
1216
|
|
|
1098
1217
|
const tokens = pendingEntries.map((entry) => entry.token);
|
|
@@ -1121,7 +1240,7 @@ export const createApnsRuntime = (deps) => {
|
|
|
1121
1240
|
return;
|
|
1122
1241
|
}
|
|
1123
1242
|
|
|
1124
|
-
if (accepted.length > 0) {
|
|
1243
|
+
if (event === 'end' && accepted.length > 0) {
|
|
1125
1244
|
await removeLiveActivityTokens(accepted);
|
|
1126
1245
|
}
|
|
1127
1246
|
};
|
|
@@ -115,6 +115,25 @@ describe('apns runtime bundle ID', () => {
|
|
|
115
115
|
});
|
|
116
116
|
});
|
|
117
117
|
|
|
118
|
+
describe('apns runtime APNs environment', () => {
|
|
119
|
+
it('defaults resolveApnsConfig environment to production when env and settings omit it', async () => {
|
|
120
|
+
process.env.OPENCHAMBER_APNS_KEY_ID = 'KEY123';
|
|
121
|
+
process.env.OPENCHAMBER_APNS_TEAM_ID = 'TEAM123';
|
|
122
|
+
process.env.OPENCHAMBER_APNS_P8 = P8;
|
|
123
|
+
const runtime = createApnsRuntime(makeDeps());
|
|
124
|
+
await expect(runtime.resolveApnsConfig()).resolves.toMatchObject({ environment: 'production' });
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('uses sandbox when OPENCHAMBER_APNS_ENVIRONMENT=sandbox', async () => {
|
|
128
|
+
process.env.OPENCHAMBER_APNS_KEY_ID = 'KEY123';
|
|
129
|
+
process.env.OPENCHAMBER_APNS_TEAM_ID = 'TEAM123';
|
|
130
|
+
process.env.OPENCHAMBER_APNS_P8 = P8;
|
|
131
|
+
process.env.OPENCHAMBER_APNS_ENVIRONMENT = 'sandbox';
|
|
132
|
+
const runtime = createApnsRuntime(makeDeps());
|
|
133
|
+
await expect(runtime.resolveApnsConfig()).resolves.toMatchObject({ environment: 'sandbox' });
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
|
|
118
137
|
describe('apns runtime relay mode (default)', () => {
|
|
119
138
|
it('registers tokens (signed) and posts signed generic text, dropping dead tokens', async () => {
|
|
120
139
|
const fetchMock = vi.fn(async (url) =>
|
|
@@ -618,9 +637,9 @@ describe('apns runtime push relay derivation and re-register', () => {
|
|
|
618
637
|
expect(sendBodies).toHaveLength(3);
|
|
619
638
|
|
|
620
639
|
const byTitle = Object.fromEntries(sendBodies.map((body) => [body.title, body]));
|
|
621
|
-
expect(byTitle['
|
|
622
|
-
expect(byTitle['
|
|
623
|
-
expect(byTitle['
|
|
640
|
+
expect(byTitle['Task completed']?.tokens).toEqual(['tokenEn']);
|
|
641
|
+
expect(byTitle['任务已完成']?.tokens).toEqual(['tokenZh']);
|
|
642
|
+
expect(byTitle['タスク完了']?.tokens).toEqual(['tokenJa']);
|
|
624
643
|
for (const body of sendBodies) {
|
|
625
644
|
expect(body.body).toBe('My session');
|
|
626
645
|
expect(body.badge).toBe(2);
|
|
@@ -641,7 +660,7 @@ describe('apns runtime push relay derivation and re-register', () => {
|
|
|
641
660
|
await runtime.sendApnsToAllUiSessions({ type: 'permission', sessionName: 'S' });
|
|
642
661
|
|
|
643
662
|
const sent = JSON.parse(fetchMock.mock.calls.find(isSend)[1].body);
|
|
644
|
-
expect(sent.title).toBe('
|
|
663
|
+
expect(sent.title).toBe('Needs permission');
|
|
645
664
|
expect(sent.body).toBe('S');
|
|
646
665
|
});
|
|
647
666
|
});
|
|
@@ -840,7 +859,7 @@ describe('apns live activity routes', () => {
|
|
|
840
859
|
body: { token: ' la-token ', activityId: ' act-1 ', sessionId: ' ses_1 ' },
|
|
841
860
|
}, postRes);
|
|
842
861
|
expect(postRes.body).toEqual({ ok: true });
|
|
843
|
-
expect(addOrUpdateLiveActivityToken).toHaveBeenCalledWith('ui-token', 'la-token', 'act-1', 'ses_1');
|
|
862
|
+
expect(addOrUpdateLiveActivityToken).toHaveBeenCalledWith('ui-token', 'la-token', 'act-1', 'ses_1', undefined);
|
|
844
863
|
|
|
845
864
|
const delRes = createMockRes();
|
|
846
865
|
await getRoute('DELETE', '/api/push/live-activity-token')({ body: { token: 'la-token' } }, delRes);
|
|
@@ -906,6 +925,34 @@ describe('apns live activity relay and direct delivery', () => {
|
|
|
906
925
|
}
|
|
907
926
|
});
|
|
908
927
|
|
|
928
|
+
it('updates the aggregate live activity when one covered session completes', async () => {
|
|
929
|
+
const fetchMock = vi.fn(async () => jsonResponse({ ok: true, results: [] }));
|
|
930
|
+
vi.stubGlobal('fetch', fetchMock);
|
|
931
|
+
process.env.OPENCHAMBER_PUSH_RELAY_URL = 'https://relay.test/v1/push/send';
|
|
932
|
+
const runtime = createApnsRuntime(makeDeps());
|
|
933
|
+
const token = 'a'.repeat(64);
|
|
934
|
+
await runtime.addOrUpdateLiveActivityToken('s1', token, 'act-1', 'live', [
|
|
935
|
+
{ sessionId: 'ses_a', title: 'Alpha', status: 'working', startedAt: 100 },
|
|
936
|
+
{ sessionId: 'ses_b', title: 'Beta', status: 'working', startedAt: 100 },
|
|
937
|
+
]);
|
|
938
|
+
fetchMock.mockClear();
|
|
939
|
+
await runtime.sendLiveActivityEnd({ sessionId: 'ses_a', status: 'complete' });
|
|
940
|
+
const sent = JSON.parse(fetchMock.mock.calls.find(isLiveActivitySend)[1].body);
|
|
941
|
+
expect(sent.event).toBe('update');
|
|
942
|
+
expect(sent.contentState.workingCount).toBe(1);
|
|
943
|
+
expect(sent.contentState.items).toEqual([
|
|
944
|
+
expect.objectContaining({ sessionID: 'ses_a', status: 'complete', title: 'Alpha' }),
|
|
945
|
+
expect.objectContaining({ sessionID: 'ses_b', status: 'working', title: 'Beta' }),
|
|
946
|
+
]);
|
|
947
|
+
expect(sent.staleDate).toBeTypeOf('number');
|
|
948
|
+
expect(sent.dismissalDate).toBeUndefined();
|
|
949
|
+
await verifyRelaySignature(
|
|
950
|
+
sent.publicKeyJwk,
|
|
951
|
+
`${sent.ts}.${[...sent.tokens].sort().join(',')}.${sent.event}.${sent.contentState.status}.${sent.contentState.eventVersion}.${sent.contentState.updatedAt}.${sent.contentState.endedAt ?? ''}.${sent.dismissalDate ?? ''}.${sent.staleDate ?? ''}.${sent.contentState.title ?? ''}.${sent.contentState.workingCount ?? ''}.${JSON.stringify(sent.contentState.items ?? [])}`,
|
|
952
|
+
sent.sig,
|
|
953
|
+
).then((ok) => expect(ok).toBe(true));
|
|
954
|
+
});
|
|
955
|
+
|
|
909
956
|
it('uses a 60-minute dismissal for error ends', async () => {
|
|
910
957
|
const fetchMock = vi.fn(async () => jsonResponse({ ok: true, results: [] }));
|
|
911
958
|
vi.stubGlobal('fetch', fetchMock);
|
|
@@ -921,11 +968,10 @@ describe('apns live activity relay and direct delivery', () => {
|
|
|
921
968
|
expect(sent.dismissalDate).toBeLessThanOrEqual(Math.floor(after / 1000) + 60 * 60);
|
|
922
969
|
});
|
|
923
970
|
|
|
924
|
-
it('
|
|
971
|
+
it('defaults live-activity relay env to production when OPENCHAMBER_APNS_ENVIRONMENT is unset', async () => {
|
|
925
972
|
const fetchMock = vi.fn(async () => jsonResponse({ ok: true, results: [] }));
|
|
926
973
|
vi.stubGlobal('fetch', fetchMock);
|
|
927
974
|
process.env.OPENCHAMBER_PUSH_RELAY_URL = 'https://relay.test/v1/push/send';
|
|
928
|
-
process.env.OPENCHAMBER_APNS_ENVIRONMENT = 'production';
|
|
929
975
|
const runtime = createApnsRuntime(makeDeps());
|
|
930
976
|
await runtime.addOrUpdateLiveActivityToken('s1', 'la-token', 'act-1', 'ses_1');
|
|
931
977
|
fetchMock.mockClear();
|
|
@@ -934,6 +980,19 @@ describe('apns live activity relay and direct delivery', () => {
|
|
|
934
980
|
expect(sent.env).toBe('production');
|
|
935
981
|
});
|
|
936
982
|
|
|
983
|
+
it('forwards OPENCHAMBER_APNS_ENVIRONMENT=sandbox as env on live-activity relay send', async () => {
|
|
984
|
+
const fetchMock = vi.fn(async () => jsonResponse({ ok: true, results: [] }));
|
|
985
|
+
vi.stubGlobal('fetch', fetchMock);
|
|
986
|
+
process.env.OPENCHAMBER_PUSH_RELAY_URL = 'https://relay.test/v1/push/send';
|
|
987
|
+
process.env.OPENCHAMBER_APNS_ENVIRONMENT = 'sandbox';
|
|
988
|
+
const runtime = createApnsRuntime(makeDeps());
|
|
989
|
+
await runtime.addOrUpdateLiveActivityToken('s1', 'la-token', 'act-1', 'ses_1');
|
|
990
|
+
fetchMock.mockClear();
|
|
991
|
+
await runtime.sendLiveActivityEnd({ sessionId: 'ses_1', status: 'complete' });
|
|
992
|
+
const sent = JSON.parse(fetchMock.mock.calls.find(isLiveActivitySend)[1].body);
|
|
993
|
+
expect(sent.env).toBe('sandbox');
|
|
994
|
+
});
|
|
995
|
+
|
|
937
996
|
it('keeps tokens after a failed relay end so a later attempt can retry', async () => {
|
|
938
997
|
const fetchMock = vi.fn(async (url) => {
|
|
939
998
|
if (String(url).endsWith('/live-activity')) return jsonResponse({ error: true }, 500);
|
|
@@ -20,113 +20,113 @@ export const APNS_LOCALES = Object.freeze([
|
|
|
20
20
|
|
|
21
21
|
const TITLES = Object.freeze({
|
|
22
22
|
en: Object.freeze({
|
|
23
|
-
ready: '
|
|
24
|
-
error: '
|
|
25
|
-
question: '
|
|
26
|
-
permission: '
|
|
27
|
-
goal_complete: 'Goal
|
|
23
|
+
ready: 'Task completed',
|
|
24
|
+
error: 'Something went wrong',
|
|
25
|
+
question: 'Needs your answer',
|
|
26
|
+
permission: 'Needs permission',
|
|
27
|
+
goal_complete: 'Goal completed',
|
|
28
28
|
goal_blocked: 'Goal blocked',
|
|
29
|
-
goal_budget: '
|
|
30
|
-
update: '
|
|
29
|
+
goal_budget: 'Token budget reached',
|
|
30
|
+
update: 'Update',
|
|
31
31
|
session: 'Session',
|
|
32
32
|
}),
|
|
33
33
|
fr: Object.freeze({
|
|
34
|
-
ready:
|
|
35
|
-
error: "
|
|
36
|
-
question:
|
|
37
|
-
permission:
|
|
38
|
-
goal_complete: 'Objectif
|
|
34
|
+
ready: 'Tâche terminée',
|
|
35
|
+
error: "Une erreur s'est produite",
|
|
36
|
+
question: 'Besoin de votre réponse',
|
|
37
|
+
permission: 'Autorisation requise',
|
|
38
|
+
goal_complete: 'Objectif atteint',
|
|
39
39
|
goal_blocked: 'Objectif bloqué',
|
|
40
|
-
goal_budget:
|
|
41
|
-
update:
|
|
40
|
+
goal_budget: 'Budget de tokens atteint',
|
|
41
|
+
update: 'Mise à jour',
|
|
42
42
|
session: 'Session',
|
|
43
43
|
}),
|
|
44
44
|
'zh-CN': Object.freeze({
|
|
45
|
-
ready: '
|
|
46
|
-
error: '
|
|
47
|
-
question: '
|
|
48
|
-
permission: '
|
|
45
|
+
ready: '任务已完成',
|
|
46
|
+
error: '出现错误',
|
|
47
|
+
question: '需要你回答',
|
|
48
|
+
permission: '需要你授权',
|
|
49
49
|
goal_complete: '目标已完成',
|
|
50
50
|
goal_blocked: '目标受阻',
|
|
51
|
-
goal_budget: '
|
|
52
|
-
update: '
|
|
51
|
+
goal_budget: '已达用量上限',
|
|
52
|
+
update: '有新进展',
|
|
53
53
|
session: '会话',
|
|
54
54
|
}),
|
|
55
55
|
'zh-TW': Object.freeze({
|
|
56
|
-
ready: '
|
|
57
|
-
error: '
|
|
58
|
-
question: '
|
|
59
|
-
permission: '
|
|
56
|
+
ready: '任務已完成',
|
|
57
|
+
error: '出現錯誤',
|
|
58
|
+
question: '需要你回答',
|
|
59
|
+
permission: '需要你授權',
|
|
60
60
|
goal_complete: '目標已完成',
|
|
61
61
|
goal_blocked: '目標受阻',
|
|
62
|
-
goal_budget: '
|
|
63
|
-
update: '
|
|
62
|
+
goal_budget: '已達用量上限',
|
|
63
|
+
update: '有新進展',
|
|
64
64
|
session: '工作階段',
|
|
65
65
|
}),
|
|
66
66
|
uk: Object.freeze({
|
|
67
|
-
ready: '
|
|
68
|
-
error: '
|
|
69
|
-
question: '
|
|
70
|
-
permission: '
|
|
67
|
+
ready: 'Завдання виконано',
|
|
68
|
+
error: 'Сталася помилка',
|
|
69
|
+
question: 'Потрібна ваша відповідь',
|
|
70
|
+
permission: 'Потрібен дозвіл',
|
|
71
71
|
goal_complete: 'Цілі досягнуто',
|
|
72
72
|
goal_blocked: 'Цілі заблоковано',
|
|
73
|
-
goal_budget: '
|
|
74
|
-
update: 'Оновлення
|
|
73
|
+
goal_budget: 'Досягнуто ліміт токенів',
|
|
74
|
+
update: 'Оновлення',
|
|
75
75
|
session: 'Сесія',
|
|
76
76
|
}),
|
|
77
77
|
es: Object.freeze({
|
|
78
|
-
ready: '
|
|
79
|
-
error: '
|
|
80
|
-
question: '
|
|
81
|
-
permission: '
|
|
78
|
+
ready: 'Tarea completada',
|
|
79
|
+
error: 'Se produjo un error',
|
|
80
|
+
question: 'Necesita tu respuesta',
|
|
81
|
+
permission: 'Necesita permiso',
|
|
82
82
|
goal_complete: 'Objetivo completado',
|
|
83
83
|
goal_blocked: 'Objetivo bloqueado',
|
|
84
|
-
goal_budget: '
|
|
85
|
-
update: 'Actualización
|
|
84
|
+
goal_budget: 'Presupuesto de tokens alcanzado',
|
|
85
|
+
update: 'Actualización',
|
|
86
86
|
session: 'Sesión',
|
|
87
87
|
}),
|
|
88
88
|
'pt-BR': Object.freeze({
|
|
89
|
-
ready: '
|
|
90
|
-
error: '
|
|
91
|
-
question: '
|
|
92
|
-
permission: '
|
|
89
|
+
ready: 'Tarefa concluída',
|
|
90
|
+
error: 'Ocorreu um erro',
|
|
91
|
+
question: 'Precisa da sua resposta',
|
|
92
|
+
permission: 'Precisa de permissão',
|
|
93
93
|
goal_complete: 'Objetivo concluído',
|
|
94
94
|
goal_blocked: 'Objetivo bloqueado',
|
|
95
|
-
goal_budget: '
|
|
96
|
-
update: 'Atualização
|
|
95
|
+
goal_budget: 'Orçamento de tokens atingido',
|
|
96
|
+
update: 'Atualização',
|
|
97
97
|
session: 'Sessão',
|
|
98
98
|
}),
|
|
99
99
|
ko: Object.freeze({
|
|
100
|
-
ready: '
|
|
101
|
-
error: '
|
|
102
|
-
question: '
|
|
103
|
-
permission: '
|
|
100
|
+
ready: '작업 완료',
|
|
101
|
+
error: '오류가 발생했습니다',
|
|
102
|
+
question: '답변이 필요합니다',
|
|
103
|
+
permission: '권한이 필요합니다',
|
|
104
104
|
goal_complete: '목표 완료',
|
|
105
105
|
goal_blocked: '목표 차단됨',
|
|
106
|
-
goal_budget: '
|
|
107
|
-
update: '
|
|
106
|
+
goal_budget: '토큰 한도 도달',
|
|
107
|
+
update: '업데이트',
|
|
108
108
|
session: '세션',
|
|
109
109
|
}),
|
|
110
110
|
pl: Object.freeze({
|
|
111
|
-
ready: '
|
|
112
|
-
error: '
|
|
113
|
-
question: '
|
|
114
|
-
permission: '
|
|
111
|
+
ready: 'Zadanie ukończone',
|
|
112
|
+
error: 'Wystąpił błąd',
|
|
113
|
+
question: 'Potrzebna twoja odpowiedź',
|
|
114
|
+
permission: 'Potrzebne uprawnienie',
|
|
115
115
|
goal_complete: 'Cel ukończony',
|
|
116
116
|
goal_blocked: 'Cel zablokowany',
|
|
117
|
-
goal_budget: '
|
|
118
|
-
update: 'Aktualizacja
|
|
117
|
+
goal_budget: 'Osiągnięto limit tokenów',
|
|
118
|
+
update: 'Aktualizacja',
|
|
119
119
|
session: 'Sesja',
|
|
120
120
|
}),
|
|
121
121
|
ja: Object.freeze({
|
|
122
|
-
ready: '
|
|
123
|
-
error: '
|
|
124
|
-
question: '
|
|
125
|
-
permission: '
|
|
122
|
+
ready: 'タスク完了',
|
|
123
|
+
error: 'エラーが発生しました',
|
|
124
|
+
question: '回答が必要です',
|
|
125
|
+
permission: '許可が必要です',
|
|
126
126
|
goal_complete: '目標完了',
|
|
127
127
|
goal_blocked: '目標がブロックされました',
|
|
128
|
-
goal_budget: '
|
|
129
|
-
update: '
|
|
128
|
+
goal_budget: 'トークン上限に達しました',
|
|
129
|
+
update: '更新',
|
|
130
130
|
session: 'セッション',
|
|
131
131
|
}),
|
|
132
132
|
});
|
|
@@ -33,12 +33,14 @@ describe('apns-titles', () => {
|
|
|
33
33
|
|
|
34
34
|
it('localizes payload title by type and keeps session name as body', () => {
|
|
35
35
|
expect(localizeApnsPayload({ type: 'question', sessionName: 'Build fix' }, 'zh-CN')).toEqual({
|
|
36
|
-
title: '
|
|
36
|
+
title: '需要你回答',
|
|
37
37
|
body: 'Build fix',
|
|
38
38
|
badge: undefined,
|
|
39
39
|
tag: undefined,
|
|
40
40
|
data: undefined,
|
|
41
41
|
});
|
|
42
|
+
expect(resolveApnsTitle('ready', 'zh-CN')).toBe('任务已完成');
|
|
43
|
+
expect(resolveApnsTitle('ready', 'en')).toBe('Task completed');
|
|
42
44
|
expect(localizeApnsPayload({ type: 'ready' }, 'fr').body).toBe('Session');
|
|
43
45
|
expect(localizeApnsPayload({ type: 'ready' }, 'zh-CN').body).toBe('会话');
|
|
44
46
|
});
|
|
@@ -219,7 +219,7 @@ export const registerNotificationRoutes = (app, dependencies) => {
|
|
|
219
219
|
}
|
|
220
220
|
|
|
221
221
|
if (typeof addOrUpdateLiveActivityToken === 'function') {
|
|
222
|
-
await addOrUpdateLiveActivityToken(uiToken, token, activityId, sessionId);
|
|
222
|
+
await addOrUpdateLiveActivityToken(uiToken, token, activityId, sessionId, req.body?.items);
|
|
223
223
|
}
|
|
224
224
|
return res.json({ ok: true });
|
|
225
225
|
});
|