instar 0.19.4 → 0.19.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.
Files changed (48) hide show
  1. package/README.md +3 -3
  2. package/dashboard/index.html +400 -3
  3. package/dist/commands/server.d.ts.map +1 -1
  4. package/dist/commands/server.js +49 -9
  5. package/dist/commands/server.js.map +1 -1
  6. package/dist/commands/setup.d.ts.map +1 -1
  7. package/dist/commands/setup.js +8 -4
  8. package/dist/commands/setup.js.map +1 -1
  9. package/dist/core/AutoUpdater.d.ts.map +1 -1
  10. package/dist/core/AutoUpdater.js +12 -0
  11. package/dist/core/AutoUpdater.js.map +1 -1
  12. package/dist/core/GlobalInstallCleanup.d.ts +23 -0
  13. package/dist/core/GlobalInstallCleanup.d.ts.map +1 -0
  14. package/dist/core/GlobalInstallCleanup.js +130 -0
  15. package/dist/core/GlobalInstallCleanup.js.map +1 -0
  16. package/dist/core/SessionManager.d.ts +6 -0
  17. package/dist/core/SessionManager.d.ts.map +1 -1
  18. package/dist/core/SessionManager.js +20 -0
  19. package/dist/core/SessionManager.js.map +1 -1
  20. package/dist/core/UpdateChecker.js +3 -3
  21. package/dist/core/UpdateChecker.js.map +1 -1
  22. package/dist/paste/PasteManager.d.ts +154 -0
  23. package/dist/paste/PasteManager.d.ts.map +1 -0
  24. package/dist/paste/PasteManager.js +524 -0
  25. package/dist/paste/PasteManager.js.map +1 -0
  26. package/dist/paste/TruncationDetector.d.ts +51 -0
  27. package/dist/paste/TruncationDetector.d.ts.map +1 -0
  28. package/dist/paste/TruncationDetector.js +220 -0
  29. package/dist/paste/TruncationDetector.js.map +1 -0
  30. package/dist/server/AgentServer.d.ts +2 -0
  31. package/dist/server/AgentServer.d.ts.map +1 -1
  32. package/dist/server/AgentServer.js +12 -3
  33. package/dist/server/AgentServer.js.map +1 -1
  34. package/dist/server/WebSocketManager.d.ts +5 -0
  35. package/dist/server/WebSocketManager.d.ts.map +1 -1
  36. package/dist/server/WebSocketManager.js +14 -0
  37. package/dist/server/WebSocketManager.js.map +1 -1
  38. package/dist/server/routes.d.ts +4 -0
  39. package/dist/server/routes.d.ts.map +1 -1
  40. package/dist/server/routes.js +158 -0
  41. package/dist/server/routes.js.map +1 -1
  42. package/dist/threadline/relay/RegistryStore.d.ts.map +1 -1
  43. package/dist/threadline/relay/RegistryStore.js +4 -2
  44. package/dist/threadline/relay/RegistryStore.js.map +1 -1
  45. package/package.json +3 -3
  46. package/src/data/builtin-manifest.json +48 -48
  47. package/upgrades/0.19.6.md +17 -0
  48. package/upgrades/0.19.7.md +33 -0
package/README.md CHANGED
@@ -11,14 +11,14 @@
11
11
  <p align="center">
12
12
  <a href="https://www.npmjs.com/package/instar"><img src="https://img.shields.io/npm/v/instar?style=flat-square" alt="npm version"></a>
13
13
  <a href="https://www.npmjs.com/package/instar"><img src="https://img.shields.io/npm/dw/instar?style=flat-square" alt="npm downloads"></a>
14
- <a href="https://github.com/SageMindAI/instar/actions/workflows/ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/SageMindAI/instar/ci.yml?branch=main&style=flat-square&label=CI" alt="CI"></a>
15
- <a href="https://github.com/SageMindAI/instar/blob/main/LICENSE"><img src="https://img.shields.io/badge/License-MIT-green?style=flat-square" alt="License"></a>
14
+ <a href="https://github.com/JKHeadley/instar/actions/workflows/ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/JKHeadley/instar/ci.yml?branch=main&style=flat-square&label=CI" alt="CI"></a>
15
+ <a href="https://github.com/JKHeadley/instar/blob/main/LICENSE"><img src="https://img.shields.io/badge/License-MIT-green?style=flat-square" alt="License"></a>
16
16
  <img src="https://img.shields.io/badge/TypeScript-100%25-blue?style=flat-square&logo=typescript&logoColor=white" alt="TypeScript">
