create-walle 0.9.21 → 0.9.22
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/README.md +5 -5
- package/package.json +2 -2
- package/template/claude-task-manager/api-prompts.js +13 -0
- package/template/claude-task-manager/api-reviews.js +5 -2
- package/template/claude-task-manager/db.js +348 -15
- package/template/claude-task-manager/docs/app-update-refresh-protocol.md +69 -0
- package/template/claude-task-manager/docs/image-paste-ux.md +3 -0
- package/template/claude-task-manager/docs/ipad-web-preview.md +88 -0
- package/template/claude-task-manager/git-utils.js +146 -17
- package/template/claude-task-manager/lib/auth-rate-limit.js +23 -3
- package/template/claude-task-manager/lib/auth-rules.js +3 -0
- package/template/claude-task-manager/lib/document-review.js +33 -2
- package/template/claude-task-manager/lib/microsoft-dev-tunnel-setup.js +83 -0
- package/template/claude-task-manager/lib/mobile-auth-api.js +14 -0
- package/template/claude-task-manager/lib/restart-guard.js +68 -0
- package/template/claude-task-manager/lib/session-standup.js +36 -13
- package/template/claude-task-manager/lib/session-stream.js +11 -4
- package/template/claude-task-manager/lib/transport-security.js +50 -0
- package/template/claude-task-manager/lib/walle-transcript.js +16 -0
- package/template/claude-task-manager/lib/worktree-active-sync.js +6 -3
- package/template/claude-task-manager/public/css/reviews.css +10 -0
- package/template/claude-task-manager/public/css/setup.css +13 -0
- package/template/claude-task-manager/public/css/walle.css +145 -0
- package/template/claude-task-manager/public/index.html +539 -44
- package/template/claude-task-manager/public/ipad.html +363 -0
- package/template/claude-task-manager/public/js/document-review-links.js +196 -0
- package/template/claude-task-manager/public/js/message-renderer.js +14 -3
- package/template/claude-task-manager/public/js/reviews.js +30 -6
- package/template/claude-task-manager/public/js/setup.js +42 -2
- package/template/claude-task-manager/public/js/stream-view.js +20 -1
- package/template/claude-task-manager/public/js/walle.js +314 -18
- package/template/claude-task-manager/public/m/app.css +789 -11
- package/template/claude-task-manager/public/m/app.js +1070 -67
- package/template/claude-task-manager/public/m/claim.html +9 -2
- package/template/claude-task-manager/public/m/index.html +17 -10
- package/template/claude-task-manager/public/m/sw.js +1 -1
- package/template/claude-task-manager/server.js +365 -95
- package/template/claude-task-manager/session-integrity.js +4 -0
- package/template/docs/designs/2026-05-17-portkey-gateway-provider-ux.md +86 -35
- package/template/package.json +1 -1
- package/template/wall-e/api-walle.js +19 -1
- package/template/wall-e/brain.js +152 -6
- package/template/wall-e/chat.js +85 -0
- package/template/wall-e/coding-orchestrator.js +106 -12
- package/template/wall-e/http/model-admin.js +131 -0
- package/template/wall-e/lib/service-health.js +194 -0
- package/template/wall-e/llm/anthropic.js +7 -0
- package/template/wall-e/llm/client.js +46 -12
- package/template/wall-e/llm/openai.js +17 -2
- package/template/wall-e/llm/portkey-sync.js +201 -0
- package/template/wall-e/server.js +13 -0
- package/template/website/index.html +10 -10
|
@@ -4310,6 +4310,38 @@
|
|
|
4310
4310
|
.update-wizard-actions { flex-wrap: wrap; }
|
|
4311
4311
|
.update-wizard-actions .btn { flex: 1 1 auto; }
|
|
4312
4312
|
}
|
|
4313
|
+
.app-reload-banner {
|
|
4314
|
+
display: none;
|
|
4315
|
+
align-items: center;
|
|
4316
|
+
gap: 10px;
|
|
4317
|
+
padding: 8px 16px;
|
|
4318
|
+
border-bottom: 1px solid var(--border);
|
|
4319
|
+
background: color-mix(in srgb, var(--warning, #e5b567) 12%, var(--bg-elevated, #1f2335));
|
|
4320
|
+
color: var(--fg);
|
|
4321
|
+
font-size: 12px;
|
|
4322
|
+
line-height: 1.4;
|
|
4323
|
+
}
|
|
4324
|
+
.app-reload-banner.active { display: flex; }
|
|
4325
|
+
.app-reload-banner strong {
|
|
4326
|
+
color: var(--fg);
|
|
4327
|
+
font-weight: 700;
|
|
4328
|
+
}
|
|
4329
|
+
.app-reload-banner span {
|
|
4330
|
+
color: var(--fg-dim);
|
|
4331
|
+
}
|
|
4332
|
+
.app-reload-banner .btn {
|
|
4333
|
+
min-height: 28px;
|
|
4334
|
+
padding: 4px 10px;
|
|
4335
|
+
font-size: 12px;
|
|
4336
|
+
}
|
|
4337
|
+
.app-reload-banner .reload-copy {
|
|
4338
|
+
display: flex;
|
|
4339
|
+
flex-wrap: wrap;
|
|
4340
|
+
gap: 4px 8px;
|
|
4341
|
+
align-items: baseline;
|
|
4342
|
+
min-width: 0;
|
|
4343
|
+
flex: 1 1 auto;
|
|
4344
|
+
}
|
|
4313
4345
|
|
|
4314
4346
|
/* Agent Picker Grid */
|
|
4315
4347
|
.ns-agent-grid {
|
|
@@ -5072,6 +5104,13 @@
|
|
|
5072
5104
|
<button id="update-apply-btn" onclick="applyUpdate('banner')" style="background:#7aa2f7;color:#1a1b26;border:none;padding:3px 10px;border-radius:4px;font-size:11px;cursor:pointer;font-weight:600;">Update Now</button>
|
|
5073
5105
|
<button onclick="dismissUpdate('banner')" style="background:none;border:none;color:var(--fg-dim);cursor:pointer;margin-left:auto;opacity:0.6;font-size:14px;">×</button>
|
|
5074
5106
|
</div>
|
|
5107
|
+
<div id="app-reload-banner" class="app-reload-banner" role="status" aria-live="polite" aria-atomic="true">
|
|
5108
|
+
<div class="reload-copy">
|
|
5109
|
+
<strong>Update installed.</strong>
|
|
5110
|
+
<span id="app-reload-msg">Reload this page to use the latest CTM / Wall-E UI.</span>
|
|
5111
|
+
</div>
|
|
5112
|
+
<button class="btn primary" id="app-reload-now-btn" onclick="reloadForInstalledUpdate('banner')">Reload now</button>
|
|
5113
|
+
</div>
|
|
5075
5114
|
<div class="modal-overlay update-wizard-overlay hidden" id="update-wizard" role="dialog" aria-modal="true" aria-labelledby="update-wizard-heading">
|
|
5076
5115
|
<div class="modal update-wizard-modal">
|
|
5077
5116
|
<div class="update-wizard-head">
|
|
@@ -5859,7 +5898,10 @@ url = "http://localhost:<span id="setup-mcp-port-display-toml">3457</span>/mcp"<
|
|
|
5859
5898
|
<div id="setup-ms-summary">Checking Microsoft tunnel...</div>
|
|
5860
5899
|
<div id="setup-ms-account" class="setup-tunnel-account"></div>
|
|
5861
5900
|
</div>
|
|
5862
|
-
<
|
|
5901
|
+
<div class="setup-tunnel-actions">
|
|
5902
|
+
<button class="setup-btn setup-btn-primary setup-tunnel-primary" id="setup-ms-setup" onclick="SETUP.setupMicrosoftTunnel()">Set Up</button>
|
|
5903
|
+
<button class="setup-btn setup-btn-secondary setup-tunnel-secondary" id="setup-ms-switch-login" onclick="SETUP.switchMicrosoftTunnelLogin()" style="display:none;">Use Different Account</button>
|
|
5904
|
+
</div>
|
|
5863
5905
|
</div>
|
|
5864
5906
|
<ol class="setup-tunnel-steps" aria-label="Microsoft tunnel setup progress">
|
|
5865
5907
|
<li id="setup-ms-step-install"><span>1</span><strong>Install</strong></li>
|
|
@@ -6351,6 +6393,7 @@ url = "http://localhost:<span id="setup-mcp-port-display-toml">3457</span>/mcp"<
|
|
|
6351
6393
|
<div class="models-panel-scroll models-scroll">
|
|
6352
6394
|
<div class="models-panel-content models-content">
|
|
6353
6395
|
<div id="models-header"></div>
|
|
6396
|
+
<div id="models-gateways-section"></div>
|
|
6354
6397
|
<div id="models-providers-grid"></div>
|
|
6355
6398
|
<div id="models-shadow-config" style="margin-top:20px;"></div>
|
|
6356
6399
|
<div id="models-tier-matrix" style="margin-top:20px;"></div>
|
|
@@ -7083,6 +7126,7 @@ url = "http://localhost:<span id="setup-mcp-port-display-toml">3457</span>/mcp"<
|
|
|
7083
7126
|
<script src="js/terminal-restore-state.js"></script>
|
|
7084
7127
|
<script src="js/image-normalize.js"></script>
|
|
7085
7128
|
<script src="js/walle-session.js"></script>
|
|
7129
|
+
<script src="js/document-review-links.js"></script>
|
|
7086
7130
|
<script src="js/message-renderer.js"></script>
|
|
7087
7131
|
<script src="js/stream-view.js"></script>
|
|
7088
7132
|
<script>
|
|
@@ -7560,6 +7604,12 @@ const state = window._ctmState = {
|
|
|
7560
7604
|
rulesFiles: [],
|
|
7561
7605
|
currentRule: null,
|
|
7562
7606
|
queuePanelOpen: false,
|
|
7607
|
+
appRuntime: {
|
|
7608
|
+
loaded: null,
|
|
7609
|
+
latest: null,
|
|
7610
|
+
reloadRequired: false,
|
|
7611
|
+
reloadReason: '',
|
|
7612
|
+
},
|
|
7563
7613
|
splitRoot: null, // Binary tree of panes (null = single-pane mode)
|
|
7564
7614
|
savedSplitLayout: null, // Suspended split tree (saved when switching to non-split tab)
|
|
7565
7615
|
focusedPaneId: null, // ID of focused leaf pane
|
|
@@ -7569,6 +7619,134 @@ const TERMINAL_HOT_CACHE_LIMIT = 3;
|
|
|
7569
7619
|
const DEFAULT_SNAPSHOT_SCROLLBACK_ROWS = 300;
|
|
7570
7620
|
const MAX_SNAPSHOT_SCROLLBACK_ROWS = 10000;
|
|
7571
7621
|
const SNAPSHOT_SCROLLBACK_ROW_OPTIONS = Object.freeze([0, 100, 300, 1000, 2000, 10000]);
|
|
7622
|
+
const APP_RELOAD_CHANNEL_NAME = 'ctm-app-update';
|
|
7623
|
+
let _appReloadChannel = null;
|
|
7624
|
+
|
|
7625
|
+
function normalizeAppVersionInfo(info) {
|
|
7626
|
+
if (!info || typeof info !== 'object') return null;
|
|
7627
|
+
const version = String(info.version || '').trim();
|
|
7628
|
+
const buildId = String(info.buildId || '').trim();
|
|
7629
|
+
const product = String(info.product || 'create-walle').trim();
|
|
7630
|
+
const components = info.components && typeof info.components === 'object' ? {
|
|
7631
|
+
ctm: String(info.components.ctm || '').trim(),
|
|
7632
|
+
wallE: String(info.components.wallE || '').trim(),
|
|
7633
|
+
} : {};
|
|
7634
|
+
if (!version && !buildId) return null;
|
|
7635
|
+
return { version, buildId, product, components };
|
|
7636
|
+
}
|
|
7637
|
+
|
|
7638
|
+
function appVersionIdentity(info) {
|
|
7639
|
+
const normalized = normalizeAppVersionInfo(info);
|
|
7640
|
+
if (!normalized) return '';
|
|
7641
|
+
return normalized.buildId || `${normalized.product}:${normalized.version}:${normalized.components.ctm || ''}:${normalized.components.wallE || ''}`;
|
|
7642
|
+
}
|
|
7643
|
+
|
|
7644
|
+
function sameAppVersionIdentity(a, b) {
|
|
7645
|
+
const aid = appVersionIdentity(a);
|
|
7646
|
+
const bid = appVersionIdentity(b);
|
|
7647
|
+
return !!aid && !!bid && aid === bid;
|
|
7648
|
+
}
|
|
7649
|
+
|
|
7650
|
+
function initAppReloadChannel() {
|
|
7651
|
+
if (_appReloadChannel || typeof BroadcastChannel === 'undefined') return _appReloadChannel;
|
|
7652
|
+
try {
|
|
7653
|
+
_appReloadChannel = new BroadcastChannel(APP_RELOAD_CHANNEL_NAME);
|
|
7654
|
+
_appReloadChannel.onmessage = (event) => {
|
|
7655
|
+
const data = event && event.data;
|
|
7656
|
+
if (!data || data.type !== 'reload-required') return;
|
|
7657
|
+
requestInstalledUpdateReload({
|
|
7658
|
+
server: data.server,
|
|
7659
|
+
reason: data.reason || 'peer-detected',
|
|
7660
|
+
source: 'broadcast',
|
|
7661
|
+
rebroadcast: false,
|
|
7662
|
+
});
|
|
7663
|
+
};
|
|
7664
|
+
} catch (_) {
|
|
7665
|
+
_appReloadChannel = null;
|
|
7666
|
+
}
|
|
7667
|
+
return _appReloadChannel;
|
|
7668
|
+
}
|
|
7669
|
+
|
|
7670
|
+
function broadcastInstalledUpdateReload(server, reason) {
|
|
7671
|
+
const channel = initAppReloadChannel();
|
|
7672
|
+
if (!channel) return;
|
|
7673
|
+
try {
|
|
7674
|
+
channel.postMessage({ type: 'reload-required', server, reason, at: Date.now() });
|
|
7675
|
+
} catch (_) {}
|
|
7676
|
+
}
|
|
7677
|
+
|
|
7678
|
+
function handleServerHello(msg) {
|
|
7679
|
+
if (msg && msg.clientId) state._clientId = msg.clientId;
|
|
7680
|
+
const server = normalizeAppVersionInfo(msg && msg.appVersion);
|
|
7681
|
+
if (!server) return;
|
|
7682
|
+
if (!state.appRuntime.loaded) {
|
|
7683
|
+
state.appRuntime.loaded = server;
|
|
7684
|
+
state.appRuntime.latest = server;
|
|
7685
|
+
setAppVersion(server.version, { ...server, latestVersion: server.version });
|
|
7686
|
+
return;
|
|
7687
|
+
}
|
|
7688
|
+
state.appRuntime.latest = server;
|
|
7689
|
+
if (!sameAppVersionIdentity(state.appRuntime.loaded, server)) {
|
|
7690
|
+
requestInstalledUpdateReload({ server, reason: 'server-version-changed', source: 'hello' });
|
|
7691
|
+
}
|
|
7692
|
+
}
|
|
7693
|
+
|
|
7694
|
+
function isAppReloadUnsafe() {
|
|
7695
|
+
if (shouldPreserveFocusForUpdatePrompt()) return true;
|
|
7696
|
+
if (state.queuePanelOpen) return true;
|
|
7697
|
+
const screenshotEditor = document.getElementById('screenshot-editor');
|
|
7698
|
+
if (screenshotEditor && screenshotEditor.classList.contains('active')) return true;
|
|
7699
|
+
const openModal = document.querySelector('.modal-overlay:not(.hidden)');
|
|
7700
|
+
if (openModal) return true;
|
|
7701
|
+
return false;
|
|
7702
|
+
}
|
|
7703
|
+
|
|
7704
|
+
function showInstalledUpdateReloadBanner(server) {
|
|
7705
|
+
const banner = document.getElementById('app-reload-banner');
|
|
7706
|
+
const msg = document.getElementById('app-reload-msg');
|
|
7707
|
+
if (!banner) return;
|
|
7708
|
+
const loaded = state.appRuntime.loaded || {};
|
|
7709
|
+
const latest = normalizeAppVersionInfo(server) || state.appRuntime.latest || {};
|
|
7710
|
+
if (msg) {
|
|
7711
|
+
const from = loaded.version ? `v${loaded.version}` : 'old UI';
|
|
7712
|
+
const to = latest.version ? `v${latest.version}` : 'the installed update';
|
|
7713
|
+
msg.textContent = `Reload this page to use ${to}. Current tab is still running ${from}.`;
|
|
7714
|
+
}
|
|
7715
|
+
banner.classList.add('active');
|
|
7716
|
+
}
|
|
7717
|
+
|
|
7718
|
+
function requestInstalledUpdateReload(opts = {}) {
|
|
7719
|
+
const server = normalizeAppVersionInfo(opts.server) || state.appRuntime.latest;
|
|
7720
|
+
state.appRuntime.latest = server || state.appRuntime.latest;
|
|
7721
|
+
state.appRuntime.reloadRequired = true;
|
|
7722
|
+
state.appRuntime.reloadReason = opts.reason || 'app-updated';
|
|
7723
|
+
if (opts.rebroadcast !== false) broadcastInstalledUpdateReload(server, state.appRuntime.reloadReason);
|
|
7724
|
+
if (!isAppReloadUnsafe()) {
|
|
7725
|
+
reloadForInstalledUpdate(opts.source || 'auto');
|
|
7726
|
+
return;
|
|
7727
|
+
}
|
|
7728
|
+
showInstalledUpdateReloadBanner(server);
|
|
7729
|
+
}
|
|
7730
|
+
|
|
7731
|
+
function reloadForInstalledUpdate(source = 'button') {
|
|
7732
|
+
const detail = {
|
|
7733
|
+
source,
|
|
7734
|
+
reason: state.appRuntime.reloadReason || 'app-updated',
|
|
7735
|
+
loaded: state.appRuntime.loaded,
|
|
7736
|
+
latest: state.appRuntime.latest,
|
|
7737
|
+
};
|
|
7738
|
+
try { sessionStorage.setItem('ctm_app_update_reload', JSON.stringify({ ...detail, at: Date.now() })); } catch (_) {}
|
|
7739
|
+
if (typeof window.__ctmAppReload === 'function') {
|
|
7740
|
+
window.__ctmAppReload(detail);
|
|
7741
|
+
return;
|
|
7742
|
+
}
|
|
7743
|
+
location.reload();
|
|
7744
|
+
}
|
|
7745
|
+
|
|
7746
|
+
window.__ctmHandleServerHello = handleServerHello;
|
|
7747
|
+
window.__ctmRequestInstalledUpdateReload = requestInstalledUpdateReload;
|
|
7748
|
+
window.__ctmIsAppReloadUnsafe = isAppReloadUnsafe;
|
|
7749
|
+
initAppReloadChannel();
|
|
7572
7750
|
|
|
7573
7751
|
function normalizeSnapshotScrollbackRows(value, fallback = DEFAULT_SNAPSHOT_SCROLLBACK_ROWS) {
|
|
7574
7752
|
if (value == null || value === '') return normalizeSnapshotScrollbackRows(fallback, DEFAULT_SNAPSHOT_SCROLLBACK_ROWS);
|
|
@@ -8010,7 +8188,7 @@ function connect() {
|
|
|
8010
8188
|
ws.onmessage = (e) => {
|
|
8011
8189
|
const msg = JSON.parse(e.data);
|
|
8012
8190
|
switch (msg.type) {
|
|
8013
|
-
case 'hello':
|
|
8191
|
+
case 'hello': handleServerHello(msg); break;
|
|
8014
8192
|
case 'ui-prefs-changed': onUiPrefsChanged(msg); break;
|
|
8015
8193
|
case 'created': onCreated(msg); break;
|
|
8016
8194
|
case 'output': onOutput(msg); break;
|
|
@@ -11356,6 +11534,10 @@ function _disposeTerminalRenderer(s) {
|
|
|
11356
11534
|
try { s._helperAlignDisposer.dispose(); } catch {}
|
|
11357
11535
|
s._helperAlignDisposer = null;
|
|
11358
11536
|
}
|
|
11537
|
+
if (s._docReviewLinkProvider) {
|
|
11538
|
+
try { s._docReviewLinkProvider.dispose(); } catch {}
|
|
11539
|
+
s._docReviewLinkProvider = null;
|
|
11540
|
+
}
|
|
11359
11541
|
if (s._webglAddon) {
|
|
11360
11542
|
try { s._webglAddon.dispose(); } catch {}
|
|
11361
11543
|
s._webglAddon = null;
|
|
@@ -12510,6 +12692,78 @@ function createTerminal(id, opts) {
|
|
|
12510
12692
|
return ' [' + label + ': ' + paths.map(p => '"' + p.replace(/"/g, '\\"') + '"').join(', ') + '] ';
|
|
12511
12693
|
}
|
|
12512
12694
|
|
|
12695
|
+
function _terminalImageSubmitKey(att) {
|
|
12696
|
+
return String(att && (att.path || att.url || att.filename) || '').trim();
|
|
12697
|
+
}
|
|
12698
|
+
|
|
12699
|
+
function _terminalPendingImageSubmitAttachments(id) {
|
|
12700
|
+
if (!_terminalProviderAcceptsImagePathPaste(id)) return [];
|
|
12701
|
+
const list = _terminalImageAttachmentState(id) || [];
|
|
12702
|
+
const seen = new Set();
|
|
12703
|
+
const pending = [];
|
|
12704
|
+
for (const att of list) {
|
|
12705
|
+
const key = _terminalImageSubmitKey(att);
|
|
12706
|
+
if (!key || seen.has(key)) continue;
|
|
12707
|
+
seen.add(key);
|
|
12708
|
+
if (att._terminalSubmitAcknowledgedAt || att._terminalSubmitDiscardedAt) continue;
|
|
12709
|
+
pending.push(att);
|
|
12710
|
+
}
|
|
12711
|
+
return pending;
|
|
12712
|
+
}
|
|
12713
|
+
|
|
12714
|
+
function _terminalMarkImageSubmitState(attachments, field) {
|
|
12715
|
+
const now = Date.now();
|
|
12716
|
+
for (const att of attachments || []) {
|
|
12717
|
+
if (!att) continue;
|
|
12718
|
+
att[field] = now;
|
|
12719
|
+
}
|
|
12720
|
+
}
|
|
12721
|
+
|
|
12722
|
+
function _terminalSubmittedPromptText(id, s, attachments) {
|
|
12723
|
+
const labels = (attachments || []).map(att => att && att.label).filter(Boolean).join(' ');
|
|
12724
|
+
const draft = String(_terminalInputDraftPreview(s, '') || '').trim();
|
|
12725
|
+
return [labels, draft].filter(Boolean).join(' ').trim();
|
|
12726
|
+
}
|
|
12727
|
+
|
|
12728
|
+
function _terminalRecordSessionImageRefs(id, attachments, promptText) {
|
|
12729
|
+
const list = (attachments || []).filter(Boolean).map(att => ({
|
|
12730
|
+
id: att.id || null,
|
|
12731
|
+
label: att.label || '',
|
|
12732
|
+
filename: att.filename || '',
|
|
12733
|
+
path: att.path || '',
|
|
12734
|
+
url: att.url || '',
|
|
12735
|
+
}));
|
|
12736
|
+
if (!list.length) return;
|
|
12737
|
+
fetch('/api/session/image-refs?token=' + encodeURIComponent(state.token || ''), {
|
|
12738
|
+
method: 'POST',
|
|
12739
|
+
headers: { 'Content-Type': 'application/json' },
|
|
12740
|
+
keepalive: true,
|
|
12741
|
+
body: JSON.stringify({
|
|
12742
|
+
sessionId: id,
|
|
12743
|
+
submittedAt: Date.now(),
|
|
12744
|
+
promptText: String(promptText || '').slice(0, 4096),
|
|
12745
|
+
images: list,
|
|
12746
|
+
}),
|
|
12747
|
+
}).catch(err => {
|
|
12748
|
+
console.warn('[terminal] session image ref recording failed:', err && err.message ? err.message : err);
|
|
12749
|
+
});
|
|
12750
|
+
}
|
|
12751
|
+
|
|
12752
|
+
function _terminalAcknowledgePendingImageSubmit(id, promptText) {
|
|
12753
|
+
const pending = _terminalPendingImageSubmitAttachments(id);
|
|
12754
|
+
if (!pending.length) return false;
|
|
12755
|
+
_terminalRecordSessionImageRefs(id, pending, promptText);
|
|
12756
|
+
_terminalMarkImageSubmitState(pending, '_terminalSubmitAcknowledgedAt');
|
|
12757
|
+
return true;
|
|
12758
|
+
}
|
|
12759
|
+
|
|
12760
|
+
function _terminalDiscardPendingImageSubmit(id) {
|
|
12761
|
+
const pending = _terminalPendingImageSubmitAttachments(id);
|
|
12762
|
+
if (!pending.length) return false;
|
|
12763
|
+
_terminalMarkImageSubmitState(pending, '_terminalSubmitDiscardedAt');
|
|
12764
|
+
return true;
|
|
12765
|
+
}
|
|
12766
|
+
|
|
12513
12767
|
function _terminalProviderAcceptsImagePathPaste(id) {
|
|
12514
12768
|
const s = state.sessions.get(id);
|
|
12515
12769
|
const agentType = _clientAgentTypeForSession(s);
|
|
@@ -12784,6 +13038,12 @@ function createTerminal(id, opts) {
|
|
|
12784
13038
|
if (!isSinglePrintableChar && data === _lastSentData && now - _lastSentTs < 10) return;
|
|
12785
13039
|
_lastSentData = data;
|
|
12786
13040
|
_lastSentTs = now;
|
|
13041
|
+
if (_terminalInputSubmitsComposer(data)) {
|
|
13042
|
+
const submitSession = state.sessions.get(id);
|
|
13043
|
+
const pending = _terminalPendingImageSubmitAttachments(id);
|
|
13044
|
+
_terminalAcknowledgePendingImageSubmit(id, _terminalSubmittedPromptText(id, submitSession, pending));
|
|
13045
|
+
}
|
|
13046
|
+
else if (String(data || '').includes('\x03') || String(data || '').includes('\x15')) _terminalDiscardPendingImageSubmit(id);
|
|
12787
13047
|
_forwardTerminalInput(id, data);
|
|
12788
13048
|
_terminalInputDraftObserve(id, data);
|
|
12789
13049
|
_terminalSkillObserveInput(id, data);
|
|
@@ -12918,6 +13178,9 @@ function createTerminal(id, opts) {
|
|
|
12918
13178
|
_sessionViewState(sessionEntry, id);
|
|
12919
13179
|
_markTerminalRendererChanged(sessionEntry, 'terminal-created');
|
|
12920
13180
|
state.sessions.set(id, sessionEntry);
|
|
13181
|
+
if (window.CTMDocLinks && typeof window.CTMDocLinks.registerTerminalLinkProvider === 'function') {
|
|
13182
|
+
try { sessionEntry._docReviewLinkProvider = window.CTMDocLinks.registerTerminalLinkProvider(term, id); } catch {}
|
|
13183
|
+
}
|
|
12921
13184
|
if (typeof term.onWriteParsed === 'function') {
|
|
12922
13185
|
sessionEntry._helperAlignDisposer = term.onWriteParsed(() => {
|
|
12923
13186
|
const current = state.sessions.get(id);
|
|
@@ -14053,7 +14316,7 @@ function activateTab(id) {
|
|
|
14053
14316
|
|
|
14054
14317
|
// Hide all
|
|
14055
14318
|
for (const [sid, s] of state.sessions) {
|
|
14056
|
-
s.container.classList.remove('active');
|
|
14319
|
+
if (s && s.container && s.container.classList) s.container.classList.remove('active');
|
|
14057
14320
|
}
|
|
14058
14321
|
document.getElementById('welcome').style.display = 'none';
|
|
14059
14322
|
document.getElementById('rules-panel').classList.remove('active');
|
|
@@ -15392,7 +15655,7 @@ function navTo(target, opts) {
|
|
|
15392
15655
|
} else if (target === 'permissions') {
|
|
15393
15656
|
showPermissionsPanel();
|
|
15394
15657
|
} else if (target === 'codereview') {
|
|
15395
|
-
showCodeReviewPanel();
|
|
15658
|
+
showCodeReviewPanel(opts || {});
|
|
15396
15659
|
} else if (target === 'walle') {
|
|
15397
15660
|
showWallePanel();
|
|
15398
15661
|
} else if (target === 'models') {
|
|
@@ -15406,14 +15669,16 @@ function navTo(target, opts) {
|
|
|
15406
15669
|
}
|
|
15407
15670
|
}
|
|
15408
15671
|
|
|
15409
|
-
function showCodeReviewPanel() {
|
|
15672
|
+
function showCodeReviewPanel(opts) {
|
|
15673
|
+
opts = opts || {};
|
|
15410
15674
|
if (!state.tabOrder.includes('codereview')) {
|
|
15411
15675
|
state.tabOrder.push('codereview');
|
|
15412
15676
|
}
|
|
15413
15677
|
activateTab('codereview');
|
|
15414
15678
|
renderTabs();
|
|
15415
15679
|
// Always show/refresh project list (unless in an active diff review)
|
|
15416
|
-
|
|
15680
|
+
const reviewIsBusy = window.crState && (window.crState._view === 'review' || window.crState.reviewType === 'doc');
|
|
15681
|
+
if (!opts.suppressProjectList && window.CR && (!window.crState || (!window.crState.reviewId && !reviewIsBusy))) {
|
|
15417
15682
|
CR.showProjectList();
|
|
15418
15683
|
}
|
|
15419
15684
|
}
|
|
@@ -15429,6 +15694,7 @@ function showWallePanel() {
|
|
|
15429
15694
|
// === Models Tab ===
|
|
15430
15695
|
var _modelsRegistryFilter = '';
|
|
15431
15696
|
var _modelsExpandedProviders = new Set();
|
|
15697
|
+
var _modelsCatalogExpanded = false;
|
|
15432
15698
|
var _modelsRetryTimer = null;
|
|
15433
15699
|
var _modelsRetryCount = 0;
|
|
15434
15700
|
var _modelsHasRenderedCatalog = false;
|
|
@@ -15530,6 +15796,7 @@ function _scheduleModelsRetry(delayMs) {
|
|
|
15530
15796
|
|
|
15531
15797
|
function renderModelsLoadState(kind, message, detail, retryDelayMs) {
|
|
15532
15798
|
var header = document.getElementById('models-header');
|
|
15799
|
+
var gateways = document.getElementById('models-gateways-section');
|
|
15533
15800
|
var grid = document.getElementById('models-providers-grid');
|
|
15534
15801
|
if (!header || !grid) return;
|
|
15535
15802
|
var isError = kind === 'error';
|
|
@@ -15551,6 +15818,7 @@ function renderModelsLoadState(kind, message, detail, retryDelayMs) {
|
|
|
15551
15818
|
'<button id="models-retry-btn" style="padding:6px 12px;border-radius:6px;border:1px solid var(--border,#414868);background:transparent;color:var(--fg,#c0caf5);cursor:pointer;font-size:12px;">Retry now</button>' +
|
|
15552
15819
|
'</div>';
|
|
15553
15820
|
header.innerHTML = DOMPurify.sanitize(h, { ADD_ATTR: ['style'] });
|
|
15821
|
+
if (gateways) gateways.innerHTML = '';
|
|
15554
15822
|
grid.innerHTML = DOMPurify.sanitize(body, { ADD_ATTR: ['style'] });
|
|
15555
15823
|
var addBtn = document.getElementById('models-add-provider-btn');
|
|
15556
15824
|
if (addBtn) addBtn.addEventListener('click', function() { showAddProviderDialog(); });
|
|
@@ -15570,9 +15838,10 @@ async function showModelsPanel(options) {
|
|
|
15570
15838
|
renderModelsLoadState('loading', 'Loading model catalog...', 'CTM is reading model providers and registry data from Wall-E.', 0);
|
|
15571
15839
|
}
|
|
15572
15840
|
try {
|
|
15573
|
-
const [provRes, regRes, scRes, healthRes, tierRes, promoRes, reviewRes, shadowRes] = await Promise.all([
|
|
15841
|
+
const [provRes, regRes, gatewayRes, scRes, healthRes, tierRes, promoRes, reviewRes, shadowRes] = await Promise.all([
|
|
15574
15842
|
_fetchModelsJson('/api/models/providers', { listKey: 'providers' }),
|
|
15575
15843
|
_fetchModelsJson('/api/models/registry', { listKey: 'models' }),
|
|
15844
|
+
_fetchModelsJson('/api/models/gateways', { listKey: 'gateways' }).catch(function() { return { gateways: [] }; }),
|
|
15576
15845
|
_fetchModelsJson('/api/models/scorecard?days=' + encodeURIComponent(_scorecardDaysParam())).catch(function() { return { scorecard: [], benchmarks: [], benchmarkCoverage: null }; }),
|
|
15577
15846
|
_fetchModelsJson('/api/models/health').catch(function() { return {}; }),
|
|
15578
15847
|
_fetchModelsJson('/api/models/tier-defaults').catch(function() { return {}; }),
|
|
@@ -15582,9 +15851,11 @@ async function showModelsPanel(options) {
|
|
|
15582
15851
|
]);
|
|
15583
15852
|
var providers = _modelsPayloadList(provRes, 'providers') || [];
|
|
15584
15853
|
var models = _modelsPayloadList(regRes, 'models') || [];
|
|
15854
|
+
var gateways = _modelsPayloadList(gatewayRes, 'gateways') || [];
|
|
15585
15855
|
_modelsRetryCount = 0;
|
|
15586
15856
|
_modelsHasRenderedCatalog = true;
|
|
15587
15857
|
renderModelsHeader(providers, models);
|
|
15858
|
+
renderModelGateways(gateways, providers, models);
|
|
15588
15859
|
renderModelProviders(providers, models, healthRes);
|
|
15589
15860
|
renderShadowConfig(shadowRes, models);
|
|
15590
15861
|
renderTierMatrix(tierRes, providers);
|
|
@@ -15689,6 +15960,124 @@ function _portkeyDefaultBaseUrl(type) {
|
|
|
15689
15960
|
return type === 'anthropic' ? 'https://api.portkey.ai' : 'https://api.portkey.ai/v1';
|
|
15690
15961
|
}
|
|
15691
15962
|
|
|
15963
|
+
function _providerRoutePolicyLabel(policy) {
|
|
15964
|
+
if (policy === 'portkey') return 'Prefer Portkey';
|
|
15965
|
+
if (policy === 'direct') return 'Prefer Direct';
|
|
15966
|
+
return 'Auto';
|
|
15967
|
+
}
|
|
15968
|
+
|
|
15969
|
+
function _formatModelSyncTime(value) {
|
|
15970
|
+
if (!value) return 'Not synced yet';
|
|
15971
|
+
var t = Date.parse(value);
|
|
15972
|
+
if (!Number.isFinite(t)) return value;
|
|
15973
|
+
var seconds = Math.max(0, Math.round((Date.now() - t) / 1000));
|
|
15974
|
+
if (seconds < 60) return seconds + 's ago';
|
|
15975
|
+
var minutes = Math.round(seconds / 60);
|
|
15976
|
+
if (minutes < 60) return minutes + 'm ago';
|
|
15977
|
+
var hours = Math.round(minutes / 60);
|
|
15978
|
+
if (hours < 48) return hours + 'h ago';
|
|
15979
|
+
return new Date(t).toLocaleString();
|
|
15980
|
+
}
|
|
15981
|
+
|
|
15982
|
+
function _modelProviderSupportsPortkey(type) {
|
|
15983
|
+
return ['anthropic', 'openai', 'google', 'deepseek', 'moonshot'].indexOf(type) >= 0;
|
|
15984
|
+
}
|
|
15985
|
+
|
|
15986
|
+
function _modelProviderRoutePolicy(providers, type) {
|
|
15987
|
+
var row = (providers || []).find(function(p) { return p.type === type && p.route_policy; });
|
|
15988
|
+
return row ? row.route_policy : 'auto';
|
|
15989
|
+
}
|
|
15990
|
+
|
|
15991
|
+
async function setProviderRoutePolicy(type, policy) {
|
|
15992
|
+
await fetch('/api/models/provider-route-policy?token=' + state.token, {
|
|
15993
|
+
method: 'POST',
|
|
15994
|
+
headers: { 'Content-Type': 'application/json' },
|
|
15995
|
+
body: JSON.stringify({ type: type, policy: policy }),
|
|
15996
|
+
});
|
|
15997
|
+
showModelsPanel();
|
|
15998
|
+
}
|
|
15999
|
+
|
|
16000
|
+
async function syncPortkeyModels(providerId) {
|
|
16001
|
+
await fetch('/api/models/portkey/sync?token=' + state.token, {
|
|
16002
|
+
method: 'POST',
|
|
16003
|
+
headers: { 'Content-Type': 'application/json' },
|
|
16004
|
+
body: JSON.stringify(providerId ? { provider_id: providerId } : {}),
|
|
16005
|
+
});
|
|
16006
|
+
showModelsPanel();
|
|
16007
|
+
}
|
|
16008
|
+
|
|
16009
|
+
async function applyPortkeyToAllProviders() {
|
|
16010
|
+
await fetch('/api/models/portkey/apply-all?token=' + state.token, {
|
|
16011
|
+
method: 'POST',
|
|
16012
|
+
headers: { 'Content-Type': 'application/json' },
|
|
16013
|
+
body: JSON.stringify({}),
|
|
16014
|
+
});
|
|
16015
|
+
showModelsPanel();
|
|
16016
|
+
}
|
|
16017
|
+
|
|
16018
|
+
async function disablePortkeyDefault() {
|
|
16019
|
+
await fetch('/api/models/portkey/disable-default?token=' + state.token, {
|
|
16020
|
+
method: 'POST',
|
|
16021
|
+
headers: { 'Content-Type': 'application/json' },
|
|
16022
|
+
body: JSON.stringify({}),
|
|
16023
|
+
});
|
|
16024
|
+
showModelsPanel();
|
|
16025
|
+
}
|
|
16026
|
+
|
|
16027
|
+
async function removePortkeyGateway() {
|
|
16028
|
+
if (!confirm('Remove all Portkey gateway connections and their imported gateway models? Direct provider API access will remain.')) return;
|
|
16029
|
+
await fetch('/api/models/gateways/portkey?token=' + state.token, { method: 'DELETE' });
|
|
16030
|
+
showModelsPanel();
|
|
16031
|
+
}
|
|
16032
|
+
|
|
16033
|
+
function renderModelGateways(gateways, providers, models) {
|
|
16034
|
+
var el = document.getElementById('models-gateways-section');
|
|
16035
|
+
if (!el) return;
|
|
16036
|
+
var portkey = (gateways || []).find(function(g) { return g.type === 'portkey'; }) || { type: 'portkey', name: 'Portkey Gateway', routes: [], route_count: 0, provider_count: 0, model_count: 0, provider_types: [] };
|
|
16037
|
+
var hasRoutes = (portkey.routes || []).length > 0;
|
|
16038
|
+
var routeSummary = hasRoutes
|
|
16039
|
+
? portkey.provider_count + ' provider' + (portkey.provider_count !== 1 ? 's' : '') + ' · ' + portkey.model_count + ' imported model' + (portkey.model_count !== 1 ? 's' : '')
|
|
16040
|
+
: 'No Portkey gateway configured';
|
|
16041
|
+
var lastSync = portkey.last_success_at || portkey.last_run_at || '';
|
|
16042
|
+
var h = '<section style="margin-bottom:14px;border:1px solid var(--border,#414868);background:var(--bg-card,#24283b);border-radius:8px;padding:14px;">' +
|
|
16043
|
+
'<div style="display:flex;justify-content:space-between;gap:12px;align-items:flex-start;flex-wrap:wrap;">' +
|
|
16044
|
+
'<div>' +
|
|
16045
|
+
'<div style="font-size:11px;color:var(--fg-dim,#565f89);text-transform:uppercase;letter-spacing:0.5px;margin-bottom:4px;">Gateways</div>' +
|
|
16046
|
+
'<div style="font-size:15px;color:var(--fg,#c0caf5);font-weight:600;">Portkey Gateway</div>' +
|
|
16047
|
+
'<div style="font-size:12px;color:var(--fg-dim,#565f89);margin-top:3px;">' + escHtml(routeSummary) + '</div>' +
|
|
16048
|
+
'<div style="font-size:11px;color:' + (portkey.last_error ? '#f7768e' : '#7dcfff') + ';margin-top:5px;">' +
|
|
16049
|
+
(portkey.last_error ? 'Last sync error: ' + escHtml(String(portkey.last_error).slice(0, 120)) : 'Last sync: ' + escHtml(_formatModelSyncTime(lastSync))) +
|
|
16050
|
+
'</div>' +
|
|
16051
|
+
'</div>' +
|
|
16052
|
+
'<div style="display:flex;gap:6px;flex-wrap:wrap;justify-content:flex-end;">' +
|
|
16053
|
+
'<button data-connect-portkey style="padding:5px 10px;border-radius:5px;border:1px solid var(--border,#414868);background:' + (hasRoutes ? 'transparent' : 'var(--accent,#7aa2f7)') + ';color:' + (hasRoutes ? 'var(--fg,#c0caf5)' : '#1a1b26') + ';cursor:pointer;font-size:11px;font-weight:600;">' + (hasRoutes ? 'Add Route' : 'Connect Portkey') + '</button>' +
|
|
16054
|
+
'<button data-sync-portkey style="padding:5px 10px;border-radius:5px;border:1px solid var(--border,#414868);background:transparent;color:var(--fg,#c0caf5);cursor:pointer;font-size:11px;">Sync now</button>' +
|
|
16055
|
+
'<button data-apply-portkey-all style="padding:5px 10px;border-radius:5px;border:1px solid var(--border,#414868);background:transparent;color:#7dcfff;cursor:pointer;font-size:11px;">Apply to all providers</button>' +
|
|
16056
|
+
'<button data-disable-portkey-default style="padding:5px 10px;border-radius:5px;border:1px solid var(--border,#414868);background:transparent;color:var(--fg-dim,#565f89);cursor:pointer;font-size:11px;">Disable as default</button>' +
|
|
16057
|
+
(hasRoutes ? '<button data-remove-portkey style="padding:5px 10px;border-radius:5px;border:none;background:transparent;color:#f7768e;cursor:pointer;font-size:11px;">Remove</button>' : '') +
|
|
16058
|
+
'</div>' +
|
|
16059
|
+
'</div>';
|
|
16060
|
+
if (hasRoutes) {
|
|
16061
|
+
h += '<div style="display:flex;gap:6px;flex-wrap:wrap;margin-top:10px;">' + (portkey.routes || []).map(function(route) {
|
|
16062
|
+
return '<span style="font-size:10px;color:var(--fg-dim,#565f89);background:var(--bg,#1a1b26);border:1px solid var(--border,#414868);border-radius:4px;padding:3px 7px;">' +
|
|
16063
|
+
escHtml(_providerTypeName(route.type)) + ' · ' + escHtml(_providerRoutePolicyLabel(route.route_policy || 'auto')) + ' · ' + route.model_count + ' model' + (route.model_count !== 1 ? 's' : '') +
|
|
16064
|
+
'</span>';
|
|
16065
|
+
}).join('') + '</div>';
|
|
16066
|
+
}
|
|
16067
|
+
h += '</section>';
|
|
16068
|
+
el.innerHTML = DOMPurify.sanitize(h, { ADD_ATTR: ['style', 'data-connect-portkey', 'data-sync-portkey', 'data-apply-portkey-all', 'data-disable-portkey-default', 'data-remove-portkey'] });
|
|
16069
|
+
var connect = el.querySelector('[data-connect-portkey]');
|
|
16070
|
+
if (connect) connect.addEventListener('click', function() { showAddProviderDialog('', 'portkey'); });
|
|
16071
|
+
var sync = el.querySelector('[data-sync-portkey]');
|
|
16072
|
+
if (sync) sync.addEventListener('click', function() { sync.textContent = 'Syncing...'; syncPortkeyModels(); });
|
|
16073
|
+
var applyAll = el.querySelector('[data-apply-portkey-all]');
|
|
16074
|
+
if (applyAll) applyAll.addEventListener('click', applyPortkeyToAllProviders);
|
|
16075
|
+
var disableDefault = el.querySelector('[data-disable-portkey-default]');
|
|
16076
|
+
if (disableDefault) disableDefault.addEventListener('click', disablePortkeyDefault);
|
|
16077
|
+
var remove = el.querySelector('[data-remove-portkey]');
|
|
16078
|
+
if (remove) remove.addEventListener('click', removePortkeyGateway);
|
|
16079
|
+
}
|
|
16080
|
+
|
|
15692
16081
|
function renderModelsHeader(providers, models) {
|
|
15693
16082
|
var el = document.getElementById('models-header');
|
|
15694
16083
|
var activeProviders = providers.filter(function(p) { return p.enabled; }).length;
|
|
@@ -15769,6 +16158,9 @@ function renderModelProviders(providers, models, healthData) {
|
|
|
15769
16158
|
|
|
15770
16159
|
var anyEnabled = instances.some(function(p) { return p.enabled; });
|
|
15771
16160
|
var instanceCountLabel = instances.length > 1 ? ' (' + instances.length + ' routes)' : '';
|
|
16161
|
+
var portkeyInstances = instances.filter(function(p) { return _providerConnectionKind(p) === 'portkey'; });
|
|
16162
|
+
var directInstances = instances.filter(function(p) { return _providerConnectionKind(p) !== 'portkey'; });
|
|
16163
|
+
var routePolicy = _modelProviderRoutePolicy(instances, type);
|
|
15772
16164
|
|
|
15773
16165
|
// Brand mark next to the provider name. Falls back to a tinted dot when
|
|
15774
16166
|
// brandIconSvg() doesn't recognise the type (custom / unknown).
|
|
@@ -15800,6 +16192,27 @@ function renderModelProviders(providers, models, healthData) {
|
|
|
15800
16192
|
'</div>' +
|
|
15801
16193
|
'</div>';
|
|
15802
16194
|
|
|
16195
|
+
if (!isLocal && _modelProviderSupportsPortkey(type)) {
|
|
16196
|
+
h += '<div style="margin-top:10px;background:var(--bg,#1a1b26);border-radius:6px;padding:9px 10px;">' +
|
|
16197
|
+
'<div style="display:flex;justify-content:space-between;gap:8px;align-items:center;flex-wrap:wrap;">' +
|
|
16198
|
+
'<div>' +
|
|
16199
|
+
'<div style="font-size:10px;color:var(--fg-dim,#565f89);text-transform:uppercase;letter-spacing:0.5px;">Access Route</div>' +
|
|
16200
|
+
'<div style="font-size:12px;color:var(--fg,#c0caf5);margin-top:2px;">' + escHtml(_providerRoutePolicyLabel(routePolicy)) + '</div>' +
|
|
16201
|
+
'<div style="font-size:10px;color:var(--fg-dim,#565f89);margin-top:2px;">' +
|
|
16202
|
+
directInstances.length + ' direct · ' + portkeyInstances.length + ' Portkey' +
|
|
16203
|
+
'</div>' +
|
|
16204
|
+
'</div>' +
|
|
16205
|
+
'<div style="display:flex;gap:5px;flex-wrap:wrap;justify-content:flex-end;">' +
|
|
16206
|
+
(portkeyInstances.length
|
|
16207
|
+
? '<button data-route-policy-type="' + escHtml(type) + '" data-route-policy="portkey" style="padding:4px 8px;border-radius:4px;border:1px solid ' + (routePolicy === 'portkey' ? '#7dcfff' : 'var(--border,#414868)') + ';background:' + (routePolicy === 'portkey' ? '#7dcfff22' : 'transparent') + ';color:#7dcfff;cursor:pointer;font-size:10px;">Use Portkey</button>'
|
|
16208
|
+
: '<button data-enable-portkey-type="' + escHtml(type) + '" style="padding:4px 8px;border-radius:4px;border:1px solid var(--border,#414868);background:transparent;color:#7dcfff;cursor:pointer;font-size:10px;">Enable Portkey</button>') +
|
|
16209
|
+
'<button data-route-policy-type="' + escHtml(type) + '" data-route-policy="direct" style="padding:4px 8px;border-radius:4px;border:1px solid ' + (routePolicy === 'direct' ? '#9ece6a' : 'var(--border,#414868)') + ';background:' + (routePolicy === 'direct' ? '#9ece6a22' : 'transparent') + ';color:#9ece6a;cursor:pointer;font-size:10px;">Prefer Direct</button>' +
|
|
16210
|
+
'<button data-route-policy-type="' + escHtml(type) + '" data-route-policy="auto" style="padding:4px 8px;border-radius:4px;border:1px solid ' + (routePolicy === 'auto' ? '#7aa2f7' : 'var(--border,#414868)') + ';background:' + (routePolicy === 'auto' ? '#7aa2f722' : 'transparent') + ';color:#7aa2f7;cursor:pointer;font-size:10px;">Auto</button>' +
|
|
16211
|
+
'</div>' +
|
|
16212
|
+
'</div>' +
|
|
16213
|
+
'</div>';
|
|
16214
|
+
}
|
|
16215
|
+
|
|
15803
16216
|
// Instance sub-rows (shown when multiple keys exist OR always for clarity)
|
|
15804
16217
|
if (instances.length > 0) {
|
|
15805
16218
|
h += '<div style="margin-top:12px;border-top:1px solid var(--border,#414868);padding-top:10px;">';
|
|
@@ -15843,9 +16256,17 @@ function renderModelProviders(providers, models, healthData) {
|
|
|
15843
16256
|
h += '</div>';
|
|
15844
16257
|
});
|
|
15845
16258
|
h += '</div>';
|
|
15846
|
-
el.innerHTML = DOMPurify.sanitize(h, { ADD_ATTR: ['style', 'data-rescan-provider', 'data-delete-provider', 'data-type', 'data-test-instance', 'data-add-key-type', 'title'] });
|
|
16259
|
+
el.innerHTML = DOMPurify.sanitize(h, { ADD_ATTR: ['style', 'data-rescan-provider', 'data-delete-provider', 'data-type', 'data-test-instance', 'data-add-key-type', 'data-route-policy-type', 'data-route-policy', 'data-enable-portkey-type', 'title'] });
|
|
15847
16260
|
|
|
15848
16261
|
// Wire up event handlers
|
|
16262
|
+
el.querySelectorAll('[data-route-policy-type]').forEach(function(btn) {
|
|
16263
|
+
btn.addEventListener('click', function() {
|
|
16264
|
+
setProviderRoutePolicy(btn.dataset.routePolicyType, btn.dataset.routePolicy || 'auto');
|
|
16265
|
+
});
|
|
16266
|
+
});
|
|
16267
|
+
el.querySelectorAll('[data-enable-portkey-type]').forEach(function(btn) {
|
|
16268
|
+
btn.addEventListener('click', function() { showAddProviderDialog(btn.dataset.enablePortkeyType, 'portkey'); });
|
|
16269
|
+
});
|
|
15849
16270
|
el.querySelectorAll('[data-delete-provider]').forEach(function(btn) {
|
|
15850
16271
|
btn.addEventListener('click', function() { deleteModelProvider(btn.dataset.deleteProvider); });
|
|
15851
16272
|
});
|
|
@@ -15963,10 +16384,17 @@ function renderModelRegistry(models) {
|
|
|
15963
16384
|
groups[key].push(m);
|
|
15964
16385
|
});
|
|
15965
16386
|
|
|
15966
|
-
|
|
15967
|
-
|
|
15968
|
-
'<
|
|
15969
|
-
|
|
16387
|
+
if (_modelsRegistryFilter) _modelsCatalogExpanded = true;
|
|
16388
|
+
var h = '<div style="border:1px solid var(--border,#414868);border-radius:8px;overflow:hidden;background:var(--bg-card,#24283b);">' +
|
|
16389
|
+
'<div data-toggle-model-catalog style="display:flex;justify-content:space-between;align-items:center;padding:10px 14px;cursor:pointer;user-select:none;">' +
|
|
16390
|
+
'<div style="display:flex;align-items:center;gap:8px;">' +
|
|
16391
|
+
'<span style="color:#7dcfff;font-size:11px;transition:transform 0.2s;display:inline-block;' + (_modelsCatalogExpanded ? 'transform:rotate(90deg)' : '') + ';">\u25B6</span>' +
|
|
16392
|
+
'<span style="font-size:13px;font-weight:600;color:var(--fg,#c0caf5);">Model Catalog</span>' +
|
|
16393
|
+
'<span style="font-size:11px;color:var(--fg-dim,#565f89);">' + models.length + ' models</span>' +
|
|
16394
|
+
'</div>' +
|
|
16395
|
+
'<span style="font-size:11px;color:var(--fg-dim,#565f89);">' + (_modelsCatalogExpanded ? 'Collapse' : 'Expand') + '</span>' +
|
|
16396
|
+
'</div>' +
|
|
16397
|
+
'<div class="models-catalog-body" style="display:' + (_modelsCatalogExpanded ? 'block' : 'none') + ';border-top:1px solid var(--border,#414868);padding:8px;">';
|
|
15970
16398
|
|
|
15971
16399
|
groupOrder.forEach(function(provName) {
|
|
15972
16400
|
var pModels = groups[provName];
|
|
@@ -16019,7 +16447,17 @@ function renderModelRegistry(models) {
|
|
|
16019
16447
|
h += '</div>';
|
|
16020
16448
|
});
|
|
16021
16449
|
|
|
16022
|
-
|
|
16450
|
+
h += '</div></div>';
|
|
16451
|
+
|
|
16452
|
+
el.innerHTML = DOMPurify.sanitize(h, { ADD_ATTR: ['style', 'data-toggle-model-catalog', 'data-toggle-provider', 'data-provider', 'data-name'] });
|
|
16453
|
+
var catalogToggle = el.querySelector('[data-toggle-model-catalog]');
|
|
16454
|
+
if (catalogToggle) {
|
|
16455
|
+
catalogToggle.addEventListener('click', function() {
|
|
16456
|
+
_modelsCatalogExpanded = !_modelsCatalogExpanded;
|
|
16457
|
+
renderModelRegistry(models);
|
|
16458
|
+
_filterModelCards();
|
|
16459
|
+
});
|
|
16460
|
+
}
|
|
16023
16461
|
// Bind toggle
|
|
16024
16462
|
el.querySelectorAll('[data-toggle-provider]').forEach(function(hdr) {
|
|
16025
16463
|
hdr.addEventListener('click', function() {
|
|
@@ -16041,6 +16479,11 @@ function renderModelRegistry(models) {
|
|
|
16041
16479
|
|
|
16042
16480
|
function _filterModelCards() {
|
|
16043
16481
|
var q = (_modelsRegistryFilter || '').toLowerCase();
|
|
16482
|
+
var catalogBody = document.querySelector('#models-registry-table .models-catalog-body');
|
|
16483
|
+
if (catalogBody && q) {
|
|
16484
|
+
catalogBody.style.display = 'block';
|
|
16485
|
+
_modelsCatalogExpanded = true;
|
|
16486
|
+
}
|
|
16044
16487
|
document.querySelectorAll('.models-provider-group').forEach(function(group) {
|
|
16045
16488
|
var rows = group.querySelectorAll('.model-row');
|
|
16046
16489
|
var visibleCount = 0;
|
|
@@ -17610,8 +18053,9 @@ async function deleteModelProvider(id) {
|
|
|
17610
18053
|
showModelsPanel();
|
|
17611
18054
|
}
|
|
17612
18055
|
|
|
17613
|
-
async function showAddProviderDialog(preselectedType) {
|
|
18056
|
+
async function showAddProviderDialog(preselectedType, preferredConnection) {
|
|
17614
18057
|
if (typeof preselectedType !== 'string') preselectedType = '';
|
|
18058
|
+
if (typeof preferredConnection !== 'string') preferredConnection = '';
|
|
17615
18059
|
var existing = document.getElementById('add-provider-dialog');
|
|
17616
18060
|
if (existing) existing.remove();
|
|
17617
18061
|
|
|
@@ -17733,6 +18177,10 @@ async function showAddProviderDialog(preselectedType) {
|
|
|
17733
18177
|
}
|
|
17734
18178
|
document.getElementById('new-provider-type').addEventListener('change', updateProviderPlaceholders);
|
|
17735
18179
|
document.getElementById('new-provider-connection').addEventListener('change', updateProviderPlaceholders);
|
|
18180
|
+
if (preferredConnection === 'portkey') {
|
|
18181
|
+
var connectionEl = document.getElementById('new-provider-connection');
|
|
18182
|
+
if (connectionEl) connectionEl.value = 'portkey';
|
|
18183
|
+
}
|
|
17736
18184
|
updateProviderPlaceholders();
|
|
17737
18185
|
|
|
17738
18186
|
// Only scan for env keys when adding a new provider type, not when adding a key to existing type
|
|
@@ -17984,12 +18432,25 @@ async function saveNewProvider() {
|
|
|
17984
18432
|
body: JSON.stringify({
|
|
17985
18433
|
id: id + ':' + m.id, providerId: id, modelId: m.id,
|
|
17986
18434
|
displayName: m.name, capabilities: m.capabilities || ['code'],
|
|
18435
|
+
source: payload.connection === 'portkey' ? 'portkey' : 'catalog',
|
|
18436
|
+
gatewayType: payload.connection === 'portkey' ? 'portkey' : null,
|
|
18437
|
+
verificationStatus: payload.connection === 'portkey' ? 'unverified' : 'verified',
|
|
18438
|
+
lastSeenAt: payload.connection === 'portkey' ? new Date().toISOString() : null,
|
|
17987
18439
|
speedTier: 3, enabled: 1,
|
|
17988
18440
|
})
|
|
17989
18441
|
});
|
|
17990
18442
|
}
|
|
17991
18443
|
}
|
|
17992
18444
|
} catch (e) { /* ignore auto-detect failures */ }
|
|
18445
|
+
if (payload.connection === 'portkey') {
|
|
18446
|
+
try {
|
|
18447
|
+
await fetch('/api/models/provider-route-policy?token=' + state.token, {
|
|
18448
|
+
method: 'POST',
|
|
18449
|
+
headers: { 'Content-Type': 'application/json' },
|
|
18450
|
+
body: JSON.stringify({ type: type, policy: 'portkey' }),
|
|
18451
|
+
});
|
|
18452
|
+
} catch (e) { /* route policy is optional; provider was saved */ }
|
|
18453
|
+
}
|
|
17993
18454
|
var dlg = document.getElementById('add-provider-dialog');
|
|
17994
18455
|
if (dlg) dlg.remove();
|
|
17995
18456
|
showModelsPanel();
|
|
@@ -18302,12 +18763,22 @@ function _wtRecommendedButton(wt) {
|
|
|
18302
18763
|
return btn;
|
|
18303
18764
|
}
|
|
18304
18765
|
|
|
18766
|
+
function _wtTrackedDirty(wt) {
|
|
18767
|
+
if (!wt) return 0;
|
|
18768
|
+
if (wt.trackedDirtyFiles != null) return Number(wt.trackedDirtyFiles || 0);
|
|
18769
|
+
return Math.max(0, Number(wt.dirtyFiles || 0) - Number(wt.untrackedFiles || 0));
|
|
18770
|
+
}
|
|
18771
|
+
|
|
18772
|
+
function _wtUntrackedOnly(wt) {
|
|
18773
|
+
return !!wt && Number(wt.dirtyFiles || 0) > 0 && _wtTrackedDirty(wt) === 0;
|
|
18774
|
+
}
|
|
18775
|
+
|
|
18305
18776
|
function _wtSyncBlockReason(wt) {
|
|
18306
18777
|
if (!wt || wt.isMain) return '';
|
|
18307
18778
|
if (wt.sessionId && !wt.activeSyncEligible) {
|
|
18308
18779
|
return wt.activeSyncBlockReason || 'Close the active session before syncing from main.';
|
|
18309
18780
|
}
|
|
18310
|
-
if ((wt
|
|
18781
|
+
if (_wtTrackedDirty(wt) > 0) return 'Commit or stash tracked dirty files before syncing from main.';
|
|
18311
18782
|
if (!wt.branch || wt.branch === 'HEAD' || wt.state === 'detached') return 'Recover this worktree onto a branch before syncing from main.';
|
|
18312
18783
|
if (wt.isGhost || wt.state === 'ghost') return 'Prune or recover this ghost worktree before syncing from main.';
|
|
18313
18784
|
return '';
|
|
@@ -18346,7 +18817,7 @@ function _wtSyncAllEligible(wts) {
|
|
|
18346
18817
|
&& ['behind', 'diverged'].indexOf(wt.state) !== -1
|
|
18347
18818
|
&& !wt.isGhost
|
|
18348
18819
|
&& (inactiveEligible || activeIdleEligible)
|
|
18349
|
-
&& (wt
|
|
18820
|
+
&& _wtTrackedDirty(wt) === 0
|
|
18350
18821
|
&& (wt.behind || 0) > 0;
|
|
18351
18822
|
});
|
|
18352
18823
|
}
|
|
@@ -18430,8 +18901,8 @@ async function loadWorktreesPanel(opts) {
|
|
|
18430
18901
|
syncAllBtn.textContent = '↓ Sync all' + (syncTargets.length > 0 ? ' (' + syncTargets.length + ')' : '');
|
|
18431
18902
|
syncAllBtn.disabled = false;
|
|
18432
18903
|
syncAllBtn.title = syncTargets.length > 0
|
|
18433
|
-
? 'Sync ' + syncTargets.length + ' clean inactive or idle active branch(es) that are behind main'
|
|
18434
|
-
: 'No clean inactive or idle active branches are behind main';
|
|
18904
|
+
? 'Sync ' + syncTargets.length + ' tracked-clean inactive or idle active branch(es) that are behind main'
|
|
18905
|
+
: 'No tracked-clean inactive or idle active branches are behind main';
|
|
18435
18906
|
}
|
|
18436
18907
|
|
|
18437
18908
|
_wtRenderFilterChips(d.counts || {}, wts);
|
|
@@ -18711,9 +19182,9 @@ function _wtRenderCard(frag, wt) {
|
|
|
18711
19182
|
prBtn.className = 'btn';
|
|
18712
19183
|
prBtn.style.cssText = 'font-size:11px;padding:4px 10px;background:rgba(122,162,247,0.12);color:#7aa2f7;border:1px solid rgba(122,162,247,0.3);';
|
|
18713
19184
|
prBtn.textContent = 'PR ▶';
|
|
18714
|
-
prBtn.disabled = !!wt.sessionId || wt
|
|
19185
|
+
prBtn.disabled = !!wt.sessionId || _wtTrackedDirty(wt) > 0;
|
|
18715
19186
|
if (wt.sessionId) prBtn.title = 'Close the active session before pushing';
|
|
18716
|
-
else if (wt
|
|
19187
|
+
else if (_wtTrackedDirty(wt) > 0) prBtn.title = 'Commit or stash tracked dirty files before pushing';
|
|
18717
19188
|
prBtn.onclick = function() { openCreatePRModal(wt); };
|
|
18718
19189
|
actions.appendChild(prBtn);
|
|
18719
19190
|
}
|
|
@@ -18763,7 +19234,8 @@ function _wtRenderCard(frag, wt) {
|
|
|
18763
19234
|
metrics.style.cssText = 'font-size:11px;color:var(--fg-dim,#565f89);display:flex;flex-wrap:wrap;gap:6px;margin:0 0 8px;';
|
|
18764
19235
|
if (!wt.isMain && wt.state !== 'ghost') {
|
|
18765
19236
|
metrics.appendChild(_wtMetric('vs main', '+' + (wt.ahead || 0) + ' / -' + (wt.behind || 0), (wt.ahead || wt.behind) ? 'warn' : 'good'));
|
|
18766
|
-
metrics.appendChild(_wtMetric('dirty', String(wt
|
|
19237
|
+
metrics.appendChild(_wtMetric('dirty', String(_wtTrackedDirty(wt)), _wtTrackedDirty(wt) ? 'bad' : 'good'));
|
|
19238
|
+
if ((wt.untrackedFiles || 0) > 0) metrics.appendChild(_wtMetric('untracked', String(wt.untrackedFiles || 0), 'neutral'));
|
|
18767
19239
|
metrics.appendChild(_wtMetric('unmerged', String(wt.unmergedCommits || 0), wt.unmergedCommits ? 'warn' : 'good'));
|
|
18768
19240
|
metrics.appendChild(_wtMetric('cleanup', wt.safeCleanup ? 'safe' : 'blocked', wt.safeCleanup ? 'good' : 'warn'));
|
|
18769
19241
|
}
|
|
@@ -18815,9 +19287,12 @@ function openSyncModal(wt) {
|
|
|
18815
19287
|
};
|
|
18816
19288
|
var sum = document.getElementById('wt-sync-summary');
|
|
18817
19289
|
if (sum) {
|
|
18818
|
-
|
|
19290
|
+
var untrackedNote = (wt.untrackedFiles || 0) > 0 && _wtTrackedDirty(wt) === 0
|
|
19291
|
+
? ' Untracked files will be kept local; CTM will stop if main would overwrite one.'
|
|
19292
|
+
: '';
|
|
19293
|
+
sum.textContent = (activeSync
|
|
18819
19294
|
? 'Pull main into idle active session "' + (wt.sessionLabel || wt.branch) + '" (' + wt.behind + ' commits behind).'
|
|
18820
|
-
: 'Pull main into "' + wt.branch + '" (' + wt.behind + ' commits behind).';
|
|
19295
|
+
: 'Pull main into "' + wt.branch + '" (' + wt.behind + ' commits behind).') + untrackedNote;
|
|
18821
19296
|
}
|
|
18822
19297
|
var strategy = document.getElementById('wt-sync-strategy');
|
|
18823
19298
|
if (strategy) {
|
|
@@ -18882,11 +19357,11 @@ async function submitSyncWorktree() {
|
|
|
18882
19357
|
async function submitSyncAllWorktrees() {
|
|
18883
19358
|
var targets = _wtSyncAllEligible((_wtCache && _wtCache.items) || []);
|
|
18884
19359
|
if (targets.length === 0) {
|
|
18885
|
-
toast('No clean inactive or idle active branches are behind main', { type: 'info' });
|
|
19360
|
+
toast('No tracked-clean inactive or idle active branches are behind main', { type: 'info' });
|
|
18886
19361
|
return;
|
|
18887
19362
|
}
|
|
18888
19363
|
var activeIdleCount = targets.filter(function(wt) { return !!wt.sessionId; }).length;
|
|
18889
|
-
var confirmMsg = 'Sync ' + targets.length + ' clean branch(es) from main?
|
|
19364
|
+
var confirmMsg = 'Sync ' + targets.length + ' tracked-clean branch(es) from main? Tracked-dirty, running/waiting active-session, detached, and ghost worktrees will be skipped.';
|
|
18890
19365
|
if (activeIdleCount) confirmMsg += ' ' + activeIdleCount + ' idle active session(s) will use merge with a pre-sync checkpoint.';
|
|
18891
19366
|
if (!confirm(confirmMsg)) return;
|
|
18892
19367
|
var btn = document.getElementById('wt-sync-all-btn');
|
|
@@ -19076,6 +19551,8 @@ function _wtGateWorktree(msg, wt) {
|
|
|
19076
19551
|
if (msg.ahead != null) merged.ahead = msg.ahead || 0;
|
|
19077
19552
|
if (msg.behind != null) merged.behind = msg.behind || 0;
|
|
19078
19553
|
if (msg.dirtyFiles != null) merged.dirtyFiles = msg.dirtyFiles || 0;
|
|
19554
|
+
if (msg.trackedDirtyFiles != null) merged.trackedDirtyFiles = msg.trackedDirtyFiles || 0;
|
|
19555
|
+
if (msg.untrackedFiles != null) merged.untrackedFiles = msg.untrackedFiles || 0;
|
|
19079
19556
|
if (msg.unmergedCommits != null) merged.unmergedCommits = msg.unmergedCommits || 0;
|
|
19080
19557
|
if (msg.summary) merged.summary = msg.summary;
|
|
19081
19558
|
if (msg.worktreePath) merged.path = msg.worktreePath;
|
|
@@ -19091,6 +19568,8 @@ function _wtGateWorktree(msg, wt) {
|
|
|
19091
19568
|
ahead: msg.ahead || msg.unmergedCommits || 0,
|
|
19092
19569
|
behind: msg.behind || 0,
|
|
19093
19570
|
dirtyFiles: msg.dirtyFiles || 0,
|
|
19571
|
+
trackedDirtyFiles: msg.trackedDirtyFiles || 0,
|
|
19572
|
+
untrackedFiles: msg.untrackedFiles || 0,
|
|
19094
19573
|
unmergedCommits: msg.unmergedCommits || 0,
|
|
19095
19574
|
summary: msg.summary || msg.message || '',
|
|
19096
19575
|
};
|
|
@@ -19101,8 +19580,8 @@ function _wtGateLabel(wt) {
|
|
|
19101
19580
|
}
|
|
19102
19581
|
|
|
19103
19582
|
function _wtGateHint(wt) {
|
|
19104
|
-
if ((wt
|
|
19105
|
-
return 'This worktree has
|
|
19583
|
+
if (_wtTrackedDirty(wt) > 0) {
|
|
19584
|
+
return 'This worktree has tracked dirty files. Commit, stash, or inspect them before merging or opening a PR.';
|
|
19106
19585
|
}
|
|
19107
19586
|
if ((wt.behind || 0) > 0 && (wt.ahead || 0) > 0) {
|
|
19108
19587
|
return 'This branch has work and is behind main. Sync from main first if you want the cleanest finish path.';
|
|
@@ -19126,14 +19605,15 @@ function openWorktreeFinishGate(msg, wt) {
|
|
|
19126
19605
|
_wtClearChildren(metrics);
|
|
19127
19606
|
metrics.appendChild(_wtMetric('state', gateWt.state || 'unknown', 'neutral'));
|
|
19128
19607
|
metrics.appendChild(_wtMetric('vs main', '+' + (gateWt.ahead || 0) + ' / -' + (gateWt.behind || 0), (gateWt.ahead || gateWt.behind) ? 'warn' : 'good'));
|
|
19129
|
-
metrics.appendChild(_wtMetric('dirty', String(gateWt
|
|
19608
|
+
metrics.appendChild(_wtMetric('dirty', String(_wtTrackedDirty(gateWt)), _wtTrackedDirty(gateWt) ? 'bad' : 'good'));
|
|
19609
|
+
if ((gateWt.untrackedFiles || 0) > 0) metrics.appendChild(_wtMetric('untracked', String(gateWt.untrackedFiles || 0), 'neutral'));
|
|
19130
19610
|
metrics.appendChild(_wtMetric('unmerged', String(gateWt.unmergedCommits || 0), gateWt.unmergedCommits ? 'warn' : 'good'));
|
|
19131
19611
|
}
|
|
19132
19612
|
|
|
19133
19613
|
var hint = document.getElementById('wt-finish-hint');
|
|
19134
19614
|
if (hint) hint.textContent = _wtGateHint(gateWt);
|
|
19135
19615
|
|
|
19136
|
-
var hasDirty = (gateWt
|
|
19616
|
+
var hasDirty = _wtTrackedDirty(gateWt) > 0;
|
|
19137
19617
|
var hasCommittedWork = (gateWt.unmergedCommits || 0) > 0 || (gateWt.ahead || 0) > 0;
|
|
19138
19618
|
var canFinish = !hasDirty && hasCommittedWork && gateWt.branch && gateWt.branch !== 'HEAD';
|
|
19139
19619
|
var mergeBtn = document.getElementById('wt-finish-merge-btn');
|
|
@@ -19274,7 +19754,7 @@ function _wtMergeBlockReason(wt) {
|
|
|
19274
19754
|
wt = wt || {};
|
|
19275
19755
|
if (!wt.branch || wt.branch === 'HEAD') return 'Recover this worktree onto a branch before merging.';
|
|
19276
19756
|
if (wt.sessionId) return 'Close the active session before merging: ' + (wt.sessionLabel || wt.branch) + '.';
|
|
19277
|
-
if ((wt
|
|
19757
|
+
if (_wtTrackedDirty(wt) > 0) return 'Commit or stash tracked dirty files before merging.';
|
|
19278
19758
|
if ((wt.behind || 0) > 0) return 'Sync from main before merging.';
|
|
19279
19759
|
if ((wt.unmergedCommits || 0) === 0 && (wt.ahead || 0) === 0) return 'Nothing to merge — this branch is already integrated with main.';
|
|
19280
19760
|
return '';
|
|
@@ -22043,7 +22523,7 @@ async function onSessionsList(msg) {
|
|
|
22043
22523
|
if (s._outputIdleTimer) clearTimeout(s._outputIdleTimer);
|
|
22044
22524
|
if (s._snapshotRetryTimer) clearTimeout(s._snapshotRetryTimer);
|
|
22045
22525
|
if (s._blankCheckTimer) clearTimeout(s._blankCheckTimer);
|
|
22046
|
-
|
|
22526
|
+
_disposeTerminalRenderer(s);
|
|
22047
22527
|
try { if (s.term) s.term.dispose(); } catch {}
|
|
22048
22528
|
try { s.container.remove(); } catch {}
|
|
22049
22529
|
state.sessions.delete(id);
|
|
@@ -22414,14 +22894,17 @@ function worktreeAttentionBadge(s) {
|
|
|
22414
22894
|
const branch = wt.branch || s?.meta?.branch || '';
|
|
22415
22895
|
if (!branch || branch === 'main' || branch === 'master') return '';
|
|
22416
22896
|
const dirtyFiles = Number(wt.dirtyFiles || 0);
|
|
22897
|
+
const trackedDirtyFiles = wt.trackedDirtyFiles != null
|
|
22898
|
+
? Number(wt.trackedDirtyFiles || 0)
|
|
22899
|
+
: Math.max(0, dirtyFiles - Number(wt.untrackedFiles || 0));
|
|
22417
22900
|
const unmergedCommits = Number(wt.unmergedCommits || 0);
|
|
22418
|
-
if (
|
|
22901
|
+
if (trackedDirtyFiles <= 0 && unmergedCommits <= 0) return '';
|
|
22419
22902
|
|
|
22420
22903
|
const parts = [];
|
|
22421
22904
|
const titleParts = [];
|
|
22422
|
-
if (
|
|
22423
|
-
parts.push(`<span class="wt-part" aria-hidden="true">✎${
|
|
22424
|
-
titleParts.push(`${
|
|
22905
|
+
if (trackedDirtyFiles > 0) {
|
|
22906
|
+
parts.push(`<span class="wt-part" aria-hidden="true">✎${trackedDirtyFiles}</span>`);
|
|
22907
|
+
titleParts.push(`${trackedDirtyFiles} tracked dirty file${trackedDirtyFiles === 1 ? '' : 's'}`);
|
|
22425
22908
|
}
|
|
22426
22909
|
if (unmergedCommits > 0) {
|
|
22427
22910
|
parts.push(`<span class="wt-part" aria-hidden="true">↑${unmergedCommits}</span>`);
|
|
@@ -24235,8 +24718,8 @@ async function _maybeRecommendWorktreeForNewSession(force) {
|
|
|
24235
24718
|
var repoRoot = _nsNormalizeCwdForCompare(d.cwd || '');
|
|
24236
24719
|
var mainWt = (d.worktrees || []).find(function(wt) { return wt.isMain; });
|
|
24237
24720
|
var isPrimaryRepo = repoRoot && cwd === repoRoot;
|
|
24238
|
-
if (!reason && isPrimaryRepo && mainWt && (mainWt
|
|
24239
|
-
reason = 'Recommended: main has uncommitted files';
|
|
24721
|
+
if (!reason && isPrimaryRepo && mainWt && _wtTrackedDirty(mainWt) > 0) {
|
|
24722
|
+
reason = 'Recommended: main has tracked uncommitted files';
|
|
24240
24723
|
}
|
|
24241
24724
|
if (!reason && isPrimaryRepo) {
|
|
24242
24725
|
reason = 'Recommended: code agent in the primary checkout';
|
|
@@ -24455,7 +24938,7 @@ function killSession(id, opts) {
|
|
|
24455
24938
|
if (s._outputIdleTimer) clearTimeout(s._outputIdleTimer);
|
|
24456
24939
|
if (s._snapshotRetryTimer) clearTimeout(s._snapshotRetryTimer);
|
|
24457
24940
|
if (s._blankCheckTimer) clearTimeout(s._blankCheckTimer);
|
|
24458
|
-
|
|
24941
|
+
_disposeTerminalRenderer(s);
|
|
24459
24942
|
if (s.term) s.term.dispose();
|
|
24460
24943
|
s.container.remove();
|
|
24461
24944
|
state.sessions.delete(id);
|
|
@@ -24497,7 +24980,7 @@ function _killTabSilent(id) {
|
|
|
24497
24980
|
if (s._outputIdleTimer) clearTimeout(s._outputIdleTimer);
|
|
24498
24981
|
if (s._snapshotRetryTimer) clearTimeout(s._snapshotRetryTimer);
|
|
24499
24982
|
if (s._blankCheckTimer) clearTimeout(s._blankCheckTimer);
|
|
24500
|
-
|
|
24983
|
+
_disposeTerminalRenderer(s);
|
|
24501
24984
|
if (s.term) s.term.dispose();
|
|
24502
24985
|
s.container.remove();
|
|
24503
24986
|
state.sessions.delete(id);
|
|
@@ -27345,6 +27828,15 @@ function openFolder(folderPath) {
|
|
|
27345
27828
|
});
|
|
27346
27829
|
}
|
|
27347
27830
|
|
|
27831
|
+
function linkReviewDocumentReferences(root, sessionId) {
|
|
27832
|
+
if (!root || !window.CTMDocLinks || typeof window.CTMDocLinks.linkifyElement !== 'function') return;
|
|
27833
|
+
try {
|
|
27834
|
+
window.CTMDocLinks.linkifyElement(root, window.CTMDocLinks.contextForSession(sessionId));
|
|
27835
|
+
} catch (e) {
|
|
27836
|
+
console.warn('[review] document linkification failed:', e && e.message ? e.message : e);
|
|
27837
|
+
}
|
|
27838
|
+
}
|
|
27839
|
+
|
|
27348
27840
|
async function reviewSession(sessionId, projectEntry, title, sessionData, opts) {
|
|
27349
27841
|
const explicitAgentType = _clientNormalizeAgentType(sessionData?.agent || sessionData?.agentType);
|
|
27350
27842
|
if (explicitAgentType && !_clientAgentCaps(explicitAgentType).structuredTranscript) {
|
|
@@ -27456,6 +27948,7 @@ async function reviewSession(sessionId, projectEntry, title, sessionData, opts)
|
|
|
27456
27948
|
// internally via formatMsgText); sanitize the wrapper before insertion.
|
|
27457
27949
|
const firstHtml = turns.slice(0, FIRST_BATCH).map(renderReviewTurn).join('');
|
|
27458
27950
|
container.innerHTML = DOMPurify.sanitize(firstHtml, { ADD_ATTR: ['style', 'data-idx', 'data-role', 'data-turn-id', 'data-msg-idx', 'data-parent-uuid', 'role', 'tabindex', 'aria-expanded'] });
|
|
27951
|
+
linkReviewDocumentReferences(container, sessionId);
|
|
27459
27952
|
_updateReviewLoadOlderBar(container, fetchId, projectEntry, page);
|
|
27460
27953
|
|
|
27461
27954
|
// Show partial load indicator if backend couldn't load all files
|
|
@@ -27485,6 +27978,7 @@ async function reviewSession(sessionId, projectEntry, title, sessionData, opts)
|
|
|
27485
27978
|
const end = Math.min(offset + CHUNK_SIZE, turns.length);
|
|
27486
27979
|
const chunkHtml = turns.slice(offset, end).map(renderReviewTurn).join('');
|
|
27487
27980
|
container.insertAdjacentHTML('beforeend', DOMPurify.sanitize(chunkHtml, { ADD_ATTR: ['style', 'data-idx', 'data-role', 'data-turn-id', 'data-msg-idx', 'data-parent-uuid', 'role', 'tabindex', 'aria-expanded'] }));
|
|
27981
|
+
linkReviewDocumentReferences(container, sessionId);
|
|
27488
27982
|
offset = end;
|
|
27489
27983
|
requestAnimationFrame(renderNextChunk);
|
|
27490
27984
|
};
|
|
@@ -31098,7 +31592,7 @@ function _parseHashRoute() {
|
|
|
31098
31592
|
const params = {};
|
|
31099
31593
|
for (const part of (isNav ? parts.slice(1) : parts)) {
|
|
31100
31594
|
const eq = part.indexOf('=');
|
|
31101
|
-
if (eq > 0) params[part.slice(0, eq)] = decodeURIComponent(part.slice(eq + 1));
|
|
31595
|
+
if (eq > 0) params[part.slice(0, eq)] = decodeURIComponent(part.slice(eq + 1).replace(/\+/g, '%20'));
|
|
31102
31596
|
}
|
|
31103
31597
|
return { hash, firstPart, isNav, params };
|
|
31104
31598
|
}
|
|
@@ -31189,13 +31683,14 @@ function handleHashRoute() {
|
|
|
31189
31683
|
return;
|
|
31190
31684
|
}
|
|
31191
31685
|
|
|
31192
|
-
// #review&type=doc&path=<
|
|
31686
|
+
// #review&type=doc&path=<path>&line=<line>&cwd=<session cwd> — open document review workspace.
|
|
31193
31687
|
if (firstPart === 'review' && params.type === 'doc' && params.path) {
|
|
31194
|
-
navTo('codereview', { skipHash: true });
|
|
31688
|
+
navTo('codereview', { skipHash: true, suppressProjectList: true });
|
|
31689
|
+
const docOpts = { cwd: params.cwd || '', sessionId: params.session || '' };
|
|
31195
31690
|
if (window.CR && typeof CR.openDocumentReview === 'function') {
|
|
31196
|
-
setTimeout(() => CR.openDocumentReview(params.path, params.line || 1), 100);
|
|
31691
|
+
setTimeout(() => CR.openDocumentReview(params.path, params.line || 1, docOpts), 100);
|
|
31197
31692
|
} else {
|
|
31198
|
-
state.pendingDocumentReview = { path: params.path, line: params.line || 1 };
|
|
31693
|
+
state.pendingDocumentReview = { path: params.path, line: params.line || 1, cwd: docOpts.cwd, sessionId: docOpts.sessionId };
|
|
31199
31694
|
}
|
|
31200
31695
|
return;
|
|
31201
31696
|
}
|