tabminal 3.0.33 → 3.0.35
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/package.json +1 -1
- package/public/app.js +35 -7
- package/public/index.html +175 -0
- package/public/sw.js +54 -7
- package/src/acp-manager.mjs +20 -5
package/package.json
CHANGED
package/public/app.js
CHANGED
|
@@ -11815,16 +11815,31 @@ function buildAgentMessageAttachmentsNode(attachments) {
|
|
|
11815
11815
|
return container;
|
|
11816
11816
|
}
|
|
11817
11817
|
|
|
11818
|
+
function isLikelyReplayTextFragment(value) {
|
|
11819
|
+
const text = String(value || '');
|
|
11820
|
+
return text.length >= 3 && /[\p{L}\p{N}]/u.test(text);
|
|
11821
|
+
}
|
|
11822
|
+
|
|
11818
11823
|
function mergeAgentMessageText(previousText, chunkText) {
|
|
11819
11824
|
const previous = String(previousText || '');
|
|
11820
11825
|
const chunk = String(chunkText || '');
|
|
11821
11826
|
if (!previous) return chunk;
|
|
11822
11827
|
if (!chunk) return previous;
|
|
11823
|
-
if (previous === chunk)
|
|
11824
|
-
|
|
11828
|
+
if (previous === chunk) {
|
|
11829
|
+
return isLikelyReplayTextFragment(chunk)
|
|
11830
|
+
? previous
|
|
11831
|
+
: `${previous}${chunk}`;
|
|
11832
|
+
}
|
|
11833
|
+
if (
|
|
11834
|
+
chunk.startsWith(previous)
|
|
11835
|
+
&& isLikelyReplayTextFragment(previous)
|
|
11836
|
+
) {
|
|
11825
11837
|
return chunk;
|
|
11826
11838
|
}
|
|
11827
|
-
if (
|
|
11839
|
+
if (
|
|
11840
|
+
previous.startsWith(chunk)
|
|
11841
|
+
&& isLikelyReplayTextFragment(chunk)
|
|
11842
|
+
) {
|
|
11828
11843
|
return previous;
|
|
11829
11844
|
}
|
|
11830
11845
|
const maxOverlap = Math.min(previous.length, chunk.length, 2048);
|
|
@@ -11839,8 +11854,8 @@ function mergeAgentMessageText(previousText, chunkText) {
|
|
|
11839
11854
|
const previousLast = previous.slice(-1);
|
|
11840
11855
|
const chunkFirst = chunk[0] || '';
|
|
11841
11856
|
if (
|
|
11842
|
-
/[
|
|
11843
|
-
&& /[A-Z
|
|
11857
|
+
/[.!?'")\]]/.test(previousLast)
|
|
11858
|
+
&& /[A-Z"'[(]/.test(chunkFirst)
|
|
11844
11859
|
) {
|
|
11845
11860
|
return `${previous}\n\n${chunk}`;
|
|
11846
11861
|
}
|
|
@@ -17952,7 +17967,20 @@ document.addEventListener('keydown', (e) => {
|
|
|
17952
17967
|
}, true); // Use capture phase to override editor/terminal
|
|
17953
17968
|
|
|
17954
17969
|
|
|
17970
|
+
async function bootApp() {
|
|
17971
|
+
try {
|
|
17972
|
+
bootstrapServers();
|
|
17973
|
+
await initApp();
|
|
17974
|
+
window.__tabminalMarkBootSuccess?.();
|
|
17975
|
+
} catch (error) {
|
|
17976
|
+
console.error('[Boot] Failed to start Tabminal:', error);
|
|
17977
|
+
window.__tabminalMarkBootFailure?.(
|
|
17978
|
+
error?.message || 'app initialization failed'
|
|
17979
|
+
);
|
|
17980
|
+
throw error;
|
|
17981
|
+
}
|
|
17982
|
+
}
|
|
17983
|
+
|
|
17955
17984
|
// Start the app
|
|
17956
|
-
|
|
17957
|
-
initApp();
|
|
17985
|
+
void bootApp();
|
|
17958
17986
|
// #endregion
|
package/public/index.html
CHANGED
|
@@ -277,8 +277,20 @@
|
|
|
277
277
|
}
|
|
278
278
|
|
|
279
279
|
const runtimeStorageKey = 'tabminal_runtime_boot_id';
|
|
280
|
+
const bootRetryStorageKey = 'tabminal_boot_retry_state';
|
|
280
281
|
const versionApiUrl = './api/version';
|
|
281
282
|
const runtimeVersionTimeoutMs = 3000;
|
|
283
|
+
const bootWatchdogTimeoutMs = 25000;
|
|
284
|
+
const bootRecoveryProbeIntervalMs = 15000;
|
|
285
|
+
const bootRecoveryReloadDelayMs = 1200;
|
|
286
|
+
const bootRetryWindowMs = 90000;
|
|
287
|
+
const bootMaxReloadsPerWindow = 3;
|
|
288
|
+
window.__tabminalBootState = {
|
|
289
|
+
status: 'pending',
|
|
290
|
+
startedAt: Date.now(),
|
|
291
|
+
assetKey: '',
|
|
292
|
+
error: ''
|
|
293
|
+
};
|
|
282
294
|
const readStoredRuntimeBootId = () => {
|
|
283
295
|
try {
|
|
284
296
|
return localStorage.getItem(runtimeStorageKey) || '';
|
|
@@ -297,6 +309,159 @@
|
|
|
297
309
|
return readStoredRuntimeBootId() || `cold-${Date.now()}`;
|
|
298
310
|
};
|
|
299
311
|
window.__tabminalRuntimeAssetKey = getStartupFallbackAssetKey();
|
|
312
|
+
window.__tabminalBootState.assetKey =
|
|
313
|
+
window.__tabminalRuntimeAssetKey;
|
|
314
|
+
|
|
315
|
+
const readBootRetryState = () => {
|
|
316
|
+
try {
|
|
317
|
+
const parsed = JSON.parse(
|
|
318
|
+
sessionStorage.getItem(bootRetryStorageKey) || '{}'
|
|
319
|
+
);
|
|
320
|
+
if (!parsed || typeof parsed !== 'object') {
|
|
321
|
+
return { firstAt: 0, count: 0 };
|
|
322
|
+
}
|
|
323
|
+
return {
|
|
324
|
+
firstAt: Number.isFinite(parsed.firstAt)
|
|
325
|
+
? parsed.firstAt
|
|
326
|
+
: 0,
|
|
327
|
+
count: Number.isFinite(parsed.count)
|
|
328
|
+
? parsed.count
|
|
329
|
+
: 0
|
|
330
|
+
};
|
|
331
|
+
} catch {
|
|
332
|
+
return { firstAt: 0, count: 0 };
|
|
333
|
+
}
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
const writeBootRetryState = (state) => {
|
|
337
|
+
try {
|
|
338
|
+
sessionStorage.setItem(
|
|
339
|
+
bootRetryStorageKey,
|
|
340
|
+
JSON.stringify(state)
|
|
341
|
+
);
|
|
342
|
+
} catch {
|
|
343
|
+
// Ignore storage failures; the watchdog can still probe.
|
|
344
|
+
}
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
const clearBootRetryState = () => {
|
|
348
|
+
try {
|
|
349
|
+
sessionStorage.removeItem(bootRetryStorageKey);
|
|
350
|
+
} catch {
|
|
351
|
+
// Ignore storage failures.
|
|
352
|
+
}
|
|
353
|
+
};
|
|
354
|
+
|
|
355
|
+
const canReloadForBootFailure = () => {
|
|
356
|
+
const now = Date.now();
|
|
357
|
+
const retryState = readBootRetryState();
|
|
358
|
+
const firstAt = (
|
|
359
|
+
retryState.firstAt
|
|
360
|
+
&& now - retryState.firstAt <= bootRetryWindowMs
|
|
361
|
+
)
|
|
362
|
+
? retryState.firstAt
|
|
363
|
+
: now;
|
|
364
|
+
const count = firstAt === retryState.firstAt
|
|
365
|
+
? retryState.count
|
|
366
|
+
: 0;
|
|
367
|
+
if (count >= bootMaxReloadsPerWindow) {
|
|
368
|
+
return false;
|
|
369
|
+
}
|
|
370
|
+
writeBootRetryState({
|
|
371
|
+
firstAt,
|
|
372
|
+
count: count + 1
|
|
373
|
+
});
|
|
374
|
+
return true;
|
|
375
|
+
};
|
|
376
|
+
|
|
377
|
+
const probeRuntimeReachable = async () => {
|
|
378
|
+
const controller = (
|
|
379
|
+
typeof AbortController === 'function'
|
|
380
|
+
? new AbortController()
|
|
381
|
+
: null
|
|
382
|
+
);
|
|
383
|
+
const timeoutId = window.setTimeout(() => {
|
|
384
|
+
if (controller) {
|
|
385
|
+
controller.abort();
|
|
386
|
+
}
|
|
387
|
+
}, runtimeVersionTimeoutMs);
|
|
388
|
+
try {
|
|
389
|
+
const response = await fetch(versionApiUrl, {
|
|
390
|
+
method: 'GET',
|
|
391
|
+
cache: 'no-store',
|
|
392
|
+
credentials: 'same-origin',
|
|
393
|
+
headers: {
|
|
394
|
+
'accept': 'application/json'
|
|
395
|
+
},
|
|
396
|
+
signal: controller?.signal
|
|
397
|
+
});
|
|
398
|
+
return response.ok;
|
|
399
|
+
} catch {
|
|
400
|
+
return false;
|
|
401
|
+
} finally {
|
|
402
|
+
window.clearTimeout(timeoutId);
|
|
403
|
+
}
|
|
404
|
+
};
|
|
405
|
+
|
|
406
|
+
const scheduleBootRecovery = (() => {
|
|
407
|
+
let timer = 0;
|
|
408
|
+
return (reason, delay = bootRecoveryReloadDelayMs) => {
|
|
409
|
+
const state = window.__tabminalBootState;
|
|
410
|
+
if (!state || state.status === 'success') {
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
state.status = 'recovering';
|
|
414
|
+
state.error = String(reason || 'boot timeout');
|
|
415
|
+
if (timer) {
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
timer = window.setTimeout(async () => {
|
|
419
|
+
timer = 0;
|
|
420
|
+
if (state.status === 'success') {
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
const reachable = await probeRuntimeReachable();
|
|
424
|
+
if (!reachable) {
|
|
425
|
+
scheduleBootRecovery(
|
|
426
|
+
'runtime unavailable',
|
|
427
|
+
bootRecoveryProbeIntervalMs
|
|
428
|
+
);
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
if (!canReloadForBootFailure()) {
|
|
432
|
+
state.status = 'failed';
|
|
433
|
+
console.warn(
|
|
434
|
+
'[Boot] App shell failed after repeated reloads.',
|
|
435
|
+
state.error
|
|
436
|
+
);
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
window.location.reload();
|
|
440
|
+
}, delay);
|
|
441
|
+
};
|
|
442
|
+
})();
|
|
443
|
+
|
|
444
|
+
window.__tabminalMarkBootSuccess = () => {
|
|
445
|
+
const state = window.__tabminalBootState;
|
|
446
|
+
if (state) {
|
|
447
|
+
state.status = 'success';
|
|
448
|
+
state.completedAt = Date.now();
|
|
449
|
+
state.error = '';
|
|
450
|
+
}
|
|
451
|
+
clearBootRetryState();
|
|
452
|
+
};
|
|
453
|
+
|
|
454
|
+
window.__tabminalMarkBootFailure = (reason) => {
|
|
455
|
+
scheduleBootRecovery(reason || 'boot failure');
|
|
456
|
+
};
|
|
457
|
+
|
|
458
|
+
window.setTimeout(() => {
|
|
459
|
+
const state = window.__tabminalBootState;
|
|
460
|
+
if (!state || state.status === 'success') {
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
scheduleBootRecovery('boot watchdog timeout');
|
|
464
|
+
}, bootWatchdogTimeoutMs);
|
|
300
465
|
|
|
301
466
|
window.__tabminalResolveRuntimeVersion = (() => {
|
|
302
467
|
let promise = null;
|
|
@@ -704,10 +869,20 @@
|
|
|
704
869
|
const link = document.createElement('link');
|
|
705
870
|
link.rel = 'stylesheet';
|
|
706
871
|
link.href = `./styles.css?v=${encodeURIComponent(runtimeBootId)}`;
|
|
872
|
+
link.onerror = () => {
|
|
873
|
+
if (window.__tabminalMarkBootFailure) {
|
|
874
|
+
window.__tabminalMarkBootFailure('stylesheet load failed');
|
|
875
|
+
}
|
|
876
|
+
};
|
|
707
877
|
document.head.appendChild(link);
|
|
708
878
|
const script = document.createElement('script');
|
|
709
879
|
script.type = 'module';
|
|
710
880
|
script.src = `./app.js?v=${encodeURIComponent(runtimeBootId)}`;
|
|
881
|
+
script.onerror = () => {
|
|
882
|
+
if (window.__tabminalMarkBootFailure) {
|
|
883
|
+
window.__tabminalMarkBootFailure('app module load failed');
|
|
884
|
+
}
|
|
885
|
+
};
|
|
711
886
|
document.body.appendChild(script);
|
|
712
887
|
})();
|
|
713
888
|
|
package/public/sw.js
CHANGED
|
@@ -1,16 +1,36 @@
|
|
|
1
1
|
const WORKER_VERSION = new URL(self.location.href).searchParams.get('rt') || 'stable';
|
|
2
2
|
const CACHE_NAME = `tabminal-cache-${WORKER_VERSION}`;
|
|
3
|
+
const versioned = (path) => `${path}?v=${encodeURIComponent(WORKER_VERSION)}`;
|
|
3
4
|
const STATIC_ASSETS = [
|
|
5
|
+
'/',
|
|
6
|
+
'/index.html',
|
|
4
7
|
'/favicon.svg',
|
|
8
|
+
'/favicon_adaptive.svg',
|
|
5
9
|
'/manifest.json',
|
|
6
|
-
'/apple-touch-icon.png'
|
|
10
|
+
'/apple-touch-icon.png',
|
|
11
|
+
'/android-chrome-192x192.png',
|
|
12
|
+
'/android-chrome-192x192-any.png',
|
|
13
|
+
'/android-chrome-512x512.png',
|
|
14
|
+
'/android-chrome-512x512-any.png',
|
|
15
|
+
'/fonts/MonaspaceNeon-Regular.woff2',
|
|
16
|
+
'/fonts/MonaspaceNeon-Bold.woff2',
|
|
17
|
+
'/icons/map.json'
|
|
18
|
+
];
|
|
19
|
+
const VERSIONED_APP_ASSETS = [
|
|
20
|
+
versioned('/styles.css'),
|
|
21
|
+
versioned('/app.js'),
|
|
22
|
+
versioned('/modules/notifications.js'),
|
|
23
|
+
versioned('/modules/session-meta.js'),
|
|
24
|
+
versioned('/modules/url-auth.js')
|
|
7
25
|
];
|
|
8
26
|
|
|
9
27
|
async function networkFirst(request) {
|
|
10
28
|
const cache = await caches.open(CACHE_NAME);
|
|
11
29
|
try {
|
|
12
30
|
const response = await fetch(request);
|
|
13
|
-
|
|
31
|
+
if (response.ok) {
|
|
32
|
+
cache.put(request, response.clone());
|
|
33
|
+
}
|
|
14
34
|
return response;
|
|
15
35
|
} catch (_err) {
|
|
16
36
|
const cached = await cache.match(request);
|
|
@@ -24,15 +44,27 @@ async function cacheFirst(request) {
|
|
|
24
44
|
const cached = await cache.match(request);
|
|
25
45
|
if (cached) return cached;
|
|
26
46
|
const response = await fetch(request);
|
|
27
|
-
|
|
47
|
+
if (response.ok) {
|
|
48
|
+
cache.put(request, response.clone());
|
|
49
|
+
}
|
|
28
50
|
return response;
|
|
29
51
|
}
|
|
30
52
|
|
|
53
|
+
async function addAllSettled(cache, assets) {
|
|
54
|
+
await Promise.allSettled(
|
|
55
|
+
assets.map((asset) => cache.add(asset))
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
31
59
|
self.addEventListener('install', event => {
|
|
32
60
|
self.skipWaiting();
|
|
33
|
-
event.waitUntil(
|
|
34
|
-
caches.open(CACHE_NAME)
|
|
35
|
-
|
|
61
|
+
event.waitUntil((async () => {
|
|
62
|
+
const cache = await caches.open(CACHE_NAME);
|
|
63
|
+
await addAllSettled(cache, [
|
|
64
|
+
...STATIC_ASSETS,
|
|
65
|
+
...VERSIONED_APP_ASSETS
|
|
66
|
+
]);
|
|
67
|
+
})());
|
|
36
68
|
});
|
|
37
69
|
|
|
38
70
|
self.addEventListener('activate', event => {
|
|
@@ -81,8 +113,23 @@ self.addEventListener('fetch', event => {
|
|
|
81
113
|
|| url.pathname === '/sw.js'
|
|
82
114
|
|| url.pathname.startsWith('/modules/')
|
|
83
115
|
);
|
|
116
|
+
const isVersionedAppShell = (
|
|
117
|
+
isAppShell
|
|
118
|
+
&& (
|
|
119
|
+
url.searchParams.get('v') === WORKER_VERSION
|
|
120
|
+
|| url.searchParams.get('rt') === WORKER_VERSION
|
|
121
|
+
)
|
|
122
|
+
);
|
|
84
123
|
|
|
85
|
-
if (isDocument
|
|
124
|
+
if (isDocument) {
|
|
125
|
+
event.respondWith(networkFirst(request));
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
if (isVersionedAppShell) {
|
|
129
|
+
event.respondWith(cacheFirst(request));
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
if (isAppShell) {
|
|
86
133
|
event.respondWith(networkFirst(request));
|
|
87
134
|
return;
|
|
88
135
|
}
|
package/src/acp-manager.mjs
CHANGED
|
@@ -776,16 +776,31 @@ export function buildTerminalSpawnRequest(request = {}) {
|
|
|
776
776
|
};
|
|
777
777
|
}
|
|
778
778
|
|
|
779
|
+
function isLikelyReplayTextFragment(value) {
|
|
780
|
+
const text = String(value || '');
|
|
781
|
+
return text.length >= 3 && /[\p{L}\p{N}]/u.test(text);
|
|
782
|
+
}
|
|
783
|
+
|
|
779
784
|
export function mergeAgentMessageText(previousText, chunkText) {
|
|
780
785
|
const previous = String(previousText || '');
|
|
781
786
|
const chunk = String(chunkText || '');
|
|
782
787
|
if (!previous) return chunk;
|
|
783
788
|
if (!chunk) return previous;
|
|
784
|
-
if (previous === chunk)
|
|
785
|
-
|
|
789
|
+
if (previous === chunk) {
|
|
790
|
+
return isLikelyReplayTextFragment(chunk)
|
|
791
|
+
? previous
|
|
792
|
+
: `${previous}${chunk}`;
|
|
793
|
+
}
|
|
794
|
+
if (
|
|
795
|
+
chunk.startsWith(previous)
|
|
796
|
+
&& isLikelyReplayTextFragment(previous)
|
|
797
|
+
) {
|
|
786
798
|
return chunk;
|
|
787
799
|
}
|
|
788
|
-
if (
|
|
800
|
+
if (
|
|
801
|
+
previous.startsWith(chunk)
|
|
802
|
+
&& isLikelyReplayTextFragment(chunk)
|
|
803
|
+
) {
|
|
789
804
|
return previous;
|
|
790
805
|
}
|
|
791
806
|
|
|
@@ -803,8 +818,8 @@ export function mergeAgentMessageText(previousText, chunkText) {
|
|
|
803
818
|
const previousLast = previous.slice(-1);
|
|
804
819
|
const chunkFirst = chunk[0] || '';
|
|
805
820
|
if (
|
|
806
|
-
/[
|
|
807
|
-
&& /[A-Z
|
|
821
|
+
/[.!?'")\]]/.test(previousLast)
|
|
822
|
+
&& /[A-Z"'[(]/.test(chunkFirst)
|
|
808
823
|
) {
|
|
809
824
|
return `${previous}\n\n${chunk}`;
|
|
810
825
|
}
|