17
17
  <a href="https://instar.sh/introduction/"><img src="https://img.shields.io/badge/Docs-instar.sh-teal?style=flat-square" alt="Docs"></a>
18
18
  </p>
19
19
 
20
20
  <p align="center">
21
- <a href="https://www.npmjs.com/package/instar">npm</a> · <a href="https://github.com/SageMindAI/instar">GitHub</a> · <a href="https://instar.sh">instar.sh</a> · <a href="https://instar.sh/introduction/">Docs</a>
21
+ <a href="https://www.npmjs.com/package/instar">npm</a> · <a href="https://github.com/JKHeadley/instar">GitHub</a> · <a href="https://instar.sh">instar.sh</a> · <a href="https://instar.sh/introduction/">Docs</a>
22
22
  </p>
23
23
 
24
24
  ---
@@ -246,6 +246,190 @@
246
246
  .empty-state .icon { font-size: 32px; margin-bottom: 12px; opacity: 0.5; }
247
247
  .empty-state p { font-size: 13px; line-height: 1.5; }
248
248
 
249
+ /* Drop Zone */
250
+ .dropzone-container {
251
+ display: flex;
252
+ justify-content: center;
253
+ padding: 24px;
254
+ overflow-y: auto;
255
+ height: calc(100vh - 56px);
256
+ }
257
+ .dropzone-panel {
258
+ width: 100%;
259
+ max-width: 720px;
260
+ }
261
+ .dropzone-header h2 {
262
+ color: var(--text-bright);
263
+ font-size: 20px;
264
+ margin-bottom: 4px;
265
+ }
266
+ .dropzone-subtitle {
267
+ color: var(--text-dim);
268
+ font-size: 13px;
269
+ margin-bottom: 20px;
270
+ }
271
+ .dropzone-form {
272
+ display: flex;
273
+ flex-direction: column;
274
+ gap: 12px;
275
+ }
276
+ .dropzone-label {
277
+ background: var(--surface);
278
+ border: 1px solid var(--border);
279
+ border-radius: 6px;
280
+ padding: 10px 12px;
281
+ color: var(--text);
282
+ font-size: 14px;
283
+ outline: none;
284
+ }
285
+ .dropzone-label:focus { border-color: var(--accent); }
286
+ .dropzone-textarea-wrap {
287
+ position: relative;
288
+ }
289
+ .dropzone-textarea {
290
+ width: 100%;
291
+ min-height: 200px;
292
+ max-height: 60vh;
293
+ background: var(--surface);
294
+ border: 1px solid var(--border);
295
+ border-radius: 6px;
296
+ padding: 12px;
297
+ color: var(--text);
298
+ font-family: 'SF Mono', 'Fira Code', 'Consolas', monospace;
299
+ font-size: 13px;
300
+ line-height: 1.5;
301
+ resize: vertical;
302
+ outline: none;
303
+ box-sizing: border-box;
304
+ }
305
+ .dropzone-textarea:focus { border-color: var(--accent); }
306
+ .dropzone-count {
307
+ position: absolute;
308
+ bottom: 8px;
309
+ right: 12px;
310
+ font-size: 11px;
311
+ color: var(--text-dim);
312
+ pointer-events: none;
313
+ }
314
+ .dropzone-actions {
315
+ display: flex;
316
+ gap: 10px;
317
+ align-items: center;
318
+ }
319
+ .dropzone-session-select {
320
+ flex: 1;
321
+ background: var(--surface);
322
+ border: 1px solid var(--border);
323
+ border-radius: 6px;
324
+ padding: 10px 12px;
325
+ color: var(--text);
326
+ font-size: 13px;
327
+ outline: none;
328
+ }
329
+ .dropzone-send-btn {
330
+ background: var(--accent);
331
+ color: #fff;
332
+ border: none;
333
+ border-radius: 6px;
334
+ padding: 10px 24px;
335
+ font-size: 14px;
336
+ font-weight: 600;
337
+ cursor: pointer;
338
+ transition: background 0.2s;
339
+ }
340
+ .dropzone-send-btn:hover { background: #4fa0d8; }
341
+ .dropzone-send-btn:disabled { background: #555; cursor: not-allowed; }
342
+ .dropzone-disclosure {
343
+ font-size: 11px;
344
+ color: var(--text-dim);
345
+ font-style: italic;
346
+ }
347
+ .dropzone-status {
348
+ margin-top: 12px;
349
+ padding: 10px 14px;
350
+ border-radius: 6px;
351
+ font-size: 13px;
352
+ }
353
+ .dropzone-status.success {
354
+ background: rgba(46, 160, 67, 0.15);
355
+ color: #3fb950;
356
+ border: 1px solid rgba(46, 160, 67, 0.3);
357
+ }
358
+ .dropzone-status.error {
359
+ background: rgba(248, 81, 73, 0.15);
360
+ color: #f85149;
361
+ border: 1px solid rgba(248, 81, 73, 0.3);
362
+ }
363
+ .dropzone-status.queued {
364
+ background: rgba(210, 153, 34, 0.15);
365
+ color: #d29922;
366
+ border: 1px solid rgba(210, 153, 34, 0.3);
367
+ }
368
+ .dropzone-history {
369
+ margin-top: 28px;
370
+ }
371
+ .dropzone-history h3 {
372
+ color: var(--text-bright);
373
+ font-size: 15px;
374
+ margin-bottom: 12px;
375
+ }
376
+ .dropzone-history-list {
377
+ display: flex;
378
+ flex-direction: column;
379
+ gap: 8px;
380
+ }
381
+ .dropzone-empty {
382
+ color: var(--text-dim);
383
+ font-size: 13px;
384
+ font-style: italic;
385
+ }
386
+ .dz-paste-item {
387
+ display: flex;
388
+ align-items: center;
389
+ justify-content: space-between;
390
+ background: var(--surface);
391
+ border: 1px solid var(--border);
392
+ border-radius: 6px;
393
+ padding: 10px 14px;
394
+ }
395
+ .dz-paste-info {
396
+ display: flex;
397
+ flex-direction: column;
398
+ gap: 2px;
399
+ }
400
+ .dz-paste-label {
401
+ color: var(--text);
402
+ font-size: 13px;
403
+ font-weight: 500;
404
+ }
405
+ .dz-paste-meta {
406
+ color: var(--text-dim);
407
+ font-size: 11px;
408
+ }
409
+ .dz-paste-status {
410
+ font-size: 11px;
411
+ padding: 2px 8px;
412
+ border-radius: 4px;
413
+ font-weight: 500;
414
+ }
415
+ .dz-paste-status.written { background: rgba(210, 153, 34, 0.2); color: #d29922; }
416
+ .dz-paste-status.notified { background: rgba(46, 160, 67, 0.2); color: #3fb950; }
417
+ .dz-paste-status.acknowledged { background: rgba(56, 132, 255, 0.2); color: #58a6ff; }
418
+ .dz-paste-delete {
419
+ background: none;
420
+ border: none;
421
+ color: var(--text-dim);
422
+ cursor: pointer;
423
+ font-size: 14px;
424
+ padding: 4px 8px;
425
+ }
426
+ .dz-paste-delete:hover { color: #f85149; }
427
+ @media (max-width: 768px) {
428
+ .dropzone-container { padding: 16px; }
429
+ .dropzone-actions { flex-direction: column; }
430
+ .dropzone-session-select { width: 100%; }
431
+ }
432
+
249
433
  /* WhatsApp QR panel */
250
434
  .wa-qr-panel {
251
435
  position: fixed;
@@ -1295,6 +1479,7 @@
1295
1479
  <nav class="tab-bar">
1296
1480
  <button class="tab active" data-tab="sessions" onclick="switchTab('sessions')">Sessions <span class="tab-count" id="tabSessionCount">0</span></button>
1297
1481
  <button class="tab" data-tab="files" onclick="switchTab('files')">Files</button>
1482
+ <button class="tab" data-tab="dropzone" onclick="switchTab('dropzone')">Drop Zone</button>
1298
1483
  </nav>
1299
1484
  </div>
1300
1485
  <button class="wa-status-btn" id="waStatusBtn" onclick="toggleQrPanel()">WhatsApp</button>
@@ -1396,6 +1581,45 @@
1396
1581
  </div>
1397
1582
  </div>
1398
1583
 
1584
+ <!-- Drop Zone tab -->
1585
+ <div class="dropzone-container" id="dropzoneTab" style="display:none">
1586
+ <div class="dropzone-panel">
1587
+ <div class="dropzone-header">
1588
+ <h2>Drop Zone</h2>
1589
+ <p class="dropzone-subtitle">Send large text content to your agent session</p>
1590
+ </div>
1591
+
1592
+ <div class="dropzone-form">
1593
+ <input type="text" class="dropzone-label" id="dzLabel"
1594
+ placeholder="What is this? (optional)" maxlength="256">
1595
+
1596
+ <div class="dropzone-textarea-wrap">
1597
+ <textarea class="dropzone-textarea" id="dzContent"
1598
+ placeholder="Paste your content here..."
1599
+ oninput="updateDzCount()"></textarea>
1600
+ <div class="dropzone-count" id="dzCount">0 chars &middot; ~0 tokens</div>
1601
+ </div>
1602
+
1603
+ <div class="dropzone-actions">
1604
+ <select class="dropzone-session-select" id="dzSession">
1605
+ <option value="">Auto (most recent session)</option>
1606
+ </select>
1607
+ <button class="dropzone-send-btn" id="dzSendBtn" onclick="sendPaste()">Send</button>
1608
+ </div>
1609
+ <div class="dropzone-disclosure">Stored locally for up to 7 days. Avoid pasting secrets or credentials.</div>
1610
+ </div>
1611
+
1612
+ <div class="dropzone-status" id="dzStatus" style="display:none"></div>
1613
+
1614
+ <div class="dropzone-history">
1615
+ <h3>Recent Pastes</h3>
1616
+ <div class="dropzone-history-list" id="dzHistoryList">
1617
+ <div class="dropzone-empty">No pastes yet</div>
1618
+ </div>
1619
+ </div>
1620
+ </div>
1621
+ </div>
1622
+
1399
1623
  <!-- WhatsApp QR panel (hidden by default) -->
1400
1624
  <div class="wa-qr-backdrop" id="waQrBackdrop" style="display:none" onclick="closeQrPanel()"></div>
1401
1625
  <div class="wa-qr-panel" id="waQrPanel" style="display:none">
@@ -2011,17 +2235,27 @@
2011
2235
  const sessionsTab = document.getElementById('sessionsTab');
2012
2236
  const mainPanel = document.getElementById('mainPanel');
2013
2237
  const filesTab = document.getElementById('filesTab');
2238
+ const dropzoneTab = document.getElementById('dropzoneTab');
2239
+
2240
+ sessionsTab.style.display = 'none';
2241
+ mainPanel.style.display = 'none';
2242
+ filesTab.style.display = 'none';
2243
+ dropzoneTab.style.display = 'none';
2014
2244
 
2015
2245
  if (tabName === 'sessions') {
2016
2246
  sessionsTab.style.display = '';
2017
2247
  mainPanel.style.display = '';
2018
- filesTab.style.display = 'none';
2019
2248
  } else if (tabName === 'files') {
2020
- sessionsTab.style.display = 'none';
2021
- mainPanel.style.display = 'none';
2022
2249
  filesTab.style.display = 'flex';
2023
2250
  // Load file tree on first switch
2024
2251
  if (!fileTreeLoaded) loadFileTree();
2252
+ } else if (tabName === 'dropzone') {
2253
+ dropzoneTab.style.display = 'flex';
2254
+ // Auto-focus textarea
2255
+ setTimeout(() => document.getElementById('dzContent')?.focus(), 100);
2256
+ // Load session list and paste history
2257
+ loadDzSessions();
2258
+ loadDzHistory();
2025
2259
  }
2026
2260
 
2027
2261
  // Update URL
@@ -2759,6 +2993,169 @@
2759
2993
  deepLinkObserver.observe(document.getElementById('authOverlay'), {
2760
2994
  attributes: true, attributeFilter: ['style'],
2761
2995
  });
2996
+
2997
+ // ── Drop Zone ──────────────────────────────────────────────
2998
+
2999
+ function updateDzCount() {
3000
+ const content = document.getElementById('dzContent').value;
3001
+ const chars = content.length;
3002
+ // Rough token estimate: ~4 chars per token for English
3003
+ const tokens = Math.round(chars / 4);
3004
+ document.getElementById('dzCount').textContent =
3005
+ `${chars.toLocaleString()} chars \u00b7 ~${tokens.toLocaleString()} tokens`;
3006
+ }
3007
+
3008
+ async function loadDzSessions() {
3009
+ const select = document.getElementById('dzSession');
3010
+ // Keep the first "Auto" option, remove the rest
3011
+ while (select.options.length > 1) select.remove(1);
3012
+
3013
+ // Use the cached session list from WebSocket if available
3014
+ if (sessions && sessions.length > 0) {
3015
+ const interactive = sessions.filter(s => !s.jobSlug);
3016
+ for (const s of interactive) {
3017
+ const opt = document.createElement('option');
3018
+ opt.value = s.name;
3019
+ opt.textContent = s.name + (s.model ? ` (${s.model})` : '');
3020
+ select.appendChild(opt);
3021
+ }
3022
+ }
3023
+ }
3024
+
3025
+ async function loadDzHistory() {
3026
+ try {
3027
+ const resp = await fetch('/pastes', {
3028
+ headers: { 'Authorization': 'Bearer ' + token },
3029
+ });
3030
+ if (!resp.ok) return;
3031
+ const data = await resp.json();
3032
+ renderDzHistory(data.pastes || []);
3033
+ } catch {}
3034
+ }
3035
+
3036
+ function renderDzHistory(pastes) {
3037
+ const list = document.getElementById('dzHistoryList');
3038
+ if (!pastes.length) {
3039
+ list.innerHTML = '<div class="dropzone-empty">No pastes yet</div>';
3040
+ return;
3041
+ }
3042
+ list.innerHTML = pastes.slice(0, 20).map(p => {
3043
+ const age = timeAgo(new Date(p.timestamp));
3044
+ const label = p.label || '(unlabeled)';
3045
+ const chars = p.contentLength.toLocaleString();
3046
+ return `
3047
+ <div class="dz-paste-item" data-paste-id="${esc(p.pasteId)}">
3048
+ <div class="dz-paste-info">
3049
+ <span class="dz-paste-label">${esc(label)}</span>
3050
+ <span class="dz-paste-meta">${chars} chars &middot; ${age}</span>
3051
+ </div>
3052
+ <div style="display:flex;align-items:center;gap:8px">
3053
+ <span class="dz-paste-status ${p.status}">${p.status}</span>
3054
+ <button class="dz-paste-delete" onclick="deletePaste('${esc(p.pasteId)}')" title="Delete">&times;</button>
3055
+ </div>
3056
+ </div>`;
3057
+ }).join('');
3058
+ }
3059
+
3060
+ async function sendPaste() {
3061
+ const content = document.getElementById('dzContent').value.trim();
3062
+ if (!content) return;
3063
+
3064
+ const label = document.getElementById('dzLabel').value.trim() || undefined;
3065
+ const targetSession = document.getElementById('dzSession').value || undefined;
3066
+ const btn = document.getElementById('dzSendBtn');
3067
+ const status = document.getElementById('dzStatus');
3068
+
3069
+ btn.disabled = true;
3070
+ btn.textContent = 'Sending...';
3071
+ status.style.display = 'none';
3072
+
3073
+ try {
3074
+ const resp = await fetch('/pastes', {
3075
+ method: 'POST',
3076
+ headers: {
3077
+ 'Authorization': 'Bearer ' + token,
3078
+ 'Content-Type': 'application/json',
3079
+ },
3080
+ body: JSON.stringify({ content, label, targetSession }),
3081
+ });
3082
+
3083
+ const data = await resp.json();
3084
+
3085
+ if (resp.ok && data.ok) {
3086
+ const chars = data.contentLength?.toLocaleString() || content.length.toLocaleString();
3087
+ if (data.status === 'notified') {
3088
+ status.className = 'dropzone-status success';
3089
+ status.textContent = `Sent ${chars} chars to session "${data.sessionName}"`;
3090
+ } else {
3091
+ status.className = 'dropzone-status queued';
3092
+ status.textContent = `Queued ${chars} chars \u2014 will be delivered when a session starts.`;
3093
+ }
3094
+ status.style.display = 'block';
3095
+
3096
+ // Clear form
3097
+ document.getElementById('dzContent').value = '';
3098
+ document.getElementById('dzLabel').value = '';
3099
+ updateDzCount();
3100
+
3101
+ // Refresh history
3102
+ loadDzHistory();
3103
+ } else {
3104
+ status.className = 'dropzone-status error';
3105
+ status.textContent = data.message || data.error || 'Failed to send paste';
3106
+ status.style.display = 'block';
3107
+ }
3108
+ } catch (err) {
3109
+ status.className = 'dropzone-status error';
3110
+ status.textContent = 'Network error: ' + err.message;
3111
+ status.style.display = 'block';
3112
+ }
3113
+
3114
+ btn.disabled = false;
3115
+ btn.textContent = 'Send';
3116
+ }
3117
+
3118
+ async function deletePaste(pasteId) {
3119
+ try {
3120
+ await fetch('/pastes/' + encodeURIComponent(pasteId), {
3121
+ method: 'DELETE',
3122
+ headers: { 'Authorization': 'Bearer ' + token },
3123
+ });
3124
+ loadDzHistory();
3125
+ } catch {}
3126
+ }
3127
+
3128
+ function timeAgo(date) {
3129
+ const seconds = Math.floor((Date.now() - date.getTime()) / 1000);
3130
+ if (seconds < 60) return 'just now';
3131
+ const minutes = Math.floor(seconds / 60);
3132
+ if (minutes < 60) return minutes + 'm ago';
3133
+ const hours = Math.floor(minutes / 60);
3134
+ if (hours < 24) return hours + 'h ago';
3135
+ const days = Math.floor(hours / 24);
3136
+ return days + 'd ago';
3137
+ }
3138
+
3139
+ function esc(s) {
3140
+ const d = document.createElement('div');
3141
+ d.textContent = s;
3142
+ return d.innerHTML;
3143
+ }
3144
+
3145
+ // Listen for WebSocket paste events
3146
+ const origWsOnMessage = ws?.onmessage;
3147
+ if (typeof ws !== 'undefined') {
3148
+ const origOnMessage = ws.onmessage;
3149
+ ws.addEventListener('message', function(event) {
3150
+ try {
3151
+ const msg = JSON.parse(event.data);
3152
+ if (msg.type === 'paste_delivered' || msg.type === 'paste_acknowledged') {
3153
+ loadDzHistory();
3154
+ }
3155
+ } catch {}
3156
+ });
3157
+ }
3158
+
2762
3159
  </script>
2763
3160
  </body>
2764
3161
  </html>
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/commands/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAoPH,UAAU,YAAY;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;2DACuD;IACvD,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAw+BD,wBAAsB,WAAW,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAsvEtE;AAED,wBAAsB,UAAU,CAAC,OAAO,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAsDzE;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,aAAa,CAAC,OAAO,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAuD5E"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/commands/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAqPH,UAAU,YAAY;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;2DACuD;IACvD,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAy+BD,wBAAsB,WAAW,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CA0xEtE;AAED,wBAAsB,UAAU,CAAC,OAAO,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAsDzE;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,aAAa,CAAC,OAAO,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAuD5E"}
@@ -70,6 +70,7 @@ import { LiveConfig } from '../config/LiveConfig.js';
70
70
  import { CoherenceMonitor } from '../monitoring/CoherenceMonitor.js';
71
71
  import { ProcessIntegrity } from '../core/ProcessIntegrity.js';
72
72
  import { StaleProcessGuard } from '../core/StaleProcessGuard.js';
73
+ import { cleanupGlobalInstalls } from '../core/GlobalInstallCleanup.js';
73
74
  import { ForegroundRestartWatcher } from '../core/ForegroundRestartWatcher.js';
74
75
  import { NotificationBatcher } from '../messaging/NotificationBatcher.js';
75
76
  import { MessageStore } from '../messaging/MessageStore.js';
@@ -730,7 +731,12 @@ function wireTelegramRouting(telegram, sessionManager, quotaTracker, topicMemory
730
731
  ? userManager.resolveFromTelegramUserId(telegramUserId)
731
732
  : null;
732
733
  // Most commands are handled inside TelegramAdapter.handleCommand().
733
- // /new is handled here because it needs sessionManager access.
734
+ // /new create a new topic thread. Does NOT spawn a session immediately.
735
+ // Sessions are spawned on-demand when the user sends their first real message
736
+ // in the new topic (via the auto-spawn path below). This avoids premature
737
+ // session exit: spawning with a meta-message ("new session started") gives
738
+ // Claude nothing real to do, so it responds and exits. The user's actual
739
+ // message then arrives to a dead session.
734
740
  const newMatch = text.match(/^\/new(?:\s+(.+))?$/);
735
741
  if (newMatch) {
736
742
  const sessionName = newMatch[1]?.trim() || null;
@@ -740,16 +746,15 @@ function wireTelegramRouting(telegram, sessionManager, quotaTracker, topicMemory
740
746
  (async () => {
741
747
  try {
742
748
  const topic = await telegram.findOrCreateForumTopic(topicDisplayName, TOPIC_STYLE.SESSION.color);
743
- const newSession = await sessionManager.spawnInteractiveSession(`[telegram:${topic.topicId}] New session started. (IMPORTANT: Relay all responses back via: cat <<'EOF' | .claude/scripts/telegram-reply.sh ${topic.topicId}\nYour response\nEOF)`, topicName);
744
- telegram.registerTopicSession(topic.topicId, newSession, topicName);
745
- await telegram.sendToTopic(topic.topicId, `Session created. I'm here.`);
746
- await telegram.sendToTopic(topicId, `New session created: "${topicName}" — check the new topic above.`);
747
- console.log(`[telegram] Spawned session "${newSession}" for new topic ${topic.topicId}`);
749
+ // Don't create a session findOrCreateForumTopic already stored the topic name.
750
+ // The first message in this topic will trigger auto-spawn with real content.
751
+ await telegram.sendToTopic(topic.topicId, `Ready send your first message to start.`);
752
+ await telegram.sendToTopic(topicId, `Created topic "${topicName}" — head over there.`);
753
+ console.log(`[telegram] Created topic "${topicName}" (${topic.topicId}) — session will spawn on first message`);
748
754
  }
749
755
  catch (err) {
750
756
  console.error(`[telegram] /new failed:`, err);
751
- console.error(`[telegram] Session spawn failed:`, err);
752
- await telegram.sendToTopic(topicId, 'Couldn\'t create the new session. Try again in a moment.').catch(() => { });
757
+ await telegram.sendToTopic(topicId, 'Couldn\'t create the topic. Try again in a moment.').catch(() => { });
753
758
  }
754
759
  })();
755
760
  return;
@@ -1218,6 +1223,29 @@ export async function startServer(options) {
1218
1223
  console.warn(pc.red(` rm -rf ${path.join(process.cwd(), 'node_modules')} ${path.join(process.cwd(), 'package.json')} ${path.join(process.cwd(), 'package-lock.json')}`));
1219
1224
  console.warn();
1220
1225
  }
1226
+ // ── Global install cleanup ─────────────────────────────────────────
1227
+ // Shadow installs are the sole source of truth. Global installs cause
1228
+ // version confusion — agents report stale versions when CLI commands
1229
+ // resolve to a global binary instead of the shadow install.
1230
+ // Clean up any lingering globals at startup (idempotent, safe to run every time).
1231
+ try {
1232
+ const cleanup = cleanupGlobalInstalls();
1233
+ if (cleanup.removed.length > 0) {
1234
+ console.log(pc.green(` ✓ Cleaned up ${cleanup.removed.length} stale global instar install(s):`));
1235
+ for (const r of cleanup.removed) {
1236
+ console.log(pc.green(` - ${r}`));
1237
+ }
1238
+ }
1239
+ if (cleanup.failed.length > 0) {
1240
+ for (const f of cleanup.failed) {
1241
+ console.warn(pc.yellow(` ⚠ Failed to remove global install at ${f.path}: ${f.error}`));
1242
+ }
1243
+ }
1244
+ }
1245
+ catch (err) {
1246
+ // Non-fatal — log and continue
1247
+ console.warn(`[server] Global install cleanup error: ${err instanceof Error ? err.message : String(err)}`);
1248
+ }
1221
1249
  // ── ProcessIntegrity: freeze the running version at startup ────────
1222
1250
  // This MUST happen before any version reporting. The version is captured
1223
1251
  // from the code loaded into memory, NOT from disk (which changes after
@@ -2264,6 +2292,18 @@ export async function startServer(options) {
2264
2292
  viewsDir: path.join(config.stateDir, 'views'),
2265
2293
  });
2266
2294
  console.log(pc.green(` Private viewer enabled`));
2295
+ // Set up paste manager (Drop Zone — always enabled)
2296
+ const { PasteManager } = await import('../paste/PasteManager.js');
2297
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- paste config fields are optional extensions
2298
+ const cfgAny = config;
2299
+ const pasteManager = new PasteManager({
2300
+ pasteDir: path.join(config.stateDir, 'paste'),
2301
+ stateDir: path.join(config.stateDir, 'state'),
2302
+ projectDir: config.projectDir,
2303
+ maxSizeBytes: cfgAny.pasteMaxSizeMB ? cfgAny.pasteMaxSizeMB * 1024 * 1024 : undefined,
2304
+ retentionDays: cfgAny.pasteRetentionDays ?? undefined,
2305
+ });
2306
+ console.log(pc.green(` Drop Zone (paste) enabled`));
2267
2307
  // Set up Cloudflare Tunnel — enabled by default (quick tunnel, zero-config)
2268
2308
  // Only disabled if explicitly set to tunnel.enabled = false
2269
2309
  const tunnelEnabled = config.tunnel?.enabled !== false;
@@ -2987,7 +3027,7 @@ export async function startServer(options) {
2987
3027
  console.warn(pc.yellow(` Response review pipeline: configured but ANTHROPIC_API_KEY not set`));
2988
3028
  }
2989
3029
  }
2990
- const server = new AgentServer({ config, sessionManager, state, scheduler, telegram, relationships, feedback, feedbackAnomalyDetector, dispatches, updateChecker, autoUpdater, autoDispatcher, quotaTracker, quotaManager, publisher, viewer, tunnel, evolution, watchdog, topicMemory, triageNurse, projectMapper, coherenceGate: scopeVerifier, contextHierarchy, canonicalState, operationGate, sentinel, adaptiveTrust, memoryMonitor, orphanReaper, coherenceMonitor, commitmentTracker, semanticMemory, activitySentinel, messageRouter, summarySentinel, spawnManager, systemReviewer, capabilityMapper, selfKnowledgeTree, coverageAuditor, topicResumeMap: _topicResumeMap ?? undefined, autonomyManager, trustElevationTracker, autonomousEvolution, coordinator: coordinator.enabled ? coordinator : undefined, localSigningKeyPem, whatsapp: whatsappAdapter, whatsappBusinessBackend, messageBridge, hookEventReceiver, worktreeMonitor, subagentTracker, instructionsVerifier, handshakeManager: threadlineHandshake, threadlineRelayClient, responseReviewGate, telemetryHeartbeat, liveConfig });
3030
+ const server = new AgentServer({ config, sessionManager, state, scheduler, telegram, relationships, feedback, feedbackAnomalyDetector, dispatches, updateChecker, autoUpdater, autoDispatcher, quotaTracker, quotaManager, publisher, viewer, tunnel, evolution, watchdog, topicMemory, triageNurse, projectMapper, coherenceGate: scopeVerifier, contextHierarchy, canonicalState, operationGate, sentinel, adaptiveTrust, memoryMonitor, orphanReaper, coherenceMonitor, commitmentTracker, semanticMemory, activitySentinel, messageRouter, summarySentinel, spawnManager, systemReviewer, capabilityMapper, selfKnowledgeTree, coverageAuditor, topicResumeMap: _topicResumeMap ?? undefined, autonomyManager, trustElevationTracker, autonomousEvolution, coordinator: coordinator.enabled ? coordinator : undefined, localSigningKeyPem, whatsapp: whatsappAdapter, whatsappBusinessBackend, messageBridge, hookEventReceiver, worktreeMonitor, subagentTracker, instructionsVerifier, handshakeManager: threadlineHandshake, threadlineRelayClient, responseReviewGate, telemetryHeartbeat, pasteManager, liveConfig });
2991
3031
  await server.start();
2992
3032
  // Connect DegradationReporter downstream systems now that everything is initialized.
2993
3033
  // Any degradation events queued during startup will drain to feedback + telegram.