instar 1.3.492 → 1.3.494
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/dashboard/index.html +126 -26
- package/dist/commands/server.d.ts.map +1 -1
- package/dist/commands/server.js +3 -0
- package/dist/commands/server.js.map +1 -1
- package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
- package/dist/core/PostUpdateMigrator.js +17 -0
- package/dist/core/PostUpdateMigrator.js.map +1 -1
- package/dist/core/RemoteCloseAudit.d.ts +34 -0
- package/dist/core/RemoteCloseAudit.d.ts.map +1 -0
- package/dist/core/RemoteCloseAudit.js +47 -0
- package/dist/core/RemoteCloseAudit.js.map +1 -0
- package/dist/core/SessionManager.d.ts +4 -0
- package/dist/core/SessionManager.d.ts.map +1 -1
- package/dist/core/SessionManager.js +1 -1
- package/dist/core/SessionManager.js.map +1 -1
- package/dist/messaging/slack/SocketModeClient.d.ts +10 -0
- package/dist/messaging/slack/SocketModeClient.d.ts.map +1 -1
- package/dist/messaging/slack/SocketModeClient.js +77 -49
- package/dist/messaging/slack/SocketModeClient.js.map +1 -1
- package/dist/monitoring/ReapLog.d.ts +6 -0
- package/dist/monitoring/ReapLog.d.ts.map +1 -1
- package/dist/monitoring/ReapLog.js +1 -0
- package/dist/monitoring/ReapLog.js.map +1 -1
- package/dist/scaffold/templates.d.ts.map +1 -1
- package/dist/scaffold/templates.js +1 -0
- package/dist/scaffold/templates.js.map +1 -1
- package/dist/server/CapabilityIndex.js +1 -1
- package/dist/server/CapabilityIndex.js.map +1 -1
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/routes.js +112 -0
- package/dist/server/routes.js.map +1 -1
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +64 -64
- package/src/data/state-coherence-registry.json +12 -0
- package/src/scaffold/templates.ts +1 -0
- package/upgrades/1.3.493.md +33 -0
- package/upgrades/1.3.494.md +27 -0
- package/upgrades/side-effects/remote-session-close.md +85 -0
- package/upgrades/side-effects/socketmode-ws-leak-fix.md +74 -0
package/dashboard/index.html
CHANGED
|
@@ -1381,6 +1381,7 @@
|
|
|
1381
1381
|
.toast.success { background: #166534; border: 1px solid #22c55e; }
|
|
1382
1382
|
.toast.error { background: #7f1d1d; border: 1px solid #ef4444; }
|
|
1383
1383
|
.toast.warning { background: #713f12; border: 1px solid #eab308; }
|
|
1384
|
+
.toast.info { background: #1e3a8a; border: 1px solid #60a5fa; }
|
|
1384
1385
|
|
|
1385
1386
|
@keyframes toastIn {
|
|
1386
1387
|
from { opacity: 0; transform: translateY(8px); }
|
|
@@ -1583,6 +1584,15 @@
|
|
|
1583
1584
|
background: rgba(239, 68, 68, 0.1);
|
|
1584
1585
|
}
|
|
1585
1586
|
|
|
1587
|
+
/* In-flight close (REMOTE-SESSION-CLOSE-SPEC §2.2): a relayed close can
|
|
1588
|
+
take up to ~5s against a dead peer — show pending, not an inert button
|
|
1589
|
+
the user double-clicks into parallel relays. !important outranks the
|
|
1590
|
+
hover/active opacity rules above. */
|
|
1591
|
+
.session-close-btn:disabled {
|
|
1592
|
+
opacity: 0.35 !important;
|
|
1593
|
+
cursor: wait;
|
|
1594
|
+
}
|
|
1595
|
+
|
|
1586
1596
|
.session-name-row {
|
|
1587
1597
|
display: flex;
|
|
1588
1598
|
align-items: center;
|
|
@@ -4023,7 +4033,7 @@
|
|
|
4023
4033
|
el.innerHTML = `
|
|
4024
4034
|
<div class="session-name-row">
|
|
4025
4035
|
<div class="session-name">${escapeHtml(session.platformName || session.name)}</div>
|
|
4026
|
-
|
|
4036
|
+
<button class="session-close-btn" data-tmux="${escapeHtml(session.tmuxSession)}" data-name="${escapeHtml(session.name)}"${session.remote ? ` data-remote="1" data-uuid="${escapeHtml(session.id || '')}" data-machine-id="${escapeHtml(session.machineId || '')}" data-machine-nickname="${escapeHtml(session.machineNickname || '')}"` : ''} data-protected="${session.protected === true ? '1' : session.protected === false ? '0' : ''}"${closesInFlight.has(closeKey(session.remote ? session.machineId : null, session.tmuxSession)) ? ' disabled' : ''} onclick="event.stopPropagation();closeSessionFromBtn(this)" title="Close session">×</button>
|
|
4027
4037
|
</div>
|
|
4028
4038
|
<div class="session-meta">
|
|
4029
4039
|
<span class="type-badge ${sType}">${sType}</span>
|
|
@@ -4421,16 +4431,101 @@
|
|
|
4421
4431
|
}
|
|
4422
4432
|
|
|
4423
4433
|
// ── Close Session ─────────────────────────────────────────
|
|
4424
|
-
|
|
4425
|
-
|
|
4434
|
+
// Remote-session close (REMOTE-SESSION-CLOSE-SPEC §2.2): remote tiles
|
|
4435
|
+
// render the same × as local tiles, relayed via
|
|
4436
|
+
// POST /sessions/:name/remote-close. The confirm is the informed-consent
|
|
4437
|
+
// surface — it names the machine and flags a PROTECTED session (close
|
|
4438
|
+
// still proceeds on confirm; operator-origin semantics, there is no
|
|
4439
|
+
// server-side refusal). A flag-less remote row (pre-feature peer) says
|
|
4440
|
+
// "protection status unknown" rather than silently implying unprotected.
|
|
4441
|
+
const closesInFlight = new Set(); // tile keys with a close pending — survives re-renders
|
|
4442
|
+
|
|
4443
|
+
function closeKey(machineId, tmuxSession) {
|
|
4444
|
+
return (machineId || 'local') + ':' + tmuxSession;
|
|
4445
|
+
}
|
|
4446
|
+
|
|
4447
|
+
function closeSessionFromBtn(btn) {
|
|
4448
|
+
const d = btn.dataset;
|
|
4449
|
+
closeSession(d.tmux, d.name, {
|
|
4450
|
+
remote: d.remote === '1',
|
|
4451
|
+
sessionUuid: d.uuid || undefined,
|
|
4452
|
+
machineId: d.machineId || undefined,
|
|
4453
|
+
machineNickname: d.machineNickname || undefined,
|
|
4454
|
+
protected: d.protected === '1' ? true : d.protected === '0' ? false : undefined,
|
|
4455
|
+
button: btn,
|
|
4456
|
+
});
|
|
4457
|
+
}
|
|
4458
|
+
|
|
4459
|
+
async function closeSession(tmuxSession, sessionName, opts) {
|
|
4460
|
+
opts = opts || {}; // backward-compatible: local callers may pass (tmux, name) only
|
|
4461
|
+
const nickname = opts.machineNickname || opts.machineId || 'another machine';
|
|
4462
|
+
|
|
4463
|
+
let prompt;
|
|
4464
|
+
if (opts.protected === true) {
|
|
4465
|
+
// Informed consent (spec §2.0/§2.2): protected sessions ARE closeable —
|
|
4466
|
+
// the safety is that the operator closes them knowingly. The local ×
|
|
4467
|
+
// gains this warning too (deliberate UX improvement, spec AC#6).
|
|
4468
|
+
prompt = opts.remote
|
|
4469
|
+
? `"${sessionName}" is a PROTECTED session on ${nickname} — close it anyway?`
|
|
4470
|
+
: `"${sessionName}" is a PROTECTED session — close it anyway?`;
|
|
4471
|
+
} else {
|
|
4472
|
+
prompt = opts.remote
|
|
4473
|
+
? `Close session "${sessionName}" on ${nickname}?`
|
|
4474
|
+
: `Close session "${sessionName}"?`;
|
|
4475
|
+
}
|
|
4476
|
+
if (opts.remote && opts.protected === undefined) {
|
|
4477
|
+
// Version skew: a pre-feature peer's records omit the additive
|
|
4478
|
+
// `protected` flag — say so rather than implying unprotected.
|
|
4479
|
+
prompt += ' (protection status unknown — machine needs update)';
|
|
4480
|
+
}
|
|
4481
|
+
if (!confirm(prompt)) return;
|
|
4482
|
+
|
|
4483
|
+
const key = closeKey(opts.remote ? opts.machineId : null, tmuxSession);
|
|
4484
|
+
if (closesInFlight.has(key)) return; // a close for this tile is already in flight
|
|
4485
|
+
closesInFlight.add(key);
|
|
4486
|
+
if (opts.button) opts.button.disabled = true; // pending — no double-click into parallel relays
|
|
4426
4487
|
|
|
4427
4488
|
try {
|
|
4428
|
-
|
|
4429
|
-
|
|
4430
|
-
|
|
4431
|
-
|
|
4489
|
+
let resp;
|
|
4490
|
+
if (opts.remote) {
|
|
4491
|
+
resp = await fetch(`/sessions/${encodeURIComponent(sessionName)}/remote-close`, {
|
|
4492
|
+
method: 'POST',
|
|
4493
|
+
headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' },
|
|
4494
|
+
body: JSON.stringify({ machineId: opts.machineId, sessionUuid: opts.sessionUuid }),
|
|
4495
|
+
});
|
|
4496
|
+
} else {
|
|
4497
|
+
resp = await fetch(`/sessions/${encodeURIComponent(tmuxSession)}`, {
|
|
4498
|
+
method: 'DELETE',
|
|
4499
|
+
headers: { 'Authorization': `Bearer ${token}` },
|
|
4500
|
+
});
|
|
4501
|
+
}
|
|
4432
4502
|
const data = await resp.json();
|
|
4433
4503
|
|
|
4504
|
+
if (opts.remote) {
|
|
4505
|
+
if (resp.ok && data.alreadyClosed) {
|
|
4506
|
+
// Calm path (spec §2.3): a stale tile whose session already ended
|
|
4507
|
+
// is a close that just won, not an error — simultaneous dashboard
|
|
4508
|
+
// viewers are normal.
|
|
4509
|
+
showToast(`Session "${sessionName}" already closed — refreshing`, 'success');
|
|
4510
|
+
} else if (resp.ok) {
|
|
4511
|
+
showToast(`Session "${sessionName}" closed on ${nickname}`, 'success');
|
|
4512
|
+
} else if (data.outcomeUnknown) {
|
|
4513
|
+
// Delivery honesty (spec §2.3): the peer may be mid-kill — report
|
|
4514
|
+
// OBSERVED state via a refresh, never "closed nothing".
|
|
4515
|
+
showToast(`Session "${sessionName}" — outcome unknown — refreshing`, 'info');
|
|
4516
|
+
refreshPoolSessions();
|
|
4517
|
+
return;
|
|
4518
|
+
} else {
|
|
4519
|
+
showToast(data.error || 'Failed to close remote session', 'error');
|
|
4520
|
+
return;
|
|
4521
|
+
}
|
|
4522
|
+
refreshPoolSessions();
|
|
4523
|
+
if (activeSession === tmuxSession && activeMachineId === (opts.machineId || null)) {
|
|
4524
|
+
goBack();
|
|
4525
|
+
}
|
|
4526
|
+
return;
|
|
4527
|
+
}
|
|
4528
|
+
|
|
4434
4529
|
if (!resp.ok) {
|
|
4435
4530
|
showToast(data.error || 'Failed to close session', 'error');
|
|
4436
4531
|
return;
|
|
@@ -4444,6 +4539,9 @@
|
|
|
4444
4539
|
}
|
|
4445
4540
|
} catch (err) {
|
|
4446
4541
|
showToast('Network error: ' + (err.message || 'Cannot reach server'), 'error');
|
|
4542
|
+
} finally {
|
|
4543
|
+
closesInFlight.delete(key);
|
|
4544
|
+
if (opts.button && opts.button.isConnected) opts.button.disabled = false;
|
|
4447
4545
|
}
|
|
4448
4546
|
}
|
|
4449
4547
|
|
|
@@ -4451,7 +4549,7 @@
|
|
|
4451
4549
|
if (!activeSession) return;
|
|
4452
4550
|
const session = sessions.find(s => s.tmuxSession === activeSession);
|
|
4453
4551
|
const name = session ? session.name : activeSession;
|
|
4454
|
-
closeSession(activeSession, name);
|
|
4552
|
+
closeSession(activeSession, name, session ? { protected: session.protected } : undefined);
|
|
4455
4553
|
}
|
|
4456
4554
|
|
|
4457
4555
|
// ── Helpers ───────────────────────────────────────────────
|
|
@@ -4485,26 +4583,28 @@
|
|
|
4485
4583
|
// multi-machine pool, poll the aggregated view so every session — on every
|
|
4486
4584
|
// machine — shows in the list, each tagged with the machine it runs on.
|
|
4487
4585
|
let poolPollTimer = null;
|
|
4586
|
+
// Callable outside the poll loop too: a remote close triggers an immediate
|
|
4587
|
+
// refresh so the list shows OBSERVED state (REMOTE-SESSION-CLOSE-SPEC §2.2).
|
|
4588
|
+
async function refreshPoolSessions() {
|
|
4589
|
+
try {
|
|
4590
|
+
const r = await fetch('/sessions?scope=pool', { headers: { 'Authorization': `Bearer ${token}` } });
|
|
4591
|
+
if (!r.ok) return;
|
|
4592
|
+
const j = await r.json();
|
|
4593
|
+
selfMachineNickname = (j.pool && j.pool.selfMachineNickname) || null;
|
|
4594
|
+
// LIVE remote sessions only: a peer's plain /sessions returns its FULL
|
|
4595
|
+
// registry (completed/killed records included), while the local sidebar
|
|
4596
|
+
// is built from listRunningSessions(). Without this status filter,
|
|
4597
|
+
// long-dead peer sessions render as live "click to stream" tiles
|
|
4598
|
+
// (2026-06-11: five closed Mac Mini sessions reappeared on the laptop
|
|
4599
|
+
// dashboard hours after they were closed).
|
|
4600
|
+
remoteSessions = (j.sessions || []).filter(s => s.remote === true && (s.status === 'running' || s.status === 'starting'));
|
|
4601
|
+
renderSessionList();
|
|
4602
|
+
} catch { /* best-effort — peers may be offline; next tick retries */ }
|
|
4603
|
+
}
|
|
4488
4604
|
function startPoolSessionsPolling() {
|
|
4489
4605
|
if (poolPollTimer) return;
|
|
4490
|
-
|
|
4491
|
-
|
|
4492
|
-
const r = await fetch('/sessions?scope=pool', { headers: { 'Authorization': `Bearer ${token}` } });
|
|
4493
|
-
if (!r.ok) return;
|
|
4494
|
-
const j = await r.json();
|
|
4495
|
-
selfMachineNickname = (j.pool && j.pool.selfMachineNickname) || null;
|
|
4496
|
-
// LIVE remote sessions only: a peer's plain /sessions returns its FULL
|
|
4497
|
-
// registry (completed/killed records included), while the local sidebar
|
|
4498
|
-
// is built from listRunningSessions(). Without this status filter,
|
|
4499
|
-
// long-dead peer sessions render as live "click to stream" tiles
|
|
4500
|
-
// (2026-06-11: five closed Mac Mini sessions reappeared on the laptop
|
|
4501
|
-
// dashboard hours after they were closed).
|
|
4502
|
-
remoteSessions = (j.sessions || []).filter(s => s.remote === true && (s.status === 'running' || s.status === 'starting'));
|
|
4503
|
-
renderSessionList();
|
|
4504
|
-
} catch { /* best-effort — peers may be offline; next tick retries */ }
|
|
4505
|
-
};
|
|
4506
|
-
poll();
|
|
4507
|
-
poolPollTimer = setInterval(poll, 15000);
|
|
4606
|
+
refreshPoolSessions();
|
|
4607
|
+
poolPollTimer = setInterval(refreshPoolSessions, 15000);
|
|
4508
4608
|
}
|
|
4509
4609
|
|
|
4510
4610
|
function startWaPolling() {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/commands/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAgCH,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAS3D,OAAO,EAAE,eAAe,EAAiC,MAAM,iCAAiC,CAAC;AAuBjG,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAGvD,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAyG7D,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAsBtD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,gBAAgB,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAC1C,OAAO,CAUT;AAyID,UAAU,YAAY;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;2DACuD;IACvD,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAmyCD,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,eAAe,EACzB,cAAc,EAAE,cAAc,EAC9B,YAAY,CAAC,EAAE,YAAY,EAC3B,WAAW,CAAC,EAAE,WAAW,EACzB,WAAW,CAAC,EAAE,WAAW,EACzB,iBAAiB,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,EAGvE,UAAU,CAAC,EAAE,MAAM,OAAO,8BAA8B,EAAE,WAAW,GAAG,IAAI,EAK5E,qBAAqB,CAAC,EAAE,MAAM,OAAO,gCAAgC,EAAE,kBAAkB,GAAG,IAAI,EAKhG,mBAAmB,CAAC,EAAE,MAAM,MAAM,GAAG,IAAI,GAAG,SAAS,GACpD,IAAI,CAwVN;AA2lBD,wBAAsB,WAAW,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/commands/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAgCH,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAS3D,OAAO,EAAE,eAAe,EAAiC,MAAM,iCAAiC,CAAC;AAuBjG,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAGvD,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAyG7D,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAsBtD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,gBAAgB,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAC1C,OAAO,CAUT;AAyID,UAAU,YAAY;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;2DACuD;IACvD,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAmyCD,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,eAAe,EACzB,cAAc,EAAE,cAAc,EAC9B,YAAY,CAAC,EAAE,YAAY,EAC3B,WAAW,CAAC,EAAE,WAAW,EACzB,WAAW,CAAC,EAAE,WAAW,EACzB,iBAAiB,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,EAGvE,UAAU,CAAC,EAAE,MAAM,OAAO,8BAA8B,EAAE,WAAW,GAAG,IAAI,EAK5E,qBAAqB,CAAC,EAAE,MAAM,OAAO,gCAAgC,EAAE,kBAAkB,GAAG,IAAI,EAKhG,mBAAmB,CAAC,EAAE,MAAM,MAAM,GAAG,IAAI,GAAG,SAAS,GACpD,IAAI,CAwVN;AA2lBD,wBAAsB,WAAW,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CA8uWtE;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"}
|
package/dist/commands/server.js
CHANGED
|
@@ -5446,6 +5446,9 @@ export async function startServer(options) {
|
|
|
5446
5446
|
reason: e.reason,
|
|
5447
5447
|
disposition: e.disposition,
|
|
5448
5448
|
origin: e.origin,
|
|
5449
|
+
// Untrusted provenance claim from a relayed close (spec §2.3) — a
|
|
5450
|
+
// signal in the trail, never an authority input.
|
|
5451
|
+
...(e.via ? { viaClaim: e.via } : {}),
|
|
5449
5452
|
// Positive-lane observability (june15-headless-spawn-reroute O4): record
|
|
5450
5453
|
// which billing lane the reaped session ran on so the soak can confirm
|
|
5451
5454
|
// rerouted sessions reach their completion from the reap-log too.
|