@openchambery/web 2.1.9-beta.5 → 2.1.9-beta.7
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-BMvmzMFI.js → AssistantView-CuT6bDtM.js} +3 -3
- package/dist/assets/{DiagramView-BO6rDoTN.js → DiagramView-D8kZ5sgP.js} +1 -1
- package/dist/assets/{MarkdownRenderer-D_ZS58Ur.js → MarkdownRenderer-3Cm5B3ni.js} +2 -2
- package/dist/assets/{MarkdownRendererImpl-gBtduBHO.js → MarkdownRendererImpl-BSYMaqFX.js} +1 -1
- package/dist/assets/{MarkstreamRendererImpl-CKilBBOc.js → MarkstreamRendererImpl-B9VRdRju.js} +1 -1
- package/dist/assets/{MobileDetailNavigation-Dgqdqrmy.js → MobileDetailNavigation-ZFfIEYTU.js} +1 -1
- package/dist/assets/{MobileShareBridge-G2wjyn4g.js → MobileShareBridge-DpuXJdlq.js} +1 -1
- package/dist/assets/{MobileTabPageHeader-Cipo7N8C.js → MobileTabPageHeader-DYRGHein.js} +1 -1
- package/dist/assets/{MultiRunWindow-C_ow_lbm.js → MultiRunWindow-BaJgbZP9.js} +1 -1
- package/dist/assets/{SettingsView-BaLgORC_.js → SettingsView-C18d1an5.js} +1 -1
- package/dist/assets/{SettingsWindow-DfSPTDHb.js → SettingsWindow-B07X5mru.js} +1 -1
- package/dist/assets/{TerminalView-DhUynfus.js → TerminalView-BbPFQ22Z.js} +1 -1
- package/dist/assets/{ToolOutputDialog-DaKPwmer.js → ToolOutputDialog-Bt3ou350.js} +1 -1
- package/dist/assets/{appThemeRegistry-zPqSQBFx.js → appThemeRegistry-BNncg3Lg.js} +1 -1
- package/dist/assets/{fileMentionTouchSelection-C8mKDaBe.js → fileMentionTouchSelection-BF0RYuUw.js} +1 -1
- package/dist/assets/{gitApi-BCPykg93.js → gitApi-KzJZseRG.js} +1 -1
- package/dist/assets/{insertionBoundaries-h0pySzQJ.js → insertionBoundaries-BoN2D2CB.js} +1 -1
- package/dist/assets/{main-C8RDC4kz.js → main-C2COAbeT.js} +4 -4
- package/dist/assets/{main-4JVZhTAZ.js → main-DBLCcpJe.js} +2 -2
- package/dist/assets/{mcpQueries-Cm1VNAmy.js → mcpQueries-BF9h43FH.js} +1 -1
- package/dist/assets/{miniChat-_SvWYZuQ.js → miniChat-Bv5ORJmD.js} +2 -2
- package/dist/assets/{mobile-Cil7_kYn.js → mobile-BzKeTq52.js} +2 -2
- package/dist/assets/{multirun-BzeInZt6.js → multirun-BHANiphy.js} +1 -1
- package/dist/assets/{projectMeta-CUAgRGwW.js → projectMeta-CuIJ2wzX.js} +1 -1
- package/dist/assets/{renderElectronMiniChatApp-BrAIRgb-.js → renderElectronMiniChatApp-B4NSy0ql.js} +2 -2
- package/dist/assets/{renderMobileApp-DxTazPkB.js → renderMobileApp-BnIawAK6.js} +4 -4
- package/dist/assets/{runtimeConfig-Bljmgd6r.js → runtimeConfig-BR335S7R.js} +1 -1
- package/dist/assets/{runtimeEndpointReset-BYsDk8ux.js → runtimeEndpointReset-grACS-Iv.js} +1 -1
- package/dist/assets/{sessionChangeSummary-BslBzYAm.js → sessionChangeSummary-Y6g4Hmn3.js} +1 -1
- package/dist/assets/{sessionLookup-5MrKwBXP.js → sessionLookup-Ntvtx26F.js} +1 -1
- package/dist/assets/{speechSettingsQueries-otzPynPJ.js → speechSettingsQueries-BS7COcv4.js} +1 -1
- package/dist/assets/{useAppFontEffects-DHJ_Qc3g.js → useAppFontEffects-DsaIbgdU.js} +5 -5
- package/dist/assets/{useDesktopWindowControlsLayout-BnMDHglb.js → useDesktopWindowControlsLayout-DFuraQa1.js} +1 -1
- package/dist/assets/{useEffectiveDirectory-D0qR3aRK.js → useEffectiveDirectory-Bw6EMInE.js} +1 -1
- package/dist/assets/{useMobileNavigationStore-BZ7oo4Ir.js → useMobileNavigationStore-BN866WTh.js} +3 -3
- package/dist/assets/{useWorkerHighlightedLines-B86utfqe.js → useWorkerHighlightedLines-DsyP6A_g.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/index.js +1 -0
- package/server/lib/opencode/session-directory-runtime.js +14 -1
- package/server/lib/opencode/session-directory-runtime.test.js +62 -0
- package/server/lib/relay/DOCUMENTATION.md +4 -0
- package/server/lib/relay/tunnel-host.js +3 -0
- package/server/lib/relay/tunnel-host.test.js +27 -0
- package/server/lib/session-index/DOCUMENTATION.md +34 -0
- package/server/lib/session-index/host-authority-snapshot.js +31 -0
- package/server/lib/session-index/routes.host-archive.test.js +118 -0
- package/server/lib/session-index/routes.js +7 -2
- package/server/lib/session-index/service.js +9 -0
- package/server/lib/session-index/sync-runtime.js +44 -0
- package/server/lib/session-index/sync-runtime.test.js +33 -0
|
@@ -7,6 +7,13 @@ const FULL_RECONCILE_INTERVAL_MS = 24 * 60 * 60 * 1000;
|
|
|
7
7
|
const FETCH_TIMEOUT_MS = 6_000;
|
|
8
8
|
const INTERACTIVE_YIELD_MS = 1_000;
|
|
9
9
|
const LONG_POLL_MAX_MS = 25_000;
|
|
10
|
+
/**
|
|
11
|
+
* Fixed trailing window for background progress tips. Each SSE tip makes every
|
|
12
|
+
* connected client GET the full snapshot, and one sync batch bumps the revision
|
|
13
|
+
* once per directory and once per child-flag check (~300+ per cold start). Keep
|
|
14
|
+
* progress `notify()` calls on the throttled path; only new batch, idle, stop and
|
|
15
|
+
* `publishChange()` may broadcast immediately. sync-runtime.test.js bounds this.
|
|
16
|
+
*/
|
|
10
17
|
const REVISION_TIP_THROTTLE_MS = 500;
|
|
11
18
|
|
|
12
19
|
const normalizeDirectory = (value) => {
|
|
@@ -75,6 +82,7 @@ export const createSessionIndexSyncRuntime = ({
|
|
|
75
82
|
projectSessions = null,
|
|
76
83
|
fetchFn = globalThis.fetch,
|
|
77
84
|
resolveDirectory = (value) => value,
|
|
85
|
+
isDirectoryMissing = null,
|
|
78
86
|
now = Date.now,
|
|
79
87
|
setTimer = setTimeout,
|
|
80
88
|
clearTimer = clearTimeout,
|
|
@@ -136,6 +144,9 @@ export const createSessionIndexSyncRuntime = ({
|
|
|
136
144
|
}
|
|
137
145
|
};
|
|
138
146
|
|
|
147
|
+
// `immediate` is reserved for edges a client must see without delay (new batch,
|
|
148
|
+
// idle, stop, external change). Progress callers must pass nothing: an immediate
|
|
149
|
+
// progress tip reintroduces one full client snapshot GET per directory/child.
|
|
139
150
|
const notify = (immediate = false, cause) => {
|
|
140
151
|
if (stopped && !immediate) return;
|
|
141
152
|
revision += 1;
|
|
@@ -172,6 +183,12 @@ export const createSessionIndexSyncRuntime = ({
|
|
|
172
183
|
.find((entry) => entry.directory === directory);
|
|
173
184
|
|
|
174
185
|
const fetchDirectory = async (task) => {
|
|
186
|
+
if (isDirectoryMissing && await isDirectoryMissing(task.directory)) {
|
|
187
|
+
if (stopped || task.runtimeKey !== sessionIndexService.getRuntimeKey()) return 'discarded';
|
|
188
|
+
sessionIndexService.removeEmptyDirectory(task.directory);
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
if (stopped || task.runtimeKey !== sessionIndexService.getRuntimeKey()) return 'discarded';
|
|
175
192
|
// v2 `session.list` cursor pagination. Collect candidates across pages,
|
|
176
193
|
// then re-project once before commit so a Host archive that lands mid-sync
|
|
177
194
|
// cannot resurrect a row. Root/system filters run before the 20-slot budget.
|
|
@@ -315,6 +332,8 @@ export const createSessionIndexSyncRuntime = ({
|
|
|
315
332
|
};
|
|
316
333
|
|
|
317
334
|
const fetchChildFlag = async (task) => {
|
|
335
|
+
if (isDirectoryMissing && await isDirectoryMissing(task.directory)) return 'discarded';
|
|
336
|
+
if (stopped || task.runtimeKey !== sessionIndexService.getRuntimeKey()) return 'discarded';
|
|
318
337
|
const controller = new AbortController();
|
|
319
338
|
currentController = controller;
|
|
320
339
|
currentWasPreempted = false;
|
|
@@ -503,6 +522,7 @@ export const createSessionIndexSyncRuntime = ({
|
|
|
503
522
|
const stop = () => {
|
|
504
523
|
if (stopped) return;
|
|
505
524
|
stopped = true;
|
|
525
|
+
if (cleanupTimer !== undefined) clearTimer(cleanupTimer);
|
|
506
526
|
currentController?.abort();
|
|
507
527
|
const interruptedDirectories = [...queuedDirectories];
|
|
508
528
|
queue.length = 0;
|
|
@@ -513,5 +533,29 @@ export const createSessionIndexSyncRuntime = ({
|
|
|
513
533
|
notify(true);
|
|
514
534
|
};
|
|
515
535
|
|
|
536
|
+
let cleanupTimer;
|
|
537
|
+
const cleanupMissingDirectories = async () => {
|
|
538
|
+
const key = sessionIndexService.getRuntimeKey();
|
|
539
|
+
let changed = false;
|
|
540
|
+
try {
|
|
541
|
+
for (const entry of sessionIndexService.snapshot().directories) {
|
|
542
|
+
if (stopped || key !== sessionIndexService.getRuntimeKey()) break;
|
|
543
|
+
if (entry.sessions.length || entry.hasMore) continue;
|
|
544
|
+
const missing = await isDirectoryMissing(entry.directory);
|
|
545
|
+
if (stopped || key !== sessionIndexService.getRuntimeKey()) break;
|
|
546
|
+
if (missing) changed = sessionIndexService.removeEmptyDirectory(entry.directory) || changed;
|
|
547
|
+
}
|
|
548
|
+
if (changed && !stopped && key === sessionIndexService.getRuntimeKey()) notify(true);
|
|
549
|
+
} catch (error) {
|
|
550
|
+
console.warn('[SessionIndex] Directory cleanup failed:', error);
|
|
551
|
+
} finally {
|
|
552
|
+
if (!stopped) {
|
|
553
|
+
cleanupTimer = setTimer(cleanupMissingDirectories, 60_000);
|
|
554
|
+
cleanupTimer?.unref?.();
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
};
|
|
558
|
+
if (isDirectoryMissing) void cleanupMissingDirectories();
|
|
559
|
+
|
|
516
560
|
return { enqueue, noteInteractiveRequest, publishChange, snapshot, waitForChange, stop };
|
|
517
561
|
};
|
|
@@ -41,6 +41,33 @@ const isFullyIdle = (runtime) => (
|
|
|
41
41
|
);
|
|
42
42
|
|
|
43
43
|
describe('session index background sync runtime', () => {
|
|
44
|
+
it('rechecks missing directories periodically and stops maintenance on shutdown', async () => {
|
|
45
|
+
const service = createService([{ directory: '/repo', sessions: [], hasMore: false }]);
|
|
46
|
+
service.removeEmptyDirectory = vi.fn(() => true);
|
|
47
|
+
const isDirectoryMissing = vi.fn(async () => false);
|
|
48
|
+
const timers = [];
|
|
49
|
+
const clearTimer = vi.fn();
|
|
50
|
+
const runtime = createSessionIndexSyncRuntime({
|
|
51
|
+
sessionIndexService: service,
|
|
52
|
+
isDirectoryMissing,
|
|
53
|
+
setTimer: (fn, ms) => { const timer = { fn, ms }; timers.push(timer); return timer; },
|
|
54
|
+
clearTimer,
|
|
55
|
+
});
|
|
56
|
+
await waitUntil(() => timers.length === 1);
|
|
57
|
+
expect(timers[0].ms).toBe(60_000);
|
|
58
|
+
expect(service.removeEmptyDirectory).not.toHaveBeenCalled();
|
|
59
|
+
isDirectoryMissing.mockResolvedValue(true);
|
|
60
|
+
await timers[0].fn();
|
|
61
|
+
expect(service.removeEmptyDirectory).toHaveBeenCalledWith('/repo');
|
|
62
|
+
expect(runtime.snapshot().revision).toBe(1);
|
|
63
|
+
runtime.stop();
|
|
64
|
+
expect(clearTimer).toHaveBeenCalledWith(timers[1]);
|
|
65
|
+
const calls = isDirectoryMissing.mock.calls.length;
|
|
66
|
+
await timers[1].fn();
|
|
67
|
+
expect(isDirectoryMissing).toHaveBeenCalledTimes(calls);
|
|
68
|
+
expect(timers).toHaveLength(2);
|
|
69
|
+
});
|
|
70
|
+
|
|
44
71
|
it('coalesces hundreds of directory/child checks and immediately publishes the final revision', async () => {
|
|
45
72
|
vi.useFakeTimers();
|
|
46
73
|
try {
|
|
@@ -80,6 +107,12 @@ describe('session index background sync runtime', () => {
|
|
|
80
107
|
expect(service.replaceDirectory).toHaveBeenCalledTimes(15);
|
|
81
108
|
expect(service.replaceChildSessions).toHaveBeenCalledTimes(300);
|
|
82
109
|
expect(isFullyIdle(runtime)).toBe(true);
|
|
110
|
+
// Relay cold-start budget: 315 internal revisions must reach SSE as a handful
|
|
111
|
+
// of tips (one per 500ms window + immediate edges), never one per directory or
|
|
112
|
+
// child check. Each tip costs every Relay client a full snapshot GET; the
|
|
113
|
+
// uncoalesced Host measured ~316 tips per mobile cold start.
|
|
114
|
+
expect(runtime.snapshot().revision).toBeGreaterThan(300);
|
|
115
|
+
expect(tips.mock.calls.length).toBeLessThanOrEqual(Math.ceil(2650 / 500) + 3);
|
|
83
116
|
expect(tips).toHaveBeenCalledTimes(8);
|
|
84
117
|
expect(tips.mock.calls.map(([tip]) => tip.revision)).toEqual(revisionsAtDelivery);
|
|
85
118
|
for (const [tip] of tips.mock.calls) expect(tip).not.toHaveProperty('cause');
|