@openchambery/web 1.18.2-beta.15 → 1.18.2-beta.17
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/{AssistantView-qRCdLcZN.js → AssistantView-DFHmpZ7G.js} +1 -1
- package/dist/assets/{ChatContainer-CRWQAE6E.js → ChatContainer-D7SxgV8T.js} +4 -4
- package/dist/assets/{DiagramView-B34t9o0C.js → DiagramView-DmHeDjDE.js} +1 -1
- package/dist/assets/{MarkdownRendererImpl-CvpH7Y_l.js → MarkdownRendererImpl-CR70vDmk.js} +1 -1
- package/dist/assets/{MobileDetailNavigation-CiZtnbwn.js → MobileDetailNavigation-CosmubCm.js} +1 -1
- package/dist/assets/{MobileShareBridge-DwysiJW7.js → MobileShareBridge-DzP5vJAv.js} +1 -1
- package/dist/assets/{MobileSurface-D1GUXnAt.js → MobileSurface-bpfoNOdp.js} +1 -1
- package/dist/assets/{MultiRunWindow-CDENQDyB.js → MultiRunWindow-Bj35xImj.js} +1 -1
- package/dist/assets/{OnboardingScreen-7mFH7iBm.js → OnboardingScreen-CYeRZznl.js} +1 -1
- package/dist/assets/{OverlayScrollbar-CmhitwHL.js → OverlayScrollbar-C0r95yEi.js} +1 -1
- package/dist/assets/{SettingsView-Dv566NkF.js → SettingsView-DlUCG6xP.js} +1 -1
- package/dist/assets/{SettingsWindow-CbVwm2T6.js → SettingsWindow-Bx6yUy4t.js} +1 -1
- package/dist/assets/{TerminalView-BfTCj_PO.js → TerminalView-A6lT_pyN.js} +1 -1
- package/dist/assets/{ToolOutputDialog-CdAO6ADz.js → ToolOutputDialog-74Ec3sow.js} +1 -1
- package/dist/assets/{appThemeRegistry-Cvo1QkKA.js → appThemeRegistry-C2czYOtx.js} +2 -2
- package/dist/assets/{clipboard-Cm4VHQ3n.js → clipboard-BnElHSnO.js} +1 -1
- package/dist/assets/{context-menu-C7P3at3V.js → context-menu-B87qIDjC.js} +1 -1
- package/dist/assets/{dialog-ruyemRYh.js → dialog-COrLBm9O.js} +1 -1
- package/dist/assets/{es-W_z2kSYf.js → es-DIVYdoxi.js} +1 -1
- package/dist/assets/{fr-MYuf2QNQ.js → fr-CNTlVypq.js} +1 -1
- package/dist/assets/{gitApi-GkEdeq0D.js → gitApi-CAYz6qHu.js} +1 -1
- package/dist/assets/{insertionBoundaries-CDxRoLe7.js → insertionBoundaries-iAOuvfny.js} +1 -1
- package/dist/assets/{ja-DBkl4NXl.js → ja-C_eW5ZM8.js} +1 -1
- package/dist/assets/{ko-DwSsHsxa.js → ko-CE3cOv7v.js} +1 -1
- package/dist/assets/{main-B-Pz3EI_.js → main-BQGhNyeU.js} +4 -4
- package/dist/assets/{main-CKV54Ayt.js → main-Cu7sZyJS.js} +2 -2
- package/dist/assets/{miniChat-D8VtCIh9.js → miniChat-CwrVreq-.js} +2 -2
- package/dist/assets/{mobile-B8jXmmPT.js → mobile-SQEAwCJ8.js} +2 -2
- package/dist/assets/{multirun-CQorhgZD.js → multirun-CJ3fED_0.js} +1 -1
- package/dist/assets/{pl-BGLpnSSE.js → pl-BZKZOE99.js} +1 -1
- package/dist/assets/{projectMeta-D-t3tPnX.js → projectMeta-BM3a0rwT.js} +1 -1
- package/dist/assets/{pt-BR-DuEh49lp.js → pt-BR-DHDgp8S3.js} +1 -1
- package/dist/assets/{radio-BIl2YIoa.js → radio-DfR3It8I.js} +1 -1
- package/dist/assets/{renderElectronMiniChatApp-Dn0BTQ2T.js → renderElectronMiniChatApp-b3VyI_V2.js} +2 -2
- package/dist/assets/renderMobileApp-DrU0se98.js +23 -0
- package/dist/assets/{runtimeEndpointReset-B1IMFiKd.js → runtimeEndpointReset-3gL_biDm.js} +1 -1
- package/dist/assets/{sortable-tabs-strip-QDIkcsUA.js → sortable-tabs-strip-D0fuWvzp.js} +1 -1
- package/dist/assets/src-DcPZ_rWS.css +2 -0
- package/dist/assets/{store-Cb8Q6EtD.js → store-CUWqzRt7.js} +2 -2
- package/dist/assets/{uk-CUoSz-Q8.js → uk-CfQK6cz1.js} +1 -1
- package/dist/assets/{useAppFontEffects-BwmqFEKp.js → useAppFontEffects-AWfqdYZm.js} +2 -2
- package/dist/assets/{useDesktopWindowControlsLayout-B5LGS8S5.js → useDesktopWindowControlsLayout-BgCS8KRJ.js} +1 -1
- package/dist/assets/{useEffectiveDirectory-BgIUpNz0.js → useEffectiveDirectory-ktSK1ADz.js} +1 -1
- package/dist/assets/{useMobileNavigationStore-DIRiAvR1.js → useMobileNavigationStore-Dy5a1iTA.js} +4 -4
- package/dist/assets/{useSessionAutoCleanup-Ypc623bG.js → useSessionAutoCleanup-BRYDBUlq.js} +1 -1
- package/dist/assets/{useWorkerHighlightedLines-BxaH8sAx.js → useWorkerHighlightedLines-RexsXGPu.js} +1 -1
- package/dist/assets/{zh-CN-CJP5FWCK.js → zh-CN-DvY2fKWp.js} +1 -1
- package/dist/assets/{zh-TW-B_2h1yuo.js → zh-TW-eWe0lm3-.js} +1 -1
- package/dist/index.html +2 -2
- package/dist/mini-chat.html +2 -2
- package/dist/mobile.html +2 -2
- package/package.json +1 -1
- package/server/lib/session-title/DOCUMENTATION.md +16 -12
- package/server/lib/session-title/runtime.js +7 -8
- package/server/lib/session-title/runtime.test.js +225 -0
- package/dist/assets/renderMobileApp-BE9NXDDC.js +0 -23
- package/dist/assets/src-C8jn0x69.css +0 -2
package/dist/index.html
CHANGED
|
@@ -552,7 +552,7 @@
|
|
|
552
552
|
opacity: 0.72;
|
|
553
553
|
}
|
|
554
554
|
</style>
|
|
555
|
-
<script type="module" crossorigin src="/assets/main-
|
|
555
|
+
<script type="module" crossorigin src="/assets/main-Cu7sZyJS.js"></script>
|
|
556
556
|
<link rel="modulepreload" crossorigin href="/assets/rolldown-runtime-C0FnF6B9.js">
|
|
557
557
|
<link rel="modulepreload" crossorigin href="/assets/vendor-base-ui-CnHfUmDN.js">
|
|
558
558
|
<link rel="modulepreload" crossorigin href="/assets/vendor-zustand-B4dlGEM2.js">
|
|
@@ -568,7 +568,7 @@
|
|
|
568
568
|
<link rel="modulepreload" crossorigin href="/assets/gitApiHttp-BxGAFLTf.js">
|
|
569
569
|
<link rel="modulepreload" crossorigin href="/assets/terminalApi-DpiAJlil.js">
|
|
570
570
|
<link rel="modulepreload" crossorigin href="/assets/runtimeConfig-BXCt7ZsQ.js">
|
|
571
|
-
<link rel="stylesheet" crossorigin href="/assets/src-
|
|
571
|
+
<link rel="stylesheet" crossorigin href="/assets/src-DcPZ_rWS.css">
|
|
572
572
|
</head>
|
|
573
573
|
<body class="h-full bg-background text-foreground">
|
|
574
574
|
<!-- Keep the startup layer outside #root. React replaces every child of
|
package/dist/mini-chat.html
CHANGED
|
@@ -74,7 +74,7 @@
|
|
|
74
74
|
}
|
|
75
75
|
</style>
|
|
76
76
|
|
|
77
|
-
<script type="module" crossorigin src="/assets/miniChat-
|
|
77
|
+
<script type="module" crossorigin src="/assets/miniChat-CwrVreq-.js"></script>
|
|
78
78
|
<link rel="modulepreload" crossorigin href="/assets/rolldown-runtime-C0FnF6B9.js">
|
|
79
79
|
<link rel="modulepreload" crossorigin href="/assets/vendor-base-ui-CnHfUmDN.js">
|
|
80
80
|
<link rel="modulepreload" crossorigin href="/assets/vendor-zustand-B4dlGEM2.js">
|
|
@@ -90,7 +90,7 @@
|
|
|
90
90
|
<link rel="modulepreload" crossorigin href="/assets/gitApiHttp-BxGAFLTf.js">
|
|
91
91
|
<link rel="modulepreload" crossorigin href="/assets/terminalApi-DpiAJlil.js">
|
|
92
92
|
<link rel="modulepreload" crossorigin href="/assets/runtimeConfig-BXCt7ZsQ.js">
|
|
93
|
-
<link rel="stylesheet" crossorigin href="/assets/src-
|
|
93
|
+
<link rel="stylesheet" crossorigin href="/assets/src-DcPZ_rWS.css">
|
|
94
94
|
</head>
|
|
95
95
|
<body class="h-full bg-background text-foreground">
|
|
96
96
|
<div id="root" class="h-full">
|
package/dist/mobile.html
CHANGED
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
})();
|
|
64
64
|
</script>
|
|
65
65
|
|
|
66
|
-
<script type="module" crossorigin src="/assets/mobile-
|
|
66
|
+
<script type="module" crossorigin src="/assets/mobile-SQEAwCJ8.js"></script>
|
|
67
67
|
<link rel="modulepreload" crossorigin href="/assets/rolldown-runtime-C0FnF6B9.js">
|
|
68
68
|
<link rel="modulepreload" crossorigin href="/assets/vendor-base-ui-CnHfUmDN.js">
|
|
69
69
|
<link rel="modulepreload" crossorigin href="/assets/vendor-zustand-B4dlGEM2.js">
|
|
@@ -79,7 +79,7 @@
|
|
|
79
79
|
<link rel="modulepreload" crossorigin href="/assets/gitApiHttp-BxGAFLTf.js">
|
|
80
80
|
<link rel="modulepreload" crossorigin href="/assets/terminalApi-DpiAJlil.js">
|
|
81
81
|
<link rel="modulepreload" crossorigin href="/assets/runtimeConfig-BXCt7ZsQ.js">
|
|
82
|
-
<link rel="stylesheet" crossorigin href="/assets/src-
|
|
82
|
+
<link rel="stylesheet" crossorigin href="/assets/src-DcPZ_rWS.css">
|
|
83
83
|
</head>
|
|
84
84
|
<body class="h-full bg-background text-foreground">
|
|
85
85
|
<div id="root" class="h-full"></div>
|
package/package.json
CHANGED
|
@@ -5,10 +5,11 @@ conversation's **main subject** (overall feature / goal) with the small model
|
|
|
5
5
|
(`lib/small-model`), then PATCHes `title` plus `metadata.openchamber.titleRefresh`.
|
|
6
6
|
|
|
7
7
|
OpenCode only auto-titles once from the first user message
|
|
8
|
-
(`SessionPrompt.ensureTitle`).
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
(`SessionPrompt.ensureTitle`). This module auto-refreshes sparsely (first idle
|
|
9
|
+
of a new session, first newly-sent reply on a fork) and on explicit smart-title
|
|
10
|
+
requests, naming the durable work being done — not the last wrap-up utterance
|
|
11
|
+
like "commit and push". Background auto refreshes still respect a 5-minute
|
|
12
|
+
throttle (`TITLE_THROTTLE_MS`) when a refresh is armed.
|
|
12
13
|
|
|
13
14
|
## Flow
|
|
14
15
|
|
|
@@ -16,14 +17,17 @@ minutes per session.
|
|
|
16
17
|
fan-out (`index.js` → `globalMessageStreamHub.subscribeEvent`), same
|
|
17
18
|
pattern as session-assist. Purely event-driven — dormant sessions never
|
|
18
19
|
generate anything.
|
|
19
|
-
2.
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
20
|
+
2. Auto title refresh is intentionally sparse (title stability first). A newly
|
|
21
|
+
observed root `session.created` generates its first title immediately on the
|
|
22
|
+
first `session.status: idle`. A fork title (`(fork #n)`) waits for its first
|
|
23
|
+
newly-created user message; the matching assistant completion triggers an
|
|
24
|
+
immediate title refresh that bypasses inherited title metadata and throttle.
|
|
25
|
+
Ordinary later idle transitions do **not** arm another refresh. Any
|
|
26
|
+
`busy`/`retry` status or a fresh user `message.updated` still clears an
|
|
27
|
+
already-armed timer (so initial/fork timers cancel if the user keeps going).
|
|
28
|
+
A sidebar smart-title request sets `titleRefresh.requestedAt`; its
|
|
29
|
+
`session.updated` event arms the same flow immediately (forced / manual
|
|
30
|
+
refresh is unaffected by the background gate).
|
|
27
31
|
3. On fire:
|
|
28
32
|
- Skip when Settings → Chat → session title refresh is off.
|
|
29
33
|
- Skip system-owned sessions whose metadata carries a non-empty
|
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
// Session title refresh:
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
1
|
+
// Session title refresh: regenerate the sidebar title from the conversation's
|
|
2
|
+
// MAIN SUBJECT (overall feature / goal), not just the last wrap-up utterance.
|
|
3
|
+
// OpenCode only titles once from the first user message. Auto refresh is sparse
|
|
4
|
+
// for title stability: first idle of a new session, first newly-sent reply on a
|
|
5
|
+
// fork, plus explicit smart-title / forced refresh. Continuing the same work
|
|
5
6
|
// should keep naming the thing being done — not "commit and push". This
|
|
6
7
|
// watcher throttles to at most one refresh per session every TITLE_THROTTLE_MS
|
|
7
8
|
// and never overwrites a user-renamed title.
|
|
8
9
|
//
|
|
9
|
-
// Purely event-driven: only sessions that
|
|
10
|
-
//
|
|
10
|
+
// Purely event-driven: only sessions that arm a refresh while the server is
|
|
11
|
+
// running ever generate anything. No backfill, no session scans.
|
|
11
12
|
|
|
12
13
|
import fs from 'fs';
|
|
13
14
|
import os from 'os';
|
|
@@ -742,8 +743,6 @@ export const createSessionTitleRuntime = ({
|
|
|
742
743
|
if (newSessions.delete(status.sessionId)) {
|
|
743
744
|
initialRefreshes.add(status.sessionId);
|
|
744
745
|
armTimer(status.sessionId, status.directory || directoryHint, 0);
|
|
745
|
-
} else {
|
|
746
|
-
armTimer(status.sessionId, status.directory || directoryHint, quietMs);
|
|
747
746
|
}
|
|
748
747
|
} else {
|
|
749
748
|
clearTimer(status.sessionId);
|
|
@@ -403,4 +403,229 @@ describe('session-title helpers', () => {
|
|
|
403
403
|
globalThis.fetch = originalFetch;
|
|
404
404
|
}
|
|
405
405
|
});
|
|
406
|
+
|
|
407
|
+
it('refreshes a new session on first idle, then ignores later idle transitions', async () => {
|
|
408
|
+
const originalFetch = globalThis.fetch;
|
|
409
|
+
const session = {
|
|
410
|
+
id: 'ses-new',
|
|
411
|
+
title: 'New session - 2026-07-10T12:00:00.000Z',
|
|
412
|
+
metadata: {},
|
|
413
|
+
};
|
|
414
|
+
const messages = [
|
|
415
|
+
{ info: { id: 'u1', role: 'user' }, parts: [{ type: 'text', text: 'Add rate limiting' }] },
|
|
416
|
+
{ info: { id: 'a1', role: 'assistant' }, parts: [{ type: 'text', text: 'Implemented rate limit' }] },
|
|
417
|
+
];
|
|
418
|
+
let generationCalls = 0;
|
|
419
|
+
let patchedTitle = session.title;
|
|
420
|
+
|
|
421
|
+
globalThis.fetch = async (input, init = {}) => {
|
|
422
|
+
const url = String(input);
|
|
423
|
+
if (url.includes('/message')) {
|
|
424
|
+
return new Response(JSON.stringify(messages), { status: 200 });
|
|
425
|
+
}
|
|
426
|
+
if (init.method === 'PATCH') {
|
|
427
|
+
const body = typeof init.body === 'string' ? JSON.parse(init.body) : init.body;
|
|
428
|
+
if (typeof body?.title === 'string') {
|
|
429
|
+
patchedTitle = body.title;
|
|
430
|
+
session.title = body.title;
|
|
431
|
+
session.metadata = body.metadata || session.metadata;
|
|
432
|
+
} else if (body?.metadata) {
|
|
433
|
+
session.metadata = body.metadata;
|
|
434
|
+
}
|
|
435
|
+
return new Response(JSON.stringify(session), { status: 200 });
|
|
436
|
+
}
|
|
437
|
+
return new Response(JSON.stringify({ ...session, title: patchedTitle }), { status: 200 });
|
|
438
|
+
};
|
|
439
|
+
|
|
440
|
+
try {
|
|
441
|
+
const runtime = createSessionTitleRuntime({
|
|
442
|
+
buildOpenCodeUrl: (pathname) => `http://opencode${pathname}`,
|
|
443
|
+
getOpenCodeAuthHeaders: () => ({}),
|
|
444
|
+
getSmallModelService: async () => ({
|
|
445
|
+
generateSmallModelText: async () => {
|
|
446
|
+
generationCalls += 1;
|
|
447
|
+
return { text: 'Rate limiting', providerID: 'test', modelID: 'test' };
|
|
448
|
+
},
|
|
449
|
+
}),
|
|
450
|
+
now: () => 1_000,
|
|
451
|
+
isTitleRefreshEnabled: () => true,
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
runtime.processPayload({
|
|
455
|
+
type: 'session.created',
|
|
456
|
+
properties: {
|
|
457
|
+
directory: '/repo',
|
|
458
|
+
info: { id: 'ses-new', title: session.title, time: { created: 100 } },
|
|
459
|
+
},
|
|
460
|
+
});
|
|
461
|
+
runtime.processPayload({
|
|
462
|
+
type: 'session.status',
|
|
463
|
+
properties: { sessionID: 'ses-new', directory: '/repo', status: { type: 'idle' } },
|
|
464
|
+
});
|
|
465
|
+
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
466
|
+
expect(generationCalls).toBe(1);
|
|
467
|
+
|
|
468
|
+
// Later conversation progress + idle must not auto-refresh again.
|
|
469
|
+
messages.push(
|
|
470
|
+
{ info: { id: 'u2', role: 'user' }, parts: [{ type: 'text', text: 'Also add retries' }] },
|
|
471
|
+
{ info: { id: 'a2', role: 'assistant' }, parts: [{ type: 'text', text: 'Retries added' }] },
|
|
472
|
+
);
|
|
473
|
+
runtime.processPayload({
|
|
474
|
+
type: 'message.updated',
|
|
475
|
+
properties: { directory: '/repo', info: { sessionID: 'ses-new', role: 'user', time: { created: 200 } } },
|
|
476
|
+
});
|
|
477
|
+
runtime.processPayload({
|
|
478
|
+
type: 'session.status',
|
|
479
|
+
properties: { sessionID: 'ses-new', directory: '/repo', status: { type: 'busy' } },
|
|
480
|
+
});
|
|
481
|
+
runtime.processPayload({
|
|
482
|
+
type: 'session.status',
|
|
483
|
+
properties: { sessionID: 'ses-new', directory: '/repo', status: { type: 'idle' } },
|
|
484
|
+
});
|
|
485
|
+
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
486
|
+
expect(generationCalls).toBe(1);
|
|
487
|
+
|
|
488
|
+
runtime.stop();
|
|
489
|
+
} finally {
|
|
490
|
+
globalThis.fetch = originalFetch;
|
|
491
|
+
}
|
|
492
|
+
});
|
|
493
|
+
|
|
494
|
+
it('still runs an explicit smart-title forced refresh after initial title', async () => {
|
|
495
|
+
const originalFetch = globalThis.fetch;
|
|
496
|
+
const session = {
|
|
497
|
+
id: 'ses-forced',
|
|
498
|
+
title: 'Rate limiting',
|
|
499
|
+
metadata: {
|
|
500
|
+
openchamber: {
|
|
501
|
+
titleRefresh: {
|
|
502
|
+
lastAutoTitle: 'Rate limiting',
|
|
503
|
+
generatedAt: 500,
|
|
504
|
+
forMessageID: 'a1',
|
|
505
|
+
},
|
|
506
|
+
},
|
|
507
|
+
},
|
|
508
|
+
};
|
|
509
|
+
const messages = [
|
|
510
|
+
{ info: { id: 'u1', role: 'user' }, parts: [{ type: 'text', text: 'Add rate limiting' }] },
|
|
511
|
+
{ info: { id: 'a1', role: 'assistant' }, parts: [{ type: 'text', text: 'Implemented rate limit' }] },
|
|
512
|
+
{ info: { id: 'u2', role: 'user' }, parts: [{ type: 'text', text: 'Switch to token bucket' }] },
|
|
513
|
+
{ info: { id: 'a2', role: 'assistant' }, parts: [{ type: 'text', text: 'Token bucket ready' }] },
|
|
514
|
+
];
|
|
515
|
+
let generationCalls = 0;
|
|
516
|
+
|
|
517
|
+
globalThis.fetch = async (input, init = {}) => {
|
|
518
|
+
const url = String(input);
|
|
519
|
+
if (url.includes('/message')) {
|
|
520
|
+
return new Response(JSON.stringify(messages), { status: 200 });
|
|
521
|
+
}
|
|
522
|
+
if (init.method === 'PATCH') {
|
|
523
|
+
return new Response(JSON.stringify(session), { status: 200 });
|
|
524
|
+
}
|
|
525
|
+
return new Response(JSON.stringify(session), { status: 200 });
|
|
526
|
+
};
|
|
527
|
+
|
|
528
|
+
try {
|
|
529
|
+
const runtime = createSessionTitleRuntime({
|
|
530
|
+
buildOpenCodeUrl: (pathname) => `http://opencode${pathname}`,
|
|
531
|
+
getOpenCodeAuthHeaders: () => ({}),
|
|
532
|
+
getSmallModelService: async () => ({
|
|
533
|
+
generateSmallModelText: async () => {
|
|
534
|
+
generationCalls += 1;
|
|
535
|
+
return { text: 'Token bucket rate limiting', providerID: 'test', modelID: 'test' };
|
|
536
|
+
},
|
|
537
|
+
}),
|
|
538
|
+
now: () => 1_000,
|
|
539
|
+
isTitleRefreshEnabled: () => true,
|
|
540
|
+
});
|
|
541
|
+
|
|
542
|
+
runtime.processPayload({
|
|
543
|
+
type: 'session.status',
|
|
544
|
+
properties: { sessionID: 'ses-forced', directory: '/repo', status: { type: 'idle' } },
|
|
545
|
+
});
|
|
546
|
+
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
547
|
+
expect(generationCalls).toBe(0);
|
|
548
|
+
|
|
549
|
+
runtime.processPayload({
|
|
550
|
+
type: 'session.updated',
|
|
551
|
+
properties: {
|
|
552
|
+
directory: '/repo',
|
|
553
|
+
info: {
|
|
554
|
+
id: 'ses-forced',
|
|
555
|
+
title: session.title,
|
|
556
|
+
metadata: {
|
|
557
|
+
openchamber: {
|
|
558
|
+
titleRefresh: {
|
|
559
|
+
...session.metadata.openchamber.titleRefresh,
|
|
560
|
+
requestedAt: 1_000,
|
|
561
|
+
},
|
|
562
|
+
},
|
|
563
|
+
},
|
|
564
|
+
},
|
|
565
|
+
},
|
|
566
|
+
});
|
|
567
|
+
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
568
|
+
expect(generationCalls).toBe(1);
|
|
569
|
+
|
|
570
|
+
runtime.stop();
|
|
571
|
+
} finally {
|
|
572
|
+
globalThis.fetch = originalFetch;
|
|
573
|
+
}
|
|
574
|
+
});
|
|
575
|
+
|
|
576
|
+
it('skips automatic initial refresh when session title refresh is disabled', async () => {
|
|
577
|
+
const originalFetch = globalThis.fetch;
|
|
578
|
+
const session = {
|
|
579
|
+
id: 'ses-disabled',
|
|
580
|
+
title: 'New session - 2026-07-10T12:00:00.000Z',
|
|
581
|
+
metadata: {},
|
|
582
|
+
};
|
|
583
|
+
let generationCalls = 0;
|
|
584
|
+
|
|
585
|
+
globalThis.fetch = async (input, init = {}) => {
|
|
586
|
+
if (String(input).includes('/message')) {
|
|
587
|
+
return new Response(JSON.stringify([
|
|
588
|
+
{ info: { id: 'u1', role: 'user' }, parts: [{ type: 'text', text: 'hello' }] },
|
|
589
|
+
{ info: { id: 'a1', role: 'assistant' }, parts: [{ type: 'text', text: 'world' }] },
|
|
590
|
+
]), { status: 200 });
|
|
591
|
+
}
|
|
592
|
+
if (init.method === 'PATCH') {
|
|
593
|
+
return new Response(JSON.stringify(session), { status: 200 });
|
|
594
|
+
}
|
|
595
|
+
return new Response(JSON.stringify(session), { status: 200 });
|
|
596
|
+
};
|
|
597
|
+
|
|
598
|
+
try {
|
|
599
|
+
const runtime = createSessionTitleRuntime({
|
|
600
|
+
buildOpenCodeUrl: (pathname) => `http://opencode${pathname}`,
|
|
601
|
+
getOpenCodeAuthHeaders: () => ({}),
|
|
602
|
+
getSmallModelService: async () => ({
|
|
603
|
+
generateSmallModelText: async () => {
|
|
604
|
+
generationCalls += 1;
|
|
605
|
+
return { text: 'Should not run', providerID: 'test', modelID: 'test' };
|
|
606
|
+
},
|
|
607
|
+
}),
|
|
608
|
+
now: () => 1_000,
|
|
609
|
+
isTitleRefreshEnabled: () => false,
|
|
610
|
+
});
|
|
611
|
+
|
|
612
|
+
runtime.processPayload({
|
|
613
|
+
type: 'session.created',
|
|
614
|
+
properties: {
|
|
615
|
+
directory: '/repo',
|
|
616
|
+
info: { id: 'ses-disabled', title: session.title, time: { created: 100 } },
|
|
617
|
+
},
|
|
618
|
+
});
|
|
619
|
+
runtime.processPayload({
|
|
620
|
+
type: 'session.status',
|
|
621
|
+
properties: { sessionID: 'ses-disabled', directory: '/repo', status: { type: 'idle' } },
|
|
622
|
+
});
|
|
623
|
+
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
624
|
+
expect(generationCalls).toBe(0);
|
|
625
|
+
|
|
626
|
+
runtime.stop();
|
|
627
|
+
} finally {
|
|
628
|
+
globalThis.fetch = originalFetch;
|
|
629
|
+
}
|
|
630
|
+
});
|
|
406
631
|
});
|