@openchambery/web 1.19.7-beta.2 → 1.19.7-beta.20
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-C0p-8f2B.js +2 -0
- package/dist/assets/DiagramView-CSXRZQ5S.js +1 -0
- package/dist/assets/FadeInOnReveal-BWJcUp5P.js +1 -0
- package/dist/assets/Icon-4My8YZwt.js +1 -0
- package/dist/assets/MarkdownRendererImpl-C6rkoZYH.js +7 -0
- package/dist/assets/{MarkstreamRendererImpl-CR0fBG5w.css → MarkstreamRendererImpl-Ca7RATbs.css} +1 -1
- package/dist/assets/MarkstreamRendererImpl-CowOCU5W.js +1 -0
- package/dist/assets/MobileDetailNavigation-ACMbLTqb.js +1 -0
- package/dist/assets/MobileShareBridge-CXR-B1xW.js +4 -0
- package/dist/assets/MobileSurface-CKBSKqUH.js +2 -0
- package/dist/assets/{MultiRunWindow-khpnWyZO.js → MultiRunWindow-el9_nl25.js} +1 -1
- package/dist/assets/OnboardingScreen-BNEtPmvw.js +1 -0
- package/dist/assets/OverlayScrollbar-BZB21Yp_.js +2 -0
- package/dist/assets/SettingsView-CVSiIRia.js +25 -0
- package/dist/assets/{SettingsWindow-Dx5rU9v5.js → SettingsWindow-BXYswtp5.js} +1 -1
- package/dist/assets/TerminalView-Bh0IQwa5.js +8 -0
- package/dist/assets/{ToolOutputDialog-BfuXazW9.js → ToolOutputDialog-XeIJ2etT.js} +3 -3
- package/dist/assets/appThemeRegistry-rSdmh4sW.js +2 -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-3pPDAJSl.js +1 -0
- package/dist/assets/{fr-DPA9qUl3.js → fr-BfrlJY9i.js} +1 -1
- package/dist/assets/{gitApi-DNTMAa3F.js → gitApi-Uvb9w70Y.js} +3 -3
- package/dist/assets/{input-BG3VWTIF.js → input-BUsGnzwe.js} +1 -1
- package/dist/assets/insertionBoundaries-Bgec5nsP.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-COfQt8Sj.js +169 -0
- package/dist/assets/main-PPlpRj1-.js +2 -0
- package/dist/assets/markdown-worker-wEFz5Hm_.js +3 -0
- package/dist/assets/markdownHeightCache-Bj707yG-.js +2 -0
- package/dist/assets/markdownTheme-CVS01wHf.js +2 -0
- package/dist/assets/miniChat-EZGRqQZr.js +2 -0
- package/dist/assets/mobile-DQNqMo5J.js +2 -0
- package/dist/assets/multirun-EViX73vm.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-Bv1L_nEb.js +3 -0
- package/dist/assets/{pt-BR-CFL3xSAf.js → pt-BR-DcpdXCf5.js} +1 -1
- package/dist/assets/renderElectronMiniChatApp-Nsq4Wc4s.js +2 -0
- package/dist/assets/renderMobileApp-5WJDYaMR.js +28 -0
- package/dist/assets/runtimeConfig-DKUQS4iY.js +2 -0
- package/dist/assets/runtimeEndpointReset-BMeu9Hqj.js +147 -0
- package/dist/assets/sessionChangeSummary-CX2MJIkc.js +1 -0
- package/dist/assets/sessionLookup-Sb2HN3G2.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/{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-B_6vc0u3.js} +46 -41
- package/dist/assets/useAssistantUIStore-BHacrxe_.js +1 -0
- package/dist/assets/useDesktopWindowControlsLayout-B2L5PWHW.js +1 -0
- package/dist/assets/useEffectiveDirectory-CBd5vt8N.js +1 -0
- package/dist/assets/useFontPreferences-BxOHKuVw.js +1 -0
- package/dist/assets/useMobileNavigationStore-CUGaydRW.js +412 -0
- package/dist/assets/useSessionAutoCleanup-Clhzc1wn.js +1 -0
- package/dist/assets/useUIStore-CqqO11ZI.js +1 -0
- package/dist/assets/useWorkerHighlightedLines-C4iQlbPQ.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 +4 -4
- package/server/lib/notifications/DOCUMENTATION.md +1 -1
- 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 +81 -49
- package/server/lib/notifications/runtime.test.js +61 -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/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/markdownHeightCache-CvUyZT4q.js +0 -1
- 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
|
@@ -273,40 +273,6 @@ export const createNotificationTriggerRuntime = (deps) => {
|
|
|
273
273
|
return trimmed.length > 0 ? trimmed : undefined;
|
|
274
274
|
};
|
|
275
275
|
|
|
276
|
-
const formatMode = (raw) => {
|
|
277
|
-
const value = typeof raw === 'string' ? raw.trim() : '';
|
|
278
|
-
const normalized = value.length > 0 ? value : 'agent';
|
|
279
|
-
return normalized
|
|
280
|
-
.split(/[-_\s]+/)
|
|
281
|
-
.filter(Boolean)
|
|
282
|
-
.map((token) => token.charAt(0).toUpperCase() + token.slice(1))
|
|
283
|
-
.join(' ');
|
|
284
|
-
};
|
|
285
|
-
|
|
286
|
-
const formatModelId = (raw) => {
|
|
287
|
-
const value = typeof raw === 'string' ? raw.trim() : '';
|
|
288
|
-
if (!value) {
|
|
289
|
-
return 'Assistant';
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
const tokens = value.split(/[-_]+/).filter(Boolean);
|
|
293
|
-
const result = [];
|
|
294
|
-
for (let i = 0; i < tokens.length; i += 1) {
|
|
295
|
-
const current = tokens[i];
|
|
296
|
-
const next = tokens[i + 1];
|
|
297
|
-
if (/^\d+$/.test(current) && next && /^\d+$/.test(next)) {
|
|
298
|
-
result.push(`${current}.${next}`);
|
|
299
|
-
i += 1;
|
|
300
|
-
continue;
|
|
301
|
-
}
|
|
302
|
-
result.push(current);
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
return result
|
|
306
|
-
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
|
|
307
|
-
.join(' ');
|
|
308
|
-
};
|
|
309
|
-
|
|
310
276
|
// A session with an ACTIVE goal suppresses per-turn ready notifications;
|
|
311
277
|
// the session-goal runtime sends its own notification when the goal
|
|
312
278
|
// settles. Fetch failures fall through to normal notification behavior.
|
|
@@ -338,6 +304,20 @@ export const createNotificationTriggerRuntime = (deps) => {
|
|
|
338
304
|
|
|
339
305
|
const sessionId = extractSessionIdFromPayload(payload);
|
|
340
306
|
const notificationDirectory = extractDirectoryFromPayload(payload);
|
|
307
|
+
if ((payload.type === 'session.updated' || payload.type === 'session.created') && sessionId) {
|
|
308
|
+
const title = payload.properties?.info?.title;
|
|
309
|
+
if (
|
|
310
|
+
typeof title === 'string'
|
|
311
|
+
&& title.trim()
|
|
312
|
+
&& !(await shouldSkipSystemSessionNotification(sessionId, notificationDirectory))
|
|
313
|
+
) {
|
|
314
|
+
try {
|
|
315
|
+
await sendLiveActivityEnd?.({ sessionId, title: title.trim() });
|
|
316
|
+
} catch (error) {
|
|
317
|
+
console.warn('[Live Activity] title update failed:', error?.message ?? error);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
341
321
|
if ((payload.type === 'session.idle' || payload.type === 'session.error') && sessionId) {
|
|
342
322
|
const error = payload.properties?.error;
|
|
343
323
|
const errorText = typeof error?.message === 'string'
|
|
@@ -404,13 +384,13 @@ export const createNotificationTriggerRuntime = (deps) => {
|
|
|
404
384
|
}
|
|
405
385
|
lastReadyNotificationAt.set(sessionId, now);
|
|
406
386
|
|
|
407
|
-
let title =
|
|
408
|
-
let body =
|
|
387
|
+
let title = 'Task completed';
|
|
388
|
+
let body = '';
|
|
409
389
|
let sessionName = '';
|
|
410
390
|
|
|
411
391
|
try {
|
|
412
392
|
const templates = settings.notificationTemplates || {};
|
|
413
|
-
const completionTemplate = templates.completion || { title: '
|
|
393
|
+
const completionTemplate = templates.completion || { title: 'Task completed', message: '{session_name}' };
|
|
414
394
|
|
|
415
395
|
const variables = await buildTemplateVariables(payload, sessionId);
|
|
416
396
|
sessionName = typeof variables.session_name === 'string' ? variables.session_name : sessionName;
|
|
@@ -430,8 +410,10 @@ export const createNotificationTriggerRuntime = (deps) => {
|
|
|
430
410
|
const resolvedBody = resolveNotificationTemplate(completionTemplate.message, variables);
|
|
431
411
|
if (resolvedTitle) title = resolvedTitle;
|
|
432
412
|
if (shouldApplyResolvedTemplateMessage(completionTemplate.message, resolvedBody, variables)) body = resolvedBody;
|
|
413
|
+
if (!body) body = sessionName || resolvedBody || 'Session';
|
|
433
414
|
} catch (error) {
|
|
434
415
|
console.warn('[Notification] Template resolution failed, using defaults:', error?.message || error);
|
|
416
|
+
if (!body) body = sessionName || 'Session';
|
|
435
417
|
}
|
|
436
418
|
|
|
437
419
|
if (settings.nativeNotificationsEnabled) {
|
|
@@ -492,12 +474,8 @@ export const createNotificationTriggerRuntime = (deps) => {
|
|
|
492
474
|
const header = typeof firstQuestion?.header === 'string' ? firstQuestion.header.trim() : '';
|
|
493
475
|
const questionText = typeof firstQuestion?.question === 'string' ? firstQuestion.question.trim() : '';
|
|
494
476
|
|
|
495
|
-
let title =
|
|
496
|
-
|
|
497
|
-
: /build\s*agent/i.test(header)
|
|
498
|
-
? 'Switch to build mode'
|
|
499
|
-
: header || 'Input needed';
|
|
500
|
-
let body = questionText || 'Agent is waiting for your response';
|
|
477
|
+
let title = 'Needs your answer';
|
|
478
|
+
let body = '';
|
|
501
479
|
let sessionName = '';
|
|
502
480
|
|
|
503
481
|
try {
|
|
@@ -506,14 +484,16 @@ export const createNotificationTriggerRuntime = (deps) => {
|
|
|
506
484
|
variables.last_message = questionText || header || '';
|
|
507
485
|
|
|
508
486
|
const templates = settings.notificationTemplates || {};
|
|
509
|
-
const questionTemplate = templates.question || { title: '
|
|
487
|
+
const questionTemplate = templates.question || { title: 'Needs your answer', message: '{session_name}' };
|
|
510
488
|
|
|
511
489
|
const resolvedTitle = resolveNotificationTemplate(questionTemplate.title, variables);
|
|
512
490
|
const resolvedBody = resolveNotificationTemplate(questionTemplate.message, variables);
|
|
513
491
|
if (resolvedTitle) title = resolvedTitle;
|
|
514
492
|
if (shouldApplyResolvedTemplateMessage(questionTemplate.message, resolvedBody, variables)) body = resolvedBody;
|
|
493
|
+
if (!body) body = sessionName || resolvedBody || 'Session';
|
|
515
494
|
} catch (error) {
|
|
516
495
|
console.warn('[Notification] Question template resolution failed, using defaults:', error?.message || error);
|
|
496
|
+
if (!body) body = sessionName || 'Session';
|
|
517
497
|
}
|
|
518
498
|
|
|
519
499
|
if (settings.nativeNotificationsEnabled) {
|
|
@@ -618,8 +598,8 @@ export const createNotificationTriggerRuntime = (deps) => {
|
|
|
618
598
|
? sessionTitle.trim()
|
|
619
599
|
: permissionText || 'Agent is waiting for your approval';
|
|
620
600
|
|
|
621
|
-
let title = '
|
|
622
|
-
let body =
|
|
601
|
+
let title = 'Needs permission';
|
|
602
|
+
let body = '';
|
|
623
603
|
let sessionName = '';
|
|
624
604
|
|
|
625
605
|
try {
|
|
@@ -628,14 +608,14 @@ export const createNotificationTriggerRuntime = (deps) => {
|
|
|
628
608
|
variables.last_message = fallbackMessage;
|
|
629
609
|
|
|
630
610
|
const templates = settings.notificationTemplates || {};
|
|
631
|
-
const questionTemplate = templates.question || { title: '
|
|
611
|
+
const questionTemplate = templates.question || { title: 'Needs permission', message: '{session_name}' };
|
|
632
612
|
|
|
633
|
-
const resolvedTitle = resolveNotificationTemplate(questionTemplate.title, variables);
|
|
634
613
|
const resolvedBody = resolveNotificationTemplate(questionTemplate.message, variables);
|
|
635
|
-
if (resolvedTitle) title = resolvedTitle;
|
|
636
614
|
if (shouldApplyResolvedTemplateMessage(questionTemplate.message, resolvedBody, variables)) body = resolvedBody;
|
|
615
|
+
if (!body) body = sessionName || fallbackMessage || 'Session';
|
|
637
616
|
} catch (error) {
|
|
638
617
|
console.warn('[Notification] Permission template resolution failed, using defaults:', error?.message || error);
|
|
618
|
+
if (!body) body = sessionName || fallbackMessage || 'Session';
|
|
639
619
|
}
|
|
640
620
|
|
|
641
621
|
if (settings.nativeNotificationsEnabled) {
|
|
@@ -713,6 +693,57 @@ export const createNotificationTriggerRuntime = (deps) => {
|
|
|
713
693
|
);
|
|
714
694
|
};
|
|
715
695
|
|
|
696
|
+
const contactNotificationTitle = (name) => {
|
|
697
|
+
if (typeof name !== 'string' || !name.trim()) return 'Assistant';
|
|
698
|
+
const stripped = name.replace(/^(?:\p{Extended_Pictographic}|\p{Regional_Indicator}|[\uFE0F\u200D])+\s*/u, '').trim();
|
|
699
|
+
return stripped || name.trim();
|
|
700
|
+
};
|
|
701
|
+
|
|
702
|
+
const sendContactTurnNotification = async ({ assistantID, name, body, status } = {}) => {
|
|
703
|
+
if (typeof assistantID !== 'string' || !assistantID.trim()) return;
|
|
704
|
+
const settings = await readSettingsFromDisk();
|
|
705
|
+
if (settings.notifyOnCompletion === false) return;
|
|
706
|
+
|
|
707
|
+
const title = contactNotificationTitle(name);
|
|
708
|
+
const rawBody = typeof body === 'string' ? body : '';
|
|
709
|
+
let text = '';
|
|
710
|
+
try {
|
|
711
|
+
text = await prepareNotificationLastMessage({ message: rawBody, settings });
|
|
712
|
+
} catch {
|
|
713
|
+
text = '';
|
|
714
|
+
}
|
|
715
|
+
if (!text) text = status === 'error' ? 'Something went wrong' : 'New message';
|
|
716
|
+
const tag = `contact-${assistantID.trim()}`;
|
|
717
|
+
|
|
718
|
+
if (settings.nativeNotificationsEnabled) {
|
|
719
|
+
const notificationPayload = {
|
|
720
|
+
title,
|
|
721
|
+
body: text,
|
|
722
|
+
tag,
|
|
723
|
+
kind: 'ready',
|
|
724
|
+
assistantID: assistantID.trim(),
|
|
725
|
+
requireHidden: false,
|
|
726
|
+
};
|
|
727
|
+
const desktopNotificationDelivered = emitDesktopNotification(notificationPayload);
|
|
728
|
+
broadcastUiNotification(notificationPayload, { desktopNotificationDelivered });
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
await fanoutPush(
|
|
732
|
+
{
|
|
733
|
+
title,
|
|
734
|
+
body: text,
|
|
735
|
+
tag,
|
|
736
|
+
data: {
|
|
737
|
+
url: `/?assistant=${encodeURIComponent(assistantID.trim())}`,
|
|
738
|
+
assistantID: assistantID.trim(),
|
|
739
|
+
sessionName: title,
|
|
740
|
+
type: 'ready',
|
|
741
|
+
},
|
|
742
|
+
},
|
|
743
|
+
{ requireNoSse: true },
|
|
744
|
+
);
|
|
745
|
+
};
|
|
746
|
+
|
|
716
747
|
return {
|
|
717
748
|
maybeSendPushForTrigger,
|
|
718
749
|
setAutoAcceptSession,
|
|
@@ -720,5 +751,6 @@ export const createNotificationTriggerRuntime = (deps) => {
|
|
|
720
751
|
setGetIsSessionAutoAccepting,
|
|
721
752
|
clearPendingPushBadge,
|
|
722
753
|
sendGoalSettlePush,
|
|
754
|
+
sendContactTurnNotification,
|
|
723
755
|
};
|
|
724
756
|
};
|
|
@@ -199,6 +199,45 @@ const completionPayload = (sessionId = 'ses_root', finish = 'stop') => ({
|
|
|
199
199
|
},
|
|
200
200
|
});
|
|
201
201
|
|
|
202
|
+
describe('contact turn notifications', () => {
|
|
203
|
+
it('sends an SMS-style title and body when a contact turn completes', async () => {
|
|
204
|
+
const { runtime, emitDesktopNotification, broadcastUiNotification, sendPushToAllUiSessions } = createRuntime();
|
|
205
|
+
await runtime.sendContactTurnNotification({
|
|
206
|
+
assistantID: 'asst_1',
|
|
207
|
+
name: '大小白',
|
|
208
|
+
body: '我去找一下',
|
|
209
|
+
status: 'complete',
|
|
210
|
+
});
|
|
211
|
+
expect(emitDesktopNotification).toHaveBeenCalledWith(expect.objectContaining({
|
|
212
|
+
title: '大小白',
|
|
213
|
+
body: '我去找一下',
|
|
214
|
+
tag: 'contact-asst_1',
|
|
215
|
+
kind: 'ready',
|
|
216
|
+
assistantID: 'asst_1',
|
|
217
|
+
}));
|
|
218
|
+
expect(broadcastUiNotification).toHaveBeenCalled();
|
|
219
|
+
expect(sendPushToAllUiSessions).toHaveBeenCalledWith(expect.objectContaining({
|
|
220
|
+
title: '大小白',
|
|
221
|
+
body: '我去找一下',
|
|
222
|
+
data: expect.objectContaining({ assistantID: 'asst_1', type: 'ready' }),
|
|
223
|
+
}), { requireNoSse: true });
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it('skips contact notifications when completion notices are disabled', async () => {
|
|
227
|
+
const { runtime, emitDesktopNotification, sendPushToAllUiSessions } = createRuntime({
|
|
228
|
+
readSettingsFromDisk: vi.fn(async () => ({ ...defaultSettings, notifyOnCompletion: false })),
|
|
229
|
+
});
|
|
230
|
+
await runtime.sendContactTurnNotification({
|
|
231
|
+
assistantID: 'asst_1',
|
|
232
|
+
name: '大小白',
|
|
233
|
+
body: 'hi',
|
|
234
|
+
status: 'complete',
|
|
235
|
+
});
|
|
236
|
+
expect(emitDesktopNotification).not.toHaveBeenCalled();
|
|
237
|
+
expect(sendPushToAllUiSessions).not.toHaveBeenCalled();
|
|
238
|
+
});
|
|
239
|
+
});
|
|
240
|
+
|
|
202
241
|
describe('notification trigger live activity end', () => {
|
|
203
242
|
it('ends the live activity on top-level completion and error', async () => {
|
|
204
243
|
vi.stubGlobal('fetch', vi.fn(async () => rootSessionResponse()));
|
|
@@ -232,6 +271,28 @@ describe('notification trigger live activity end', () => {
|
|
|
232
271
|
expect(sendApnsToAllUiSessions).toHaveBeenCalledTimes(1);
|
|
233
272
|
});
|
|
234
273
|
|
|
274
|
+
it('does not patch live activity titles for child sessions', async () => {
|
|
275
|
+
vi.stubGlobal('fetch', vi.fn(async () => jsonResponse({
|
|
276
|
+
id: 'ses_child',
|
|
277
|
+
parentID: 'ses_parent',
|
|
278
|
+
metadata: {},
|
|
279
|
+
})));
|
|
280
|
+
const { runtime, sendLiveActivityEnd } = createRuntime();
|
|
281
|
+
await runtime.maybeSendPushForTrigger({
|
|
282
|
+
type: 'session.updated',
|
|
283
|
+
properties: {
|
|
284
|
+
directory: '/repo',
|
|
285
|
+
info: {
|
|
286
|
+
id: 'ses_child',
|
|
287
|
+
sessionID: 'ses_child',
|
|
288
|
+
parentID: 'ses_parent',
|
|
289
|
+
title: 'Fixer',
|
|
290
|
+
},
|
|
291
|
+
},
|
|
292
|
+
});
|
|
293
|
+
expect(sendLiveActivityEnd).not.toHaveBeenCalled();
|
|
294
|
+
});
|
|
295
|
+
|
|
235
296
|
it('suppresses live activity end for child and small-model sessions', async () => {
|
|
236
297
|
const fetchMock = vi.fn(async (url) => {
|
|
237
298
|
if (String(url).includes('ses_child')) {
|
|
@@ -15,6 +15,7 @@ import { registerScheduledTaskToolRoute } from '../scheduled-tasks/managed-tool-
|
|
|
15
15
|
import { registerConversationRoutes } from '../conversations/routes.js';
|
|
16
16
|
import { registerSessionTurnPageRoutes } from '../session-turn-pages/routes.js';
|
|
17
17
|
import { registerAssistantRoutes } from '../assistants/routes.js';
|
|
18
|
+
import { registerLlmRoutes } from '../llm/routes.js';
|
|
18
19
|
import { registerMessageQueueRoutes } from '../message-queue/routes.js';
|
|
19
20
|
import { registerSkillRoutes } from './skill-routes.js';
|
|
20
21
|
import { registerPluginRoutes } from './plugin-routes.js';
|
|
@@ -126,6 +127,8 @@ export const createFeatureRoutesRuntime = (dependencies) => {
|
|
|
126
127
|
broadcastGlobalUiEvent,
|
|
127
128
|
globalMessageStreamHub,
|
|
128
129
|
getServerId,
|
|
130
|
+
sessionIndexService,
|
|
131
|
+
notifyContactTurnComplete,
|
|
129
132
|
} = routeDependencies;
|
|
130
133
|
|
|
131
134
|
registerSettingsUtilityRoutes(app, {
|
|
@@ -215,6 +218,15 @@ export const createFeatureRoutesRuntime = (dependencies) => {
|
|
|
215
218
|
getOpenChamberEventClients,
|
|
216
219
|
writeSseEvent,
|
|
217
220
|
});
|
|
221
|
+
// Contact turn lifecycle / bubble deltas — same SSE bus, not revision watermark.
|
|
222
|
+
const broadcastContactTurnEvent = createOpenChamberEventBroadcaster({
|
|
223
|
+
getOpenChamberEventClients,
|
|
224
|
+
writeSseEvent,
|
|
225
|
+
});
|
|
226
|
+
registerLlmRoutes(app, {
|
|
227
|
+
buildOpenCodeUrl,
|
|
228
|
+
getOpenCodeAuthHeaders,
|
|
229
|
+
});
|
|
218
230
|
assistantRoutesRuntime = registerAssistantRoutes(app, {
|
|
219
231
|
openchamberDataDir,
|
|
220
232
|
dbPath: path.join(openchamberDataDir, 'assistants.sqlite'),
|
|
@@ -222,12 +234,30 @@ export const createFeatureRoutesRuntime = (dependencies) => {
|
|
|
222
234
|
getOpenCodeAuthHeaders,
|
|
223
235
|
getServerId,
|
|
224
236
|
getAllowedRoots: () => assistantAllowedRoots,
|
|
237
|
+
listProjects: async () => {
|
|
238
|
+
const settings = await readSettingsFromDiskMigrated();
|
|
239
|
+
return sanitizeProjects(settings?.projects ?? [])
|
|
240
|
+
.filter((project) => project && typeof project.id === 'string' && typeof project.path === 'string')
|
|
241
|
+
.map((project) => ({
|
|
242
|
+
id: project.id,
|
|
243
|
+
path: project.path,
|
|
244
|
+
...(typeof project.label === 'string' && project.label.trim()
|
|
245
|
+
? { label: project.label.trim() }
|
|
246
|
+
: {}),
|
|
247
|
+
}));
|
|
248
|
+
},
|
|
249
|
+
listScheduledTasks: (projectID) => projectConfigRuntime.listScheduledTasks(projectID),
|
|
250
|
+
sessionIndexService,
|
|
251
|
+
upsertScheduledTask: (projectID, task) => projectConfigRuntime.upsertScheduledTask(projectID, task),
|
|
252
|
+
syncScheduledTaskProject: (projectID) => scheduledTasksRuntime.syncProject(projectID),
|
|
225
253
|
refreshAllowedRoots: refreshAssistantAllowedRoots,
|
|
226
254
|
globalEventHub: globalMessageStreamHub,
|
|
227
255
|
onRevisionTip: (tip) => broadcastAssistantRevisionTip({
|
|
228
256
|
type: 'openchamber:assistants-changed',
|
|
229
257
|
properties: tip,
|
|
230
258
|
}),
|
|
259
|
+
onContactTurnEvent: (event) => broadcastContactTurnEvent(event),
|
|
260
|
+
onContactTurnComplete: (event) => notifyContactTurnComplete?.(event),
|
|
231
261
|
});
|
|
232
262
|
messageQueueRuntime?.setAssistantDeliveryService?.(assistantRoutesRuntime.service);
|
|
233
263
|
|
|
@@ -3,6 +3,26 @@ import { describe, expect, it, vi } from 'vitest';
|
|
|
3
3
|
import { createWorktreeTopologyBroadcaster } from './feature-routes-runtime.js';
|
|
4
4
|
|
|
5
5
|
describe('feature routes runtime composition', () => {
|
|
6
|
+
it('wires scheduled-task upsert into assistant contact tools', async () => {
|
|
7
|
+
const source = await fs.readFile(new URL('./feature-routes-runtime.js', import.meta.url), 'utf8');
|
|
8
|
+
expect(source).toMatch(/registerAssistantRoutes\(app, \{[\s\S]*upsertScheduledTask:[\s\S]*projectConfigRuntime\.upsertScheduledTask/);
|
|
9
|
+
expect(source).toMatch(/registerAssistantRoutes\(app, \{[\s\S]*syncScheduledTaskProject:[\s\S]*scheduledTasksRuntime\.syncProject/);
|
|
10
|
+
expect(source).toMatch(/registerAssistantRoutes\(app, \{[\s\S]*listProjects:/);
|
|
11
|
+
expect(source).toMatch(/registerAssistantRoutes\(app, \{[\s\S]*listScheduledTasks:[\s\S]*projectConfigRuntime\.listScheduledTasks/);
|
|
12
|
+
expect(source).toMatch(/registerAssistantRoutes\(app, \{[\s\S]*sessionIndexService/);
|
|
13
|
+
expect(source).toMatch(/label: project\.label\.trim\(\)/);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('fans contact turn events out on the OpenChamber SSE bus separately from revision tips', async () => {
|
|
17
|
+
const source = await fs.readFile(new URL('./feature-routes-runtime.js', import.meta.url), 'utf8');
|
|
18
|
+
expect(source).toMatch(/const broadcastContactTurnEvent = createOpenChamberEventBroadcaster\(/);
|
|
19
|
+
expect(source).toMatch(/onContactTurnEvent: \(event\) => broadcastContactTurnEvent\(event\)/);
|
|
20
|
+
expect(source).toMatch(/onContactTurnComplete: \(event\) => notifyContactTurnComplete\?\.\(event\)/);
|
|
21
|
+
expect(source).toMatch(/onRevisionTip: \(tip\) => broadcastAssistantRevisionTip\(\{\s*type: 'openchamber:assistants-changed'/);
|
|
22
|
+
// Contact turn envelopes stay off the assistants-changed revision watermark path.
|
|
23
|
+
expect(source).not.toMatch(/onRevisionTip:[\s\S]*contact-turn/);
|
|
24
|
+
});
|
|
25
|
+
|
|
6
26
|
it('registers the managed scheduled-task tool route with its required dependencies', async () => {
|
|
7
27
|
const source = await fs.readFile(new URL('./feature-routes-runtime.js', import.meta.url), 'utf8');
|
|
8
28
|
expect(source).toContain("import { registerScheduledTaskToolRoute } from '../scheduled-tasks/managed-tool-route.js';");
|
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import { createProjectIdFromPath } from '../projects/project-id.js';
|
|
2
2
|
|
|
3
3
|
const DEFAULT_NOTIFICATION_TEMPLATES = {
|
|
4
|
+
completion: { title: 'Task completed', message: '{session_name}' },
|
|
5
|
+
error: { title: 'Something went wrong', message: '{session_name}' },
|
|
6
|
+
question: { title: 'Needs your answer', message: '{session_name}' },
|
|
7
|
+
subtask: { title: 'Task completed', message: '{session_name}' },
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
const LEGACY_NOTIFICATION_TEMPLATES = {
|
|
4
11
|
completion: { title: '{agent_name} is ready', message: '{model_name} completed the task' },
|
|
5
12
|
error: { title: 'Tool error', message: '{last_message}' },
|
|
6
13
|
question: { title: 'Input needed', message: '{last_message}' },
|
|
@@ -20,6 +27,15 @@ const ensureNotificationTemplateShape = (templates) => {
|
|
|
20
27
|
if (!currentEntry || typeof currentEntry.title !== 'string' || typeof currentEntry.message !== 'string') {
|
|
21
28
|
changed = true;
|
|
22
29
|
}
|
|
30
|
+
const legacy = LEGACY_NOTIFICATION_TEMPLATES[event];
|
|
31
|
+
const isLegacyDefault = legacy
|
|
32
|
+
&& currentTitle === legacy.title
|
|
33
|
+
&& currentMessage === legacy.message;
|
|
34
|
+
if (isLegacyDefault) {
|
|
35
|
+
changed = true;
|
|
36
|
+
next[event] = { title: base.title, message: base.message };
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
23
39
|
next[event] = { title: currentTitle, message: currentMessage };
|
|
24
40
|
}
|
|
25
41
|
|
|
@@ -650,8 +666,8 @@ export const createSettingsRuntime = (deps) => {
|
|
|
650
666
|
return { settings, changed: false };
|
|
651
667
|
}
|
|
652
668
|
|
|
653
|
-
const defaultLight = '
|
|
654
|
-
const defaultDark = '
|
|
669
|
+
const defaultLight = 'carbonfox-light';
|
|
670
|
+
const defaultDark = 'vitesse-dark-dark';
|
|
655
671
|
|
|
656
672
|
let nextLightThemeId = hasLight ? settings.lightThemeId : undefined;
|
|
657
673
|
let nextDarkThemeId = hasDark ? settings.darkThemeId : undefined;
|
|
@@ -135,7 +135,10 @@ before touching the filesystem). Rationale: metadata rides every
|
|
|
135
135
|
continuation prompt using the last assistant message's
|
|
136
136
|
provider/model/agent — the goal spends the session's own subscription.
|
|
137
137
|
4. Settling (`complete`/`blocked`/`budgetLimited`) fires the injected
|
|
138
|
-
`emitGoalNotification` so the user hears about it even with the UI closed
|
|
138
|
+
`emitGoalNotification` so the user hears about it even with the UI closed.
|
|
139
|
+
The same settle also notifies a registered Assistant contact reporter
|
|
140
|
+
(`setAssignedSessionSettleHandler`) so an assigned/watched session updates
|
|
141
|
+
its contact card — read-only, no second scheduler:
|
|
139
142
|
desktop + UI broadcast + the standard push fanout (web-push with full
|
|
140
143
|
text; APNs with a generic per-type title and the session name as body).
|
|
141
144
|
It obeys the notify-on-completion setting. Conversely, while a goal is
|
|
@@ -180,6 +183,10 @@ The loop here picks it up from session events like any other goal.
|
|
|
180
183
|
|
|
181
184
|
## Limitations
|
|
182
185
|
|
|
186
|
+
- TODO(watch): Assistant contact assign skipped a watch tool this slice.
|
|
187
|
+
Goal settle already notifies. Do not invent a second scheduler. A later
|
|
188
|
+
thin contact tool can post a read-only session status card when
|
|
189
|
+
`metadata.openchamber.goal` settles.
|
|
183
190
|
- Web-server feature: VS Code (extension-only) renders goal state via
|
|
184
191
|
`session.updated` but does not run the loop.
|
|
185
192
|
- A goal on a session with no assistant reply yet starts after the first
|
|
@@ -286,6 +286,18 @@ export const accountGoalTokenSpend = ({ messages, goal }) => {
|
|
|
286
286
|
return { tokensUsed, lastAccountedMessageID };
|
|
287
287
|
};
|
|
288
288
|
|
|
289
|
+
let assignedSessionSettle = null;
|
|
290
|
+
|
|
291
|
+
export const setAssignedSessionSettleHandler = (handler) => {
|
|
292
|
+
assignedSessionSettle = typeof handler === 'function' ? handler : null;
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
const contactStatusFromGoal = (status) => {
|
|
296
|
+
if (status === 'complete' || status === 'budgetLimited') return 'complete';
|
|
297
|
+
if (status === 'blocked') return 'error';
|
|
298
|
+
return null;
|
|
299
|
+
};
|
|
300
|
+
|
|
289
301
|
export const createSessionGoalRuntime = ({
|
|
290
302
|
buildOpenCodeUrl,
|
|
291
303
|
getOpenCodeAuthHeaders,
|
|
@@ -401,6 +413,14 @@ export const createSessionGoalRuntime = ({
|
|
|
401
413
|
console.warn('[session-goal] notification failed:', error?.message || error);
|
|
402
414
|
}
|
|
403
415
|
}
|
|
416
|
+
const contactStatus = contactStatusFromGoal(status);
|
|
417
|
+
if (contactStatus && typeof assignedSessionSettle === 'function') {
|
|
418
|
+
try {
|
|
419
|
+
assignedSessionSettle({ sessionId, directory, status: contactStatus });
|
|
420
|
+
} catch (error) {
|
|
421
|
+
console.warn('[session-goal] assigned session settle failed:', error?.message || error);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
404
424
|
};
|
|
405
425
|
|
|
406
426
|
const runAudit = async ({ goal, assistantText, directory, lastAssistantInfo }) => {
|
|
@@ -15,7 +15,9 @@ Sessions titled `smartfetch-secondary` are temporary SmartFetch model calls;
|
|
|
15
15
|
the index excludes them from every snapshot and clears prior summaries when a
|
|
16
16
|
matching session update arrives. System-owned sessions are excluded by
|
|
17
17
|
authoritative metadata only: a non-empty
|
|
18
|
-
`metadata.openchamber.assistant.assistantID
|
|
18
|
+
`metadata.openchamber.assistant.assistantID` (unless
|
|
19
|
+
`metadata.openchamber.assigned.from === 'contact'` — those are visible
|
|
20
|
+
coding workers the Assistant subscribed to),
|
|
19
21
|
`metadata.openchamber.scheduledTask.taskID`, or
|
|
20
22
|
`metadata.openchamber.smallModel.purpose`. Title prefixes are human labels and
|
|
21
23
|
never participate in this filter. Metadata is the ownership/isolation signal;
|
|
@@ -10,10 +10,14 @@ const HIDDEN_SESSION_TITLES = new Set(['smartfetch-secondary']);
|
|
|
10
10
|
const nonEmptySystemID = (value) => typeof value === 'string' && value.length > 0;
|
|
11
11
|
|
|
12
12
|
/** System sessions are isolated by authoritative metadata only — never by title prefix. */
|
|
13
|
+
const isContactAssignedSession = (openchamber) => openchamber?.assigned?.from === 'contact';
|
|
14
|
+
|
|
13
15
|
const isSystemSession = (session) => {
|
|
14
16
|
const openchamber = session?.metadata?.openchamber;
|
|
15
17
|
if (!openchamber || typeof openchamber !== 'object') return false;
|
|
16
|
-
|
|
18
|
+
// Contact-assigned workers stay visible in Chat. Only archived Assistant
|
|
19
|
+
// bindings use `openchamber.assistant` as a hide marker.
|
|
20
|
+
if (nonEmptySystemID(openchamber.assistant?.assistantID) && !isContactAssignedSession(openchamber)) return true;
|
|
17
21
|
if (nonEmptySystemID(openchamber.scheduledTask?.taskID)) return true;
|
|
18
22
|
if (nonEmptySystemID(openchamber.smallModel?.purpose)) return true;
|
|
19
23
|
return false;
|
|
@@ -52,7 +56,9 @@ const toSummary = (session, fallbackDirectory) => {
|
|
|
52
56
|
if (!session || !isVisibleSession(session) || typeof session.id !== 'string' || !session.id) return null;
|
|
53
57
|
const directory = normalizeDirectory(session.directory ?? session.project?.worktree ?? fallbackDirectory);
|
|
54
58
|
if (!directory) return null;
|
|
55
|
-
const
|
|
59
|
+
const openchamber = session.metadata?.openchamber;
|
|
60
|
+
const assistant = openchamber?.assistant;
|
|
61
|
+
const hideAssistantMarker = isContactAssignedSession(openchamber);
|
|
56
62
|
return {
|
|
57
63
|
id: session.id,
|
|
58
64
|
directory,
|
|
@@ -63,8 +69,8 @@ const toSummary = (session, fallbackDirectory) => {
|
|
|
63
69
|
archivedAt: toTimestamp(session.time?.archived),
|
|
64
70
|
parentID: typeof session.parentID === 'string' ? session.parentID : null,
|
|
65
71
|
hasChildren: typeof session.hasChildren === 'boolean' ? session.hasChildren : null,
|
|
66
|
-
assistantID: typeof assistant?.assistantID
|
|
67
|
-
assistantName: typeof assistant?.name
|
|
72
|
+
assistantID: hideAssistantMarker || typeof assistant?.assistantID !== 'string' ? null : assistant.assistantID,
|
|
73
|
+
assistantName: hideAssistantMarker || typeof assistant?.name !== 'string' ? null : assistant.name,
|
|
68
74
|
};
|
|
69
75
|
};
|
|
70
76
|
|
|
@@ -100,6 +100,43 @@ describe('Electron session index', () => {
|
|
|
100
100
|
service.close();
|
|
101
101
|
});
|
|
102
102
|
|
|
103
|
+
it('keeps contact-assigned worker sessions visible in ordinary sidebar summaries', () => {
|
|
104
|
+
const runtimeRef = { value: 'http://runtime-a.test' };
|
|
105
|
+
const service = createService(runtimeRef);
|
|
106
|
+
const worker = {
|
|
107
|
+
...session('ses_worker', 100),
|
|
108
|
+
metadata: {
|
|
109
|
+
openchamber: {
|
|
110
|
+
assigned: { from: 'contact', assistantID: 'assistant_1', name: 'A' },
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
const legacyWorker = {
|
|
115
|
+
...session('ses_legacy_worker', 99),
|
|
116
|
+
metadata: {
|
|
117
|
+
openchamber: {
|
|
118
|
+
assistant: { assistantID: 'assistant_1', name: 'A' },
|
|
119
|
+
assigned: { from: 'contact' },
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
const hiddenBinding = {
|
|
124
|
+
...session('ses_binding', 98),
|
|
125
|
+
metadata: { openchamber: { assistant: { assistantID: 'assistant_1', name: 'A' } } },
|
|
126
|
+
};
|
|
127
|
+
service.replaceDirectory({
|
|
128
|
+
directory: '/repo',
|
|
129
|
+
sessions: [worker, legacyWorker, hiddenBinding],
|
|
130
|
+
cursor: null,
|
|
131
|
+
hasMore: false,
|
|
132
|
+
});
|
|
133
|
+
expect(service.snapshot().directories[0].sessions.map((item) => item.id)).toEqual([
|
|
134
|
+
'ses_worker',
|
|
135
|
+
'ses_legacy_worker',
|
|
136
|
+
]);
|
|
137
|
+
service.close();
|
|
138
|
+
});
|
|
139
|
+
|
|
103
140
|
it('keeps runtime targets isolated in one Electron database', () => {
|
|
104
141
|
const runtimeRef = { value: 'http://runtime-a.test' };
|
|
105
142
|
const service = createService(runtimeRef);
|
|
@@ -163,6 +163,7 @@ const extractSessionStatus = (payload) => {
|
|
|
163
163
|
export const isSystemOwnedSession = (sessionOrInfo) => {
|
|
164
164
|
const openchamber = sessionOrInfo?.metadata?.openchamber;
|
|
165
165
|
if (!openchamber || typeof openchamber !== 'object') return false;
|
|
166
|
+
if (openchamber.assigned?.from === 'contact') return false;
|
|
166
167
|
if (typeof openchamber.assistant?.assistantID === 'string' && openchamber.assistant.assistantID) return true;
|
|
167
168
|
if (typeof openchamber.scheduledTask?.taskID === 'string' && openchamber.scheduledTask.taskID) return true;
|
|
168
169
|
if (typeof openchamber.smallModel?.purpose === 'string' && openchamber.smallModel.purpose) return true;
|
|
@@ -243,6 +243,17 @@ describe('session-title helpers', () => {
|
|
|
243
243
|
metadata: { openchamber: { smallModel: { purpose: '' } } },
|
|
244
244
|
})).toBe(false);
|
|
245
245
|
expect(isSystemOwnedSession({ title: 'Ordinary' })).toBe(false);
|
|
246
|
+
expect(isSystemOwnedSession({
|
|
247
|
+
metadata: { openchamber: { assigned: { from: 'contact', assistantID: 'assistant_1' } } },
|
|
248
|
+
})).toBe(false);
|
|
249
|
+
expect(isSystemOwnedSession({
|
|
250
|
+
metadata: {
|
|
251
|
+
openchamber: {
|
|
252
|
+
assistant: { assistantID: 'assistant_1' },
|
|
253
|
+
assigned: { from: 'contact' },
|
|
254
|
+
},
|
|
255
|
+
},
|
|
256
|
+
})).toBe(false);
|
|
246
257
|
});
|
|
247
258
|
|
|
248
259
|
it('skips title generation and patch for system-owned sessions', async () => {
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import"./rolldown-runtime-C0FnF6B9.js";import{nt as e,rt as t}from"./vendor-base-ui-CnHfUmDN.js";import{n}from"./vendor-react-DLPElpoQ.js";import{c as r,l as i}from"./dialog-HCE4eLUW.js";import{t as a}from"./utils-CTL16t2f.js";var o=n();t();var s=e(),c=e=>{let t=(0,o.c)(27),{name:n,emoji:c,size:u,className:d,label:f}=e,p=u===void 0?16:u,m,h,g;if(t[0]!==d||t[1]!==c||t[2]!==n){m=i(n);let e;t[6]===n?e=t[7]:(e=r(n),t[6]=n,t[7]=e),h=e,g=a(`inline-flex aspect-square shrink-0 overflow-hidden rounded-sm`,c&&`items-center justify-center select-none`,m.class,d),t[0]=d,t[1]=c,t[2]=n,t[3]=m,t[4]=h,t[5]=g}else m=t[3],h=t[4],g=t[5];let _=`calc(${p}px * var(--dpt-n, 1))`,v=`calc(${p}px * var(--dpt-n, 1))`,y=`calc(${p}px * var(--dpt-n, 1))`,b=`calc(${p}px * var(--dpt-n, 1))`,x=`calc(${p}px * var(--dpt-n, 1))`,S=`calc(${p}px * var(--dpt-n, 1))`,C=`var(${m.var})`,w;t[8]!==_||t[9]!==v||t[10]!==y||t[11]!==b||t[12]!==x||t[13]!==S||t[14]!==C?(w={width:_,minWidth:v,maxWidth:y,height:b,minHeight:x,maxHeight:S,color:C},t[8]=_,t[9]=v,t[10]=y,t[11]=b,t[12]=x,t[13]=S,t[14]=C,t[15]=w):w=t[15];let T=f?`img`:void 0,E=!f||void 0,D;t[16]!==c||t[17]!==h||t[18]!==p?(D=c?(0,s.jsx)(`span`,{"aria-hidden":`true`,style:{fontSize:`calc(${p*.75}px * var(--dpt-n, 1))`,lineHeight:1},children:c}):(0,s.jsxs)(`svg`,{width:p,height:p,viewBox:`0 0 5 5`,shapeRendering:`crispEdges`,className:`oc-agent-avatar block h-full w-full`,"aria-hidden":`true`,children:[(0,s.jsx)(`rect`,{width:`5`,height:`5`,fill:`currentColor`,opacity:.14}),h.map(l)]}),t[16]=c,t[17]=h,t[18]=p,t[19]=D):D=t[19];let O;return t[20]!==f||t[21]!==w||t[22]!==T||t[23]!==E||t[24]!==D||t[25]!==g?(O=(0,s.jsx)(`span`,{className:g,style:w,role:T,"aria-label":f,"aria-hidden":E,children:D}),t[20]=f,t[21]=w,t[22]=T,t[23]=E,t[24]=D,t[25]=g,t[26]=O):O=t[26],O};function l(e,t){return e.map((e,n)=>e?(0,s.jsx)(`rect`,{x:n,y:t,width:`1`,height:`1`,fill:`currentColor`},`${n}-${t}`):null)}export{c as t};
|