instar 1.3.91 → 1.3.92
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/.claude/skills/autonomous/hooks/autonomous-stop-hook.sh +17 -0
- package/dashboard/index.html +108 -0
- package/dist/commands/server.d.ts.map +1 -1
- package/dist/commands/server.js +207 -1
- package/dist/commands/server.js.map +1 -1
- package/dist/config/ConfigDefaults.d.ts.map +1 -1
- package/dist/config/ConfigDefaults.js +30 -0
- package/dist/config/ConfigDefaults.js.map +1 -1
- package/dist/config/LiveConfig.d.ts +3 -1
- package/dist/config/LiveConfig.d.ts.map +1 -1
- package/dist/config/LiveConfig.js +4 -1
- package/dist/config/LiveConfig.js.map +1 -1
- package/dist/config/stageWriteGuard.d.ts +23 -0
- package/dist/config/stageWriteGuard.d.ts.map +1 -0
- package/dist/config/stageWriteGuard.js +33 -0
- package/dist/config/stageWriteGuard.js.map +1 -0
- package/dist/core/DeliverMessageHandler.d.ts +33 -0
- package/dist/core/DeliverMessageHandler.d.ts.map +1 -0
- package/dist/core/DeliverMessageHandler.js +34 -0
- package/dist/core/DeliverMessageHandler.js.map +1 -0
- package/dist/core/LeaseCoordinator.d.ts +39 -2
- package/dist/core/LeaseCoordinator.d.ts.map +1 -1
- package/dist/core/LeaseCoordinator.js +45 -9
- package/dist/core/LeaseCoordinator.js.map +1 -1
- package/dist/core/MachineIdentity.d.ts +28 -1
- package/dist/core/MachineIdentity.d.ts.map +1 -1
- package/dist/core/MachineIdentity.js +81 -1
- package/dist/core/MachineIdentity.js.map +1 -1
- package/dist/core/MachineLoadBroker.d.ts +94 -0
- package/dist/core/MachineLoadBroker.d.ts.map +1 -0
- package/dist/core/MachineLoadBroker.js +87 -0
- package/dist/core/MachineLoadBroker.js.map +1 -0
- package/dist/core/MachinePoolRegistry.d.ts +94 -0
- package/dist/core/MachinePoolRegistry.d.ts.map +1 -0
- package/dist/core/MachinePoolRegistry.js +150 -0
- package/dist/core/MachinePoolRegistry.js.map +1 -0
- package/dist/core/MeshRpc.d.ts +165 -0
- package/dist/core/MeshRpc.d.ts.map +1 -0
- package/dist/core/MeshRpc.js +156 -0
- package/dist/core/MeshRpc.js.map +1 -0
- package/dist/core/MeshRpcClient.d.ts +59 -0
- package/dist/core/MeshRpcClient.d.ts.map +1 -0
- package/dist/core/MeshRpcClient.js +58 -0
- package/dist/core/MeshRpcClient.js.map +1 -0
- package/dist/core/MultiMachineCoordinator.d.ts +11 -0
- package/dist/core/MultiMachineCoordinator.d.ts.map +1 -1
- package/dist/core/MultiMachineCoordinator.js +13 -0
- package/dist/core/MultiMachineCoordinator.js.map +1 -1
- package/dist/core/NicknameAssigner.d.ts +51 -0
- package/dist/core/NicknameAssigner.d.ts.map +1 -0
- package/dist/core/NicknameAssigner.js +91 -0
- package/dist/core/NicknameAssigner.js.map +1 -0
- package/dist/core/NicknameCommand.d.ts +28 -0
- package/dist/core/NicknameCommand.d.ts.map +1 -0
- package/dist/core/NicknameCommand.js +69 -0
- package/dist/core/NicknameCommand.js.map +1 -0
- package/dist/core/PlacementExecutor.d.ts +71 -0
- package/dist/core/PlacementExecutor.d.ts.map +1 -0
- package/dist/core/PlacementExecutor.js +156 -0
- package/dist/core/PlacementExecutor.js.map +1 -0
- package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
- package/dist/core/PostUpdateMigrator.js +21 -0
- package/dist/core/PostUpdateMigrator.js.map +1 -1
- package/dist/core/RebalancePlanner.d.ts +48 -0
- package/dist/core/RebalancePlanner.d.ts.map +1 -0
- package/dist/core/RebalancePlanner.js +71 -0
- package/dist/core/RebalancePlanner.js.map +1 -0
- package/dist/core/SessionOwnership.d.ts +103 -0
- package/dist/core/SessionOwnership.d.ts.map +1 -0
- package/dist/core/SessionOwnership.js +0 -0
- package/dist/core/SessionOwnership.js.map +1 -0
- package/dist/core/SessionOwnershipRegistry.d.ts +115 -0
- package/dist/core/SessionOwnershipRegistry.d.ts.map +1 -0
- package/dist/core/SessionOwnershipRegistry.js +128 -0
- package/dist/core/SessionOwnershipRegistry.js.map +1 -0
- package/dist/core/SessionPoolE2EResultStore.d.ts +45 -0
- package/dist/core/SessionPoolE2EResultStore.d.ts.map +1 -0
- package/dist/core/SessionPoolE2EResultStore.js +66 -0
- package/dist/core/SessionPoolE2EResultStore.js.map +1 -0
- package/dist/core/SessionRouter.d.ts +95 -0
- package/dist/core/SessionRouter.d.ts.map +1 -0
- package/dist/core/SessionRouter.js +151 -0
- package/dist/core/SessionRouter.js.map +1 -0
- package/dist/core/StageAdvancer.d.ts +47 -0
- package/dist/core/StageAdvancer.d.ts.map +1 -0
- package/dist/core/StageAdvancer.js +70 -0
- package/dist/core/StageAdvancer.js.map +1 -0
- package/dist/core/TransferByNickname.d.ts +51 -0
- package/dist/core/TransferByNickname.d.ts.map +1 -0
- package/dist/core/TransferByNickname.js +50 -0
- package/dist/core/TransferByNickname.js.map +1 -0
- package/dist/core/TransferOrchestrator.d.ts +113 -0
- package/dist/core/TransferOrchestrator.d.ts.map +1 -0
- package/dist/core/TransferOrchestrator.js +79 -0
- package/dist/core/TransferOrchestrator.js.map +1 -0
- package/dist/core/types.d.ts +177 -1
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/scaffold/templates.d.ts.map +1 -1
- package/dist/scaffold/templates.js +5 -0
- package/dist/scaffold/templates.js.map +1 -1
- package/dist/server/AgentServer.d.ts +8 -0
- package/dist/server/AgentServer.d.ts.map +1 -1
- package/dist/server/AgentServer.js +4 -0
- package/dist/server/AgentServer.js.map +1 -1
- package/dist/server/CapabilityIndex.d.ts.map +1 -1
- package/dist/server/CapabilityIndex.js +14 -0
- package/dist/server/CapabilityIndex.js.map +1 -1
- package/dist/server/routes.d.ts +12 -0
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/routes.js +91 -0
- package/dist/server/routes.js.map +1 -1
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +64 -64
- package/src/scaffold/templates.ts +5 -0
- package/upgrades/1.3.92.md +63 -0
- package/upgrades/side-effects/session-pool-capability-classification.md +16 -0
- package/upgrades/side-effects/session-pool-review-fixes.md +23 -0
- package/upgrades/side-effects/session-pool-track-a-router-lease.md +29 -0
- package/upgrades/side-effects/session-pool-track-b-agent-awareness.md +29 -0
- package/upgrades/side-effects/session-pool-track-b-nicknames.md +35 -0
- package/upgrades/side-effects/session-pool-track-b-pool-api.md +36 -0
- package/upgrades/side-effects/session-pool-track-b-registry.md +31 -0
- package/upgrades/side-effects/session-pool-track-b-wiring.md +39 -0
- package/upgrades/side-effects/session-pool-track-c-dispatcher.md +24 -0
- package/upgrades/side-effects/session-pool-track-c-meshrpc.md +35 -0
- package/upgrades/side-effects/session-pool-track-c-route-wiring.md +31 -0
- package/upgrades/side-effects/session-pool-track-d-ownership-fsm.md +31 -0
- package/upgrades/side-effects/session-pool-track-d-registry.md +27 -0
- package/upgrades/side-effects/session-pool-track-d-wiring.md +30 -0
- package/upgrades/side-effects/session-pool-track-e-boot-wiring.md +20 -0
- package/upgrades/side-effects/session-pool-track-e-placement-core.md +17 -0
- package/upgrades/side-effects/session-pool-track-e-router.md +23 -0
- package/upgrades/side-effects/session-pool-track-f-config.md +12 -0
- package/upgrades/side-effects/session-pool-track-f-handoff-core.md +17 -0
- package/upgrades/side-effects/session-pool-track-g-load-broker.md +16 -0
- package/upgrades/side-effects/session-pool-track-h-gate-wiring.md +17 -0
- package/upgrades/side-effects/session-pool-track-h-mesh-client.md +15 -0
- package/upgrades/side-effects/session-pool-track-h-rebalance.md +13 -0
- package/upgrades/side-effects/session-pool-track-h-rollout-gate.md +17 -0
- package/upgrades/side-effects/session-pool-track-h-stage-guard.md +16 -0
|
@@ -30,6 +30,23 @@ set -uo pipefail # NOTE: -e intentionally omitted; field lookups for optional
|
|
|
30
30
|
# Read hook input from stdin
|
|
31
31
|
HOOK_INPUT=$(cat)
|
|
32
32
|
|
|
33
|
+
# ── Anchor to the agent home, NOT the session's CWD ───────────────────
|
|
34
|
+
# All state paths below are relative (.instar/autonomous/<topic>.local.md, the
|
|
35
|
+
# registry, the legacy file). The Stop hook inherits the session's working
|
|
36
|
+
# directory — and a session working inside a git worktree (~/.instar/agents/
|
|
37
|
+
# <name>/.worktrees/<slug>) would resolve those paths against the WORKTREE,
|
|
38
|
+
# which has no autonomous state, so the hook sees "no active job" and lets the
|
|
39
|
+
# session exit — silently stranding the autonomous loop. Anchor to the agent
|
|
40
|
+
# home (where this hook is installed) so the paths resolve regardless of CWD.
|
|
41
|
+
# (Root cause of the 2026-05-29 strand: CWD was a worktree.)
|
|
42
|
+
# Anchor to CLAUDE_PROJECT_DIR — Claude Code always sets it for hooks to the
|
|
43
|
+
# agent home (the hook itself is wired as `${CLAUDE_PROJECT_DIR}/.claude/...`).
|
|
44
|
+
# Guard on its `.instar` so a misconfigured value can't send us somewhere wrong;
|
|
45
|
+
# when unset (e.g. an isolated test harness) we stay in the caller's CWD.
|
|
46
|
+
if [[ -n "${CLAUDE_PROJECT_DIR:-}" ]] && [[ -d "${CLAUDE_PROJECT_DIR}/.instar" ]]; then
|
|
47
|
+
cd "$CLAUDE_PROJECT_DIR" || true
|
|
48
|
+
fi
|
|
49
|
+
|
|
33
50
|
REGISTRY_FILE=".instar/topic-session-registry.json"
|
|
34
51
|
RECOVERY_AUDIT=".instar/autonomous-recovery.jsonl"
|
|
35
52
|
LEGACY_STATE=".instar/autonomous-state.local.md"
|
package/dashboard/index.html
CHANGED
|
@@ -801,6 +801,18 @@
|
|
|
801
801
|
/* Page intro — one-sentence grounding right under the title so terms like
|
|
802
802
|
"it" / "the system" later in the page have a clear referent. */
|
|
803
803
|
.ph-intro { font-size: 17px; color: var(--text); line-height: 1.55; margin: 0; max-width: 720px; }
|
|
804
|
+
/* ── Machines tab (Multi-Machine Session Pool §L2) — reuses .ph-root calm type ── */
|
|
805
|
+
.machine-card { border: 1px solid var(--border, #2a2a2a); border-radius: 10px; padding: 14px 16px; margin-bottom: 12px; max-width: 720px; background: var(--panel-bg, transparent); }
|
|
806
|
+
.machine-head { display: flex; align-items: center; gap: 10px; }
|
|
807
|
+
.machine-dot { display: inline-block; width: 10px; height: 10px; border-radius: 50%; flex: 0 0 auto; }
|
|
808
|
+
.machine-dot.ok { background: var(--accent); } /* online */
|
|
809
|
+
.machine-dot.warn { background: #f59e0b; } /* clock out of sync — paused */
|
|
810
|
+
.machine-dot.off { background: var(--text-dim); } /* offline */
|
|
811
|
+
.machine-nick { font-size: 19px; font-weight: 600; color: var(--text-bright); background: transparent; border: 1px solid transparent; border-radius: 6px; padding: 3px 6px; min-width: 120px; }
|
|
812
|
+
.machine-nick:hover { border-color: var(--border, #2a2a2a); }
|
|
813
|
+
.machine-nick:focus { border-color: var(--accent); outline: none; background: rgba(255,255,255,0.03); }
|
|
814
|
+
.machine-badge { font-size: 12px; font-weight: 600; color: var(--accent); border: 1px solid var(--accent-dim); border-radius: 999px; padding: 2px 9px; }
|
|
815
|
+
.machine-meta { font-size: 15px; color: var(--text-dim); line-height: 1.55; margin-top: 6px; }
|
|
804
816
|
/* Status color tokens — calm, never alarming. The dot is a scan-aid:
|
|
805
817
|
glance the collapsed list, see which problems are still being worked
|
|
806
818
|
on (amber), cause known (blue), or worked out (green). */
|
|
@@ -2624,6 +2636,7 @@
|
|
|
2624
2636
|
<button class="tab" data-tab="threadline" onclick="switchTab('threadline')">Threadline</button>
|
|
2625
2637
|
<button class="tab" data-tab="evidence" onclick="switchTab('evidence')">Evidence</button>
|
|
2626
2638
|
<button class="tab" data-tab="process-health" onclick="switchTab('process-health')">Process Health</button>
|
|
2639
|
+
<button class="tab" data-tab="machines" onclick="switchTab('machines')">Machines</button>
|
|
2627
2640
|
</nav>
|
|
2628
2641
|
</div>
|
|
2629
2642
|
<div class="vital-signs" id="vitalSigns">
|
|
@@ -3136,6 +3149,21 @@
|
|
|
3136
3149
|
</details>
|
|
3137
3150
|
</div>
|
|
3138
3151
|
|
|
3152
|
+
<!-- machines tab (Multi-Machine Session Pool §L2) -->
|
|
3153
|
+
<div id="machinesPanel" class="ph-root" style="display:none;flex-direction:column;padding:28px;gap:24px;overflow-y:auto">
|
|
3154
|
+
<div style="display:flex;justify-content:space-between;align-items:baseline">
|
|
3155
|
+
<h2 class="ph-title">Machines</h2>
|
|
3156
|
+
<div id="machinesStamp" class="ph-stamp" aria-live="polite"></div>
|
|
3157
|
+
</div>
|
|
3158
|
+
<p class="ph-intro">Every computer this agent is set up on. Each one gets a friendly name you can change — and once it has a name, you can tell the agent "move this conversation to the mini" and it knows which machine you mean. The agent shares its conversations across these machines so it can handle more at once.</p>
|
|
3159
|
+
<div id="machinesHeadline" class="ph-headline-wrap" aria-live="polite"></div>
|
|
3160
|
+
<section class="ph-section">
|
|
3161
|
+
<h3 class="ph-h">The machines</h3>
|
|
3162
|
+
<p class="ph-h-sub">Click a name to rename it. The "dispatcher" is the one machine currently handing out new conversations.</p>
|
|
3163
|
+
<div id="machinesList"></div>
|
|
3164
|
+
</section>
|
|
3165
|
+
</div>
|
|
3166
|
+
|
|
3139
3167
|
<div id="evidenceTab" style="display:none;flex-direction:column;padding:20px;gap:16px;overflow-y:auto">
|
|
3140
3168
|
<div style="display:flex;justify-content:space-between;align-items:center">
|
|
3141
3169
|
<h2 style="margin:0">Evidence</h2>
|
|
@@ -4375,6 +4403,13 @@
|
|
|
4375
4403
|
onActivate: () => { if (typeof startProcessHealth === 'function') startProcessHealth(); },
|
|
4376
4404
|
onDeactivate: () => { if (typeof stopProcessHealth === 'function') stopProcessHealth(); },
|
|
4377
4405
|
},
|
|
4406
|
+
{
|
|
4407
|
+
id: 'machines',
|
|
4408
|
+
panels: ['machinesPanel'],
|
|
4409
|
+
display: ['flex'],
|
|
4410
|
+
onActivate: () => { if (typeof startMachines === 'function') startMachines(); },
|
|
4411
|
+
onDeactivate: () => { if (typeof stopMachines === 'function') stopMachines(); },
|
|
4412
|
+
},
|
|
4378
4413
|
{
|
|
4379
4414
|
id: 'secrets',
|
|
4380
4415
|
panels: ['secretsPanel'],
|
|
@@ -4530,6 +4565,79 @@
|
|
|
4530
4565
|
return resp.json();
|
|
4531
4566
|
}
|
|
4532
4567
|
|
|
4568
|
+
// ── Machines Tab (Multi-Machine Session Pool §L2) ─────────────────
|
|
4569
|
+
let __machinesTimer = null;
|
|
4570
|
+
function startMachines() { loadMachines(); if (!__machinesTimer) __machinesTimer = setInterval(loadMachines, 15000); }
|
|
4571
|
+
function stopMachines() { if (__machinesTimer) { clearInterval(__machinesTimer); __machinesTimer = null; } }
|
|
4572
|
+
function machineSpecLine(hw) {
|
|
4573
|
+
if (!hw) return 'specs not reported yet';
|
|
4574
|
+
const chip = (hw.cpuModel && hw.cpuModel !== 'unknown') ? hw.cpuModel : ((hw.platform || '') + (hw.arch ? ' ' + hw.arch : '')).trim();
|
|
4575
|
+
const cores = hw.cpuCores ? hw.cpuCores + ' cores' : '';
|
|
4576
|
+
const ram = hw.totalMemBytes ? Math.round(hw.totalMemBytes / 1073741824) + ' GB' : '';
|
|
4577
|
+
return [chip, cores, ram].filter(Boolean).join(' · ') || 'specs not reported yet';
|
|
4578
|
+
}
|
|
4579
|
+
function machineBusyWord(m) {
|
|
4580
|
+
if (m.loadAvg == null) return '';
|
|
4581
|
+
if (m.loadAvg < 1) return 'idle';
|
|
4582
|
+
if (m.loadAvg < 4) return 'working';
|
|
4583
|
+
return 'busy';
|
|
4584
|
+
}
|
|
4585
|
+
function renderMachineCard(m, routerHolder) {
|
|
4586
|
+
const online = !!m.online;
|
|
4587
|
+
const dot = !online ? 'off' : (m.clockSkewStatus === 'suspect-clock-removed' ? 'warn' : 'ok');
|
|
4588
|
+
const statusWord = !online ? 'offline' : (m.clockSkewStatus === 'suspect-clock-removed' ? 'clock out of sync — paused for new conversations' : 'online');
|
|
4589
|
+
const isRouter = !!(routerHolder && m.machineId === routerHolder);
|
|
4590
|
+
const nick = m.nickname || m.machineId;
|
|
4591
|
+
const busy = machineBusyWord(m);
|
|
4592
|
+
const sessions = (m.activeSessionCount != null) ? (m.activeSessionCount + (m.maxSessions ? '/' + m.maxSessions : '') + ' conversation' + (m.activeSessionCount === 1 ? '' : 's')) : '';
|
|
4593
|
+
const tail = [statusWord, busy, sessions].filter(Boolean).map(s => escapeHtml(s)).join(' · ');
|
|
4594
|
+
return '<div class="machine-card">' +
|
|
4595
|
+
'<div class="machine-head">' +
|
|
4596
|
+
'<span class="machine-dot ' + dot + '" title="' + escapeHtml(statusWord) + '"></span>' +
|
|
4597
|
+
'<input class="machine-nick" value="' + escapeHtml(nick) + '" data-mid="' + escapeHtml(m.machineId) + '" ' +
|
|
4598
|
+
'onkeydown="if(event.key===\'Enter\'){this.blur();}" onblur="saveMachineNickname(this)" aria-label="machine nickname" />' +
|
|
4599
|
+
(isRouter ? '<span class="machine-badge">dispatcher</span>' : '') +
|
|
4600
|
+
'</div>' +
|
|
4601
|
+
'<div class="machine-meta">' + escapeHtml(machineSpecLine(m.hardware)) + '</div>' +
|
|
4602
|
+
'<div class="machine-meta">' + tail + '</div>' +
|
|
4603
|
+
'</div>';
|
|
4604
|
+
}
|
|
4605
|
+
async function saveMachineNickname(input) {
|
|
4606
|
+
const mid = input.getAttribute('data-mid');
|
|
4607
|
+
const nickname = input.value.trim();
|
|
4608
|
+
try {
|
|
4609
|
+
await apiFetch('/pool/machines/' + encodeURIComponent(mid), {
|
|
4610
|
+
method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ nickname }),
|
|
4611
|
+
});
|
|
4612
|
+
loadMachines();
|
|
4613
|
+
} catch (e) {
|
|
4614
|
+
const hl = document.getElementById('machinesHeadline');
|
|
4615
|
+
if (hl) hl.innerHTML = '<div class="ph-headline">Couldn\'t rename that machine: ' + escapeHtml(e.message) + '</div>';
|
|
4616
|
+
}
|
|
4617
|
+
}
|
|
4618
|
+
async function loadMachines() {
|
|
4619
|
+
const stamp = document.getElementById('machinesStamp');
|
|
4620
|
+
const list = document.getElementById('machinesList');
|
|
4621
|
+
const headline = document.getElementById('machinesHeadline');
|
|
4622
|
+
if (!list) return;
|
|
4623
|
+
try {
|
|
4624
|
+
const data = await apiFetch('/pool');
|
|
4625
|
+
if (stamp) stamp.textContent = new Date().toLocaleTimeString();
|
|
4626
|
+
if (!data.enabled) {
|
|
4627
|
+
if (headline) headline.innerHTML = '<div class="ph-headline">Running on a single machine right now — nothing to spread across yet.</div>';
|
|
4628
|
+
list.innerHTML = '';
|
|
4629
|
+
return;
|
|
4630
|
+
}
|
|
4631
|
+
const machines = data.machines || [];
|
|
4632
|
+
const onlineCount = machines.filter(m => m.online).length;
|
|
4633
|
+
if (headline) headline.innerHTML = '<div class="ph-headline">' + onlineCount + ' of ' + machines.length + ' machine' + (machines.length === 1 ? '' : 's') + ' online</div>';
|
|
4634
|
+
const routerHolder = data.router && data.router.holder;
|
|
4635
|
+
list.innerHTML = machines.length ? machines.map(m => renderMachineCard(m, routerHolder)).join('') : '<div class="machine-meta">No machines paired yet.</div>';
|
|
4636
|
+
} catch (e) {
|
|
4637
|
+
if (headline) headline.innerHTML = '<div class="ph-headline">Couldn\'t load machines: ' + escapeHtml(e.message) + '</div>';
|
|
4638
|
+
}
|
|
4639
|
+
}
|
|
4640
|
+
|
|
4533
4641
|
// ── Tokens Tab ──────────────────────────────────────────────────
|
|
4534
4642
|
function fmtTokens(n) {
|
|
4535
4643
|
if (n == null) return '0';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/commands/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAkSH,UAAU,YAAY;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;2DACuD;IACvD,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAksDD,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;AAkSH,UAAU,YAAY;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;2DACuD;IACvD,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAksDD,wBAAsB,WAAW,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAqtOtE;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
|
@@ -8167,7 +8167,213 @@ export async function startServer(options) {
|
|
|
8167
8167
|
}
|
|
8168
8168
|
}
|
|
8169
8169
|
}
|
|
8170
|
-
|
|
8170
|
+
// ── Multi-Machine Session Pool registry (§L2) — live MachineCapacity view for GET /pool ──
|
|
8171
|
+
// Always instantiated (cheap); the feature stays dark behind the sessionPool stage gate.
|
|
8172
|
+
// Self-attests hardware + records a self-heartbeat so GET /pool + the Machines tab show
|
|
8173
|
+
// this machine online with its specs; peer liveness is fed from MachineHeartbeat.
|
|
8174
|
+
let machinePoolRegistry;
|
|
8175
|
+
try {
|
|
8176
|
+
const poolMod = await import('../core/MachinePoolRegistry.js');
|
|
8177
|
+
const osMod = await import('node:os');
|
|
8178
|
+
const poolIdMgr = coordinator?.managers?.identityManager;
|
|
8179
|
+
if (poolIdMgr) {
|
|
8180
|
+
const failoverThresholdMs = (config.multiMachine?.failoverTimeoutMinutes ?? 15) * 60_000;
|
|
8181
|
+
const clockSkewToleranceMs = config.multiMachine?.sessionPool?.clockSkewToleranceMs ?? 300_000;
|
|
8182
|
+
machinePoolRegistry = new poolMod.MachinePoolRegistry({
|
|
8183
|
+
listMachines: () => poolIdMgr.getActiveMachines().map(({ machineId, entry }) => ({
|
|
8184
|
+
machineId,
|
|
8185
|
+
nickname: entry.nickname,
|
|
8186
|
+
hardware: entry.hardware,
|
|
8187
|
+
})),
|
|
8188
|
+
clockSkewToleranceMs,
|
|
8189
|
+
failoverThresholdMs,
|
|
8190
|
+
logger: (m) => console.log(pc.dim(` [pool] ${m}`)),
|
|
8191
|
+
});
|
|
8192
|
+
const poolSelfId = machineHeartbeat?.config?.machineId ??
|
|
8193
|
+
(poolIdMgr.hasIdentity() ? poolIdMgr.loadIdentity().machineId : null);
|
|
8194
|
+
if (poolSelfId) {
|
|
8195
|
+
try {
|
|
8196
|
+
poolIdMgr.recordSelfHardware(poolSelfId, poolMod.captureHardware());
|
|
8197
|
+
}
|
|
8198
|
+
catch { /* best-effort hardware self-attest */ }
|
|
8199
|
+
const refreshPool = () => {
|
|
8200
|
+
try {
|
|
8201
|
+
machinePoolRegistry.recordHeartbeat({
|
|
8202
|
+
machineId: poolSelfId,
|
|
8203
|
+
selfReportedLastSeen: new Date().toISOString(),
|
|
8204
|
+
loadAvg: osMod.loadavg()[0],
|
|
8205
|
+
});
|
|
8206
|
+
const hbApi = machineHeartbeat?.api;
|
|
8207
|
+
if (hbApi) {
|
|
8208
|
+
for (const r of hbApi.listAll()) {
|
|
8209
|
+
if (r.machineId !== poolSelfId) {
|
|
8210
|
+
machinePoolRegistry.recordHeartbeat({ machineId: r.machineId, selfReportedLastSeen: r.lastHeartbeatAt });
|
|
8211
|
+
}
|
|
8212
|
+
}
|
|
8213
|
+
}
|
|
8214
|
+
}
|
|
8215
|
+
catch { /* best-effort pool refresh */ }
|
|
8216
|
+
};
|
|
8217
|
+
refreshPool();
|
|
8218
|
+
const poolTimer = setInterval(refreshPool, 30_000);
|
|
8219
|
+
if (typeof poolTimer.unref === 'function')
|
|
8220
|
+
poolTimer.unref();
|
|
8221
|
+
}
|
|
8222
|
+
}
|
|
8223
|
+
}
|
|
8224
|
+
catch (err) {
|
|
8225
|
+
console.log(pc.dim(` [pool] registry not wired: ${err instanceof Error ? err.message : String(err)}`));
|
|
8226
|
+
}
|
|
8227
|
+
// ── MeshRpc dispatcher (§L0) + SessionOwnership registry (§L3) ──
|
|
8228
|
+
// Built when there's a machine identity. The ownership registry (L3) feeds
|
|
8229
|
+
// the dispatcher's RBAC (ownerOf/placementTargetOf) + the place/claim/
|
|
8230
|
+
// transfer/release handlers; read-class handlers (capacity/session-status)
|
|
8231
|
+
// are live too. Single-machine uses the in-memory store; the cross-machine
|
|
8232
|
+
// git-backed store swaps in for the Track-H proof (the registry is store-agnostic).
|
|
8233
|
+
let meshRpcDispatcher;
|
|
8234
|
+
let sessionOwnershipRegistry;
|
|
8235
|
+
try {
|
|
8236
|
+
const meshMod = await import('../core/MeshRpc.js');
|
|
8237
|
+
const idMod = await import('../core/MachineIdentity.js');
|
|
8238
|
+
const meshIdMgr = coordinator?.managers?.identityManager;
|
|
8239
|
+
const meshSelfId = machineHeartbeat?.config?.machineId
|
|
8240
|
+
?? (meshIdMgr?.hasIdentity() ? meshIdMgr.loadIdentity().machineId : null);
|
|
8241
|
+
if (meshIdMgr && meshSelfId) {
|
|
8242
|
+
const meshClockToleranceMs = config.multiMachine?.sessionPool?.meshRpcClockToleranceMs ?? 30000;
|
|
8243
|
+
const seenMeshNonces = new Map(); // `${sender}:${nonce}` → ts, age-pruned
|
|
8244
|
+
const meshPruneMs = Math.max(meshClockToleranceMs * 4, 120_000);
|
|
8245
|
+
// SessionOwnership registry (§L3) — in-memory store (single-machine correct;
|
|
8246
|
+
// git-backed cross-machine store is the Track-H swap). Per-session nonce set.
|
|
8247
|
+
const ownMod = await import('../core/SessionOwnershipRegistry.js');
|
|
8248
|
+
const seenOwnNonces = new Set();
|
|
8249
|
+
sessionOwnershipRegistry = new ownMod.SessionOwnershipRegistry({
|
|
8250
|
+
store: new ownMod.InMemorySessionOwnershipStore(),
|
|
8251
|
+
seenNonce: (k) => seenOwnNonces.has(k),
|
|
8252
|
+
recordNonce: (k) => seenOwnNonces.add(k),
|
|
8253
|
+
logger: (m) => console.log(pc.dim(` ${m}`)),
|
|
8254
|
+
});
|
|
8255
|
+
const ownReg = sessionOwnershipRegistry;
|
|
8256
|
+
// The §L3 ownership commands, routed from MeshRpc to the registry CAS.
|
|
8257
|
+
const ownAction = (cmd, sender, env) => {
|
|
8258
|
+
if (cmd.type === 'place')
|
|
8259
|
+
return ownReg.cas({ type: 'place', machineId: cmd.machine }, { sessionKey: cmd.session, sender, nonce: env.nonce });
|
|
8260
|
+
if (cmd.type === 'claim')
|
|
8261
|
+
return ownReg.cas({ type: 'claim', machineId: sender }, { sessionKey: cmd.session, sender, nonce: env.nonce });
|
|
8262
|
+
if (cmd.type === 'transfer')
|
|
8263
|
+
return ownReg.cas({ type: 'transfer', to: cmd.target }, { sessionKey: cmd.session, sender, nonce: env.nonce });
|
|
8264
|
+
if (cmd.type === 'release')
|
|
8265
|
+
return ownReg.cas({ type: 'release', machineId: sender }, { sessionKey: cmd.session, sender, nonce: env.nonce });
|
|
8266
|
+
return { ok: false, reason: 'unsupported' };
|
|
8267
|
+
};
|
|
8268
|
+
// The §L4 owner-side deliverMessage receive handler (shared factory — same
|
|
8269
|
+
// code path the tests exercise). Durable-receipt-before-processing with
|
|
8270
|
+
// idempotent dedupe on messageId + the stale-ownership fence. Returns the
|
|
8271
|
+
// deliverMessageAck the router waits on before advancing the platform offset.
|
|
8272
|
+
// (Local processing hand-off to SessionManager is the Track-H staged
|
|
8273
|
+
// activation; the durable receipt + ACK is the complete dark-phase contract.)
|
|
8274
|
+
const deliverMod = await import('../core/DeliverMessageHandler.js');
|
|
8275
|
+
const deliverSeenFallback = new Set(); // used only if the SQLite ledger is unavailable
|
|
8276
|
+
const deliverMessageHandler = deliverMod.createDeliverMessageHandler({
|
|
8277
|
+
ownerEpochOf: (s) => ownReg.read(s)?.ownershipEpoch ?? null,
|
|
8278
|
+
recordReceipt: (messageId, session) => {
|
|
8279
|
+
if (messageLedger)
|
|
8280
|
+
return messageLedger.record(messageId, { platform: 'mesh', topic: session }).firstSeen;
|
|
8281
|
+
if (deliverSeenFallback.has(messageId))
|
|
8282
|
+
return false;
|
|
8283
|
+
deliverSeenFallback.add(messageId);
|
|
8284
|
+
return true;
|
|
8285
|
+
},
|
|
8286
|
+
});
|
|
8287
|
+
meshRpcDispatcher = new meshMod.MeshRpcDispatcher({
|
|
8288
|
+
verify: {
|
|
8289
|
+
selfMachineId: meshSelfId,
|
|
8290
|
+
verify: (canonical, signature, sender) => {
|
|
8291
|
+
const pem = meshIdMgr.getSigningPublicKeyPem(sender);
|
|
8292
|
+
if (!pem)
|
|
8293
|
+
return false;
|
|
8294
|
+
try {
|
|
8295
|
+
return idMod.verify(canonical, signature, pem);
|
|
8296
|
+
}
|
|
8297
|
+
catch {
|
|
8298
|
+
return false;
|
|
8299
|
+
}
|
|
8300
|
+
},
|
|
8301
|
+
isRegisteredPeer: (s) => meshIdMgr.isMachineActive(s),
|
|
8302
|
+
seenNonce: (s, n) => seenMeshNonces.has(`${s}:${n}`),
|
|
8303
|
+
now: () => Date.now(),
|
|
8304
|
+
clockToleranceMs: meshClockToleranceMs,
|
|
8305
|
+
},
|
|
8306
|
+
rbac: {
|
|
8307
|
+
routerHolder: () => coordinator?.getSyncStatus().leaseHolder ?? null,
|
|
8308
|
+
ownerOf: (s) => ownReg.ownerOf(s),
|
|
8309
|
+
placementTargetOf: (s) => ownReg.placementTargetOf(s),
|
|
8310
|
+
},
|
|
8311
|
+
recordNonce: (s, n) => {
|
|
8312
|
+
const t = Date.now();
|
|
8313
|
+
seenMeshNonces.set(`${s}:${n}`, t);
|
|
8314
|
+
if (seenMeshNonces.size > 5000) {
|
|
8315
|
+
for (const [k, v] of seenMeshNonces)
|
|
8316
|
+
if (t - v > meshPruneMs)
|
|
8317
|
+
seenMeshNonces.delete(k);
|
|
8318
|
+
}
|
|
8319
|
+
},
|
|
8320
|
+
handlers: {
|
|
8321
|
+
'capacity-report': () => machinePoolRegistry?.getCapacities() ?? [],
|
|
8322
|
+
'session-status': () => machinePoolRegistry?.getCapacity(meshSelfId) ?? { machineId: meshSelfId },
|
|
8323
|
+
place: ownAction,
|
|
8324
|
+
claim: ownAction,
|
|
8325
|
+
transfer: ownAction,
|
|
8326
|
+
release: ownAction,
|
|
8327
|
+
deliverMessage: deliverMessageHandler,
|
|
8328
|
+
},
|
|
8329
|
+
logger: (m) => console.log(pc.dim(` ${m}`)),
|
|
8330
|
+
});
|
|
8331
|
+
}
|
|
8332
|
+
}
|
|
8333
|
+
catch (err) {
|
|
8334
|
+
console.log(pc.dim(` [mesh-rpc] dispatcher not wired: ${err instanceof Error ? err.message : String(err)}`));
|
|
8335
|
+
}
|
|
8336
|
+
// ── Rollout gate (§Rollout): signed E2E result store + StageAdvancer ──────────
|
|
8337
|
+
// The E2E result store backs GET /session-pool/e2e-results (observable gate state);
|
|
8338
|
+
// StageAdvancer is the SOLE writer of multiMachine.sessionPool.stage (it passes the
|
|
8339
|
+
// stageWriteGuard token; any other write throws stage-write-not-permitted). Always
|
|
8340
|
+
// instantiated (cheap); inert until the pool activates past 'dark'.
|
|
8341
|
+
let sessionPoolE2EResultStore;
|
|
8342
|
+
try {
|
|
8343
|
+
const e2eMod = await import('../core/SessionPoolE2EResultStore.js');
|
|
8344
|
+
const stageMod = await import('../core/StageAdvancer.js');
|
|
8345
|
+
const guardMod = await import('../config/stageWriteGuard.js');
|
|
8346
|
+
const crypto = await import('node:crypto');
|
|
8347
|
+
// HMAC over the agent's authToken — tamper-evident without a separate keystore.
|
|
8348
|
+
const hmacKey = String(config.authToken ?? 'instar-session-pool-e2e');
|
|
8349
|
+
const hmac = (c) => crypto.createHmac('sha256', hmacKey).update(c).digest('hex');
|
|
8350
|
+
sessionPoolE2EResultStore = new e2eMod.SessionPoolE2EResultStore({
|
|
8351
|
+
filePath: path.join(config.stateDir, 'session-pool-e2e-results.json'),
|
|
8352
|
+
sign: hmac,
|
|
8353
|
+
verifySig: (c, s) => {
|
|
8354
|
+
try {
|
|
8355
|
+
const exp = hmac(c);
|
|
8356
|
+
return s.length === exp.length && crypto.timingSafeEqual(Buffer.from(s), Buffer.from(exp));
|
|
8357
|
+
}
|
|
8358
|
+
catch {
|
|
8359
|
+
return false;
|
|
8360
|
+
}
|
|
8361
|
+
},
|
|
8362
|
+
});
|
|
8363
|
+
// StageAdvancer: the sole stage writer. Held for the rollout job/route to drive;
|
|
8364
|
+
// constructed here so the write path (liveConfig + token) is wired in one place.
|
|
8365
|
+
void new stageMod.StageAdvancer({
|
|
8366
|
+
resultStore: sessionPoolE2EResultStore,
|
|
8367
|
+
currentCommitSha: () => process.env.INSTAR_COMMIT_SHA ?? process.env.GITHUB_SHA ?? 'unknown',
|
|
8368
|
+
readStage: () => liveConfig.get('multiMachine.sessionPool.stage', 'dark'),
|
|
8369
|
+
writeStageConfig: (s) => liveConfig.set(guardMod.STAGE_CONFIG_PATH, s, { stageWriteToken: guardMod.STAGE_WRITE_TOKEN }),
|
|
8370
|
+
audit: (event, detail) => console.log(pc.dim(` [stage-advancer] ${event} ${JSON.stringify(detail)}`)),
|
|
8371
|
+
});
|
|
8372
|
+
}
|
|
8373
|
+
catch (err) {
|
|
8374
|
+
console.log(pc.dim(` [session-pool] rollout gate not wired: ${err instanceof Error ? err.message : String(err)}`));
|
|
8375
|
+
}
|
|
8376
|
+
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, rateLimitSentinel, releaseReadinessSentinel: releaseReadinessSentinel ?? undefined, messageRouter, summarySentinel, spawnManager, systemReviewer, capabilityMapper, selfKnowledgeTree, coverageAuditor, topicResumeMap: _topicResumeMap ?? undefined, sessionRefresh: _sessionRefresh ?? undefined, autonomyManager, trustElevationTracker, autonomousEvolution, coordinator: coordinator.enabled ? coordinator : undefined, localSigningKeyPem, leaseTransport, liveTailReceiver, handoffWireTransport, onHandoffBegin, onHandoffInitiate: handoffInitiate, handoffInProgress: handoffSentinelInProgress, messageLedger, currentInboundByTopic, replyMarkerTransport, onReplyMarker: messageLedger ? (marker) => { const m = marker; messageLedger.applyRemoteReplyMarker(m.dedupeKey, { platform: m.platform, replyIdempotencyKey: m.replyIdempotencyKey, epoch: m.epoch, topic: m.topic ?? null }); } : undefined, whatsapp: whatsappAdapter, slack: slackAdapter, imessage: imessageAdapter, whatsappBusinessBackend, messageBridge, hookEventReceiver, worktreeMonitor, subagentTracker, instructionsVerifier, handshakeManager: threadlineHandshake, threadlineRouter, conversationStore, warrantsReplyGate, collaborationSurfacer, threadResumeMap, topicLinkageHandler: topicLinkageHandler ?? undefined, threadlineRelayClient, threadlineReplyWaiters, listenerManager: listenerManager ?? undefined, responseReviewGate, messagingToneGate, outboundDedupGate, telemetryHeartbeat, pasteManager, featureRegistry, discoveryEvaluator, completionEvaluator, unifiedTrust, liveConfig, sharedStateLedger, ledgerSessionRegistry, worktreeManager, oidcEnrolledRepos: parallelDevConfig?.oidcEnrolledRepos, initiativeTracker, projectRoundRunner, projectDriftChecker, machineHeartbeat, machinePoolRegistry, meshRpcDispatcher, sessionOwnershipRegistry, sessionPoolE2EResultStore, proxyCoordinator, topicIntentStore, topicIntentArcCheck, usherSignalStore, intelligence: sharedIntelligence ?? undefined, telegramBridgeConfig, telegramBridge: telegramBridge ?? undefined, threadlineObservability, briefDeps, workingMemory, taskFlowRegistry, threadlineFlowBridge, sessionReaper, reapLog, sleepWakeDetector, unjustifiedStopGate, stopGateDb, stopNotifier });
|
|
8171
8377
|
// Boot-recovery (tunnel-failure-resilience spec Part 6): if the agent
|
|
8172
8378
|
// died mid-relay-episode, the persisted tunnel.json carries
|
|
8173
8379
|
// rotationPending=true. Rotate the dashboard PIN + authToken BEFORE
|