@sentropic/h2a-runtime 0.90.1 → 0.90.2
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/dist/index.d.ts +16 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +131 -20
- package/dist/index.js.map +1 -1
- package/dist/proc-cpu.d.ts +44 -0
- package/dist/proc-cpu.d.ts.map +1 -1
- package/dist/proc-cpu.js +84 -2
- package/dist/proc-cpu.js.map +1 -1
- package/dist/relaunch.d.ts +32 -1
- package/dist/relaunch.d.ts.map +1 -1
- package/dist/relaunch.js +93 -0
- package/dist/relaunch.js.map +1 -1
- package/dist/run.d.ts.map +1 -1
- package/dist/run.js +0 -3
- package/dist/run.js.map +1 -1
- package/dist/session-lease.d.ts +83 -4
- package/dist/session-lease.d.ts.map +1 -1
- package/dist/session-lease.js +197 -6
- package/dist/session-lease.js.map +1 -1
- package/dist/tmux.d.ts +45 -0
- package/dist/tmux.d.ts.map +1 -1
- package/dist/tmux.js +111 -1
- package/dist/tmux.js.map +1 -1
- package/package.json +2 -3
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { projectStatusForH2a } from "./status-projection.js";
|
|
3
|
+
import type { ProcView } from "./proc-cpu.js";
|
|
3
4
|
import { type RegistryEntry } from "./registry.js";
|
|
4
|
-
import { reclaimProposals, type SessionLease } from "./session-lease.js";
|
|
5
|
+
import { reclaimProposals, type SessionLease, type SessionLeaseFleetAudit, type SessionLeaseWorker } from "./session-lease.js";
|
|
5
6
|
import { projectRemoteAgents } from "./agents-projection.js";
|
|
6
7
|
export declare const packageName = "@sentropic/h2a-runtime";
|
|
7
8
|
export declare const H2A_RUNTIME_CLI_API_VERSION = 1;
|
|
@@ -93,6 +94,7 @@ export declare function acquireSessionLease(sessionId: string, opts: {
|
|
|
93
94
|
holder: string;
|
|
94
95
|
workspace?: string;
|
|
95
96
|
agentPane?: string;
|
|
97
|
+
worker?: SessionLeaseWorker;
|
|
96
98
|
}): SessionLease | undefined;
|
|
97
99
|
/** What one supervising pass over the session leases did. */
|
|
98
100
|
export type SessionLeaseSupervision = {
|
|
@@ -113,6 +115,19 @@ export type SessionLeaseSupervision = {
|
|
|
113
115
|
* Its only output beyond the store is a list of PROPOSALS.
|
|
114
116
|
*/
|
|
115
117
|
export declare function superviseSessionLeases(sessions: ReadonlyArray<RegistryEntry>, now?: string): SessionLeaseSupervision;
|
|
118
|
+
/** Relaunch one managed session while its lease carries the bounded protection. */
|
|
119
|
+
export declare function relaunchSessionWithLease(input: {
|
|
120
|
+
sessionId: string;
|
|
121
|
+
tmuxSession: string;
|
|
122
|
+
command: string;
|
|
123
|
+
resolvingBoundMs?: number;
|
|
124
|
+
}): boolean;
|
|
125
|
+
/** One on-demand fleet-wide read-back audit; it never runs from rendering. */
|
|
126
|
+
export declare function auditLiveSessionLeases(sessions?: ReadonlyArray<RegistryEntry>, now?: string): SessionLeaseFleetAudit & {
|
|
127
|
+
reaped: string[];
|
|
128
|
+
liveLeaseCount: number;
|
|
129
|
+
procView: ProcView;
|
|
130
|
+
};
|
|
116
131
|
/**
|
|
117
132
|
* Reliability slice 1 — RESUME a throttled HEADLESS LOCAL job. Relaunch the SAME
|
|
118
133
|
* job in the SAME `runCwd` it already ran in (its recorded `cwd` — NOT a fresh
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -15,7 +15,7 @@ import { transmittedSecrets, secretsSummary } from "./secrets.js";
|
|
|
15
15
|
import { localConvStat, remoteConvStat, alignment } from "./convsync.js";
|
|
16
16
|
import { gitAlignment, localAncestry, localGitStat, remoteGitStat, } from "./gitdiff.js";
|
|
17
17
|
import { syncConversation } from "./sync.js";
|
|
18
|
-
import { attachLocalSession, attachPodTmux, capturePane, capturePaneVisible, clearPaneComposer, conductorRunning, currentTmuxSessionName, currentTmuxSessionIs, ensureManagedTmuxProfile, ensureHeadlessTerminal, existingLocalSessionSlugs, fanoutLabels, findLocalSession, killLocalSession, localSessionGatewayEnvStatus, listLocalSessions, listLocalSessionsWithDiagnostics, localSessionIdle, localSessionPanePid, localSessionName, paneTreeCpuMs, paneWorkerPid, pasteLiteralBlock, submitPane, installH2aStatusSurface, managedSessionCandidates, parseManagedSessionName, openH2aStatusWindow, persistLaunchContext, readLaunchContext, relaunchInSession, resolveAgentPane, resolveAgentPaneForInstance, resolveLocalSession, runLocalCliForeground, sendKeysLiteral, sessionAttached, sessionAttachedCount, setLocalSessionDisplayName, slugify, cleanupHeadlessPromptFile, startH2aWindow, startH2aWindowVerified, startHeadlessSession, startLocalSession, tmuxAvailable, uninstallH2aStatusSurface, } from "./tmux.js";
|
|
18
|
+
import { attachLocalSession, attachPodTmux, capturePane, capturePaneVisible, clearPaneComposer, conductorRunning, currentTmuxSessionName, currentTmuxSessionIs, ensureManagedTmuxProfile, ensureHeadlessTerminal, existingLocalSessionSlugs, fanoutLabels, findLocalSession, killLocalSession, localSessionGatewayEnvStatus, listLocalSessions, listH2aStatusSurfaces, listLocalSessionsWithDiagnostics, localSessionIdle, localSessionPanePid, localSessionName, paneProcessObservation, paneTreeCpuMs, paneWorkerAttribution, paneWorkerAttributionFromView, paneWorkerPid, pasteLiteralBlock, submitPane, installH2aStatusSurface, managedSessionCandidates, parseManagedSessionName, openH2aStatusWindow, persistLaunchContext, readLaunchContext, readFleetProcView, relaunchInSession, resolveAgentPane, resolveAgentPaneForInstance, resolveLocalSession, sessionRelaunchSafety, runLocalCliForeground, sendKeysLiteral, sessionAttached, sessionAttachedCount, setLocalSessionDisplayName, slugify, cleanupHeadlessPromptFile, startH2aWindow, startH2aWindowVerified, startHeadlessSession, startLocalSession, tmuxAvailable, uninstallH2aStatusSurface, } from "./tmux.js";
|
|
19
19
|
import { deliverInitialPrompt, sleepSync, } from "./prompt-delivery.js";
|
|
20
20
|
import { buildLaunchContext } from "./launch-context.js";
|
|
21
21
|
import { migrateTmuxNames } from "./tmux-name-migration.js";
|
|
@@ -25,7 +25,7 @@ import { planRelaunch } from "./relaunch.js";
|
|
|
25
25
|
import { isHumanFacingSession, readConversationCustomTitle, readLastLayout, reconcileRunConvIds, restore as restoreLayout, } from "./restore.js";
|
|
26
26
|
import { getLayoutConfig } from "./config.js";
|
|
27
27
|
import { advanceJob, coerceRegistryTool, enroll, enrollFromRun, isLive, listJobs, listLocalForLs, loadRegistry, loadRegistryWithDiagnostics, resolveLocalTmuxSessionForName, tryClaimSlot, withRegistryLock, resolveRegistryPath, } from "./registry.js";
|
|
28
|
-
import { SessionLeaseStore, decideSessionBeat, isSessionLeaseAbandoned, planLeaseSupervision, reclaimProposals, resolveSessionLeasePath, } from "./session-lease.js";
|
|
28
|
+
import { auditSessionLeaseFleet, countLiveSessionLeases, SessionLeaseStore, decideSessionBeat, isSessionLeaseAlive, isSessionLeaseAbandoned, isSessionLeaseDeadBoot, planLeaseSupervision, reclaimProposals, resolveSessionLeasePath, } from "./session-lease.js";
|
|
29
29
|
import { aimdEffectiveCap, assertDelegateEffort, assertSafeName, buildDelegateArgs, buildJobRows, buildRemoteDelegate, buildThrottleResumeArgs, canDelegateAtDepth, childDepthEnvValue, clampDepth, clampRemoteDepthBudget, conductorAdvisory, DEFAULT_MAX_CONCURRENT, DEPTH_ENV, inheritedDepthBudget, isDelegateType, isThrottleResumeDue, jitteredBackoffMs, JOB_ID_ENV, jobDir, planNextStarts, planThrottleStep, readJobResult, THROTTLE_MAX_ATTEMPTS, reconcileRemoteJobs, renderJobsTable, resolveJobCwd, runTrackMirror, sweepStaleJobs, trackItemNewArgs, trackItemRealizeArgs, } from "./delegate.js";
|
|
30
30
|
import { detectThrottle, THROTTLE_TAIL_LINES } from "./throttle-signatures.js";
|
|
31
31
|
import { interactiveResumeNudge, planInteractiveResume, } from "./interactive-throttle.js";
|
|
@@ -1014,12 +1014,15 @@ function sessionLeaseStore() {
|
|
|
1014
1014
|
* to compute a rate against.
|
|
1015
1015
|
*/
|
|
1016
1016
|
export function acquireSessionLease(sessionId, opts) {
|
|
1017
|
-
|
|
1017
|
+
// CPU baseline and worker identity share one /proc walk at acquire time.
|
|
1018
|
+
const observation = opts.agentPane ? paneProcessObservation(opts.agentPane) : undefined;
|
|
1019
|
+
const worker = opts.worker ?? observation?.worker;
|
|
1018
1020
|
return sessionLeaseStore().acquire({
|
|
1019
1021
|
sessionId,
|
|
1020
1022
|
holder: opts.holder,
|
|
1021
1023
|
...(opts.workspace !== undefined ? { workspace: opts.workspace } : {}),
|
|
1022
|
-
...(cpuMs !== undefined ? { cpuMs } : {}),
|
|
1024
|
+
...(observation?.cpuMs !== undefined ? { cpuMs: observation.cpuMs } : {}),
|
|
1025
|
+
...(worker !== undefined ? { worker } : {}),
|
|
1023
1026
|
});
|
|
1024
1027
|
}
|
|
1025
1028
|
/**
|
|
@@ -1040,6 +1043,9 @@ export function superviseSessionLeases(sessions, now = new Date().toISOString())
|
|
|
1040
1043
|
unreadable: [],
|
|
1041
1044
|
proposals: [],
|
|
1042
1045
|
};
|
|
1046
|
+
const procView = readFleetProcView();
|
|
1047
|
+
// R3: prior-boot corpses are reaped silently before any proposal pass.
|
|
1048
|
+
store.reapDeadBootLeases(procView);
|
|
1043
1049
|
const leases = store.readAll();
|
|
1044
1050
|
const byId = new Map(leases.map((lease) => [lease.sessionId, lease]));
|
|
1045
1051
|
for (const step of planLeaseSupervision(leases, sessions)) {
|
|
@@ -1055,27 +1061,77 @@ export function superviseSessionLeases(sessions, now = new Date().toISOString())
|
|
|
1055
1061
|
if (step.action === "leave")
|
|
1056
1062
|
continue;
|
|
1057
1063
|
const pane = step.tmuxSession ? resolveAgentPane(step.tmuxSession) : undefined;
|
|
1064
|
+
let currentLease = lease;
|
|
1065
|
+
// R1 lazy migration: old leases remain alive until this pass can resolve
|
|
1066
|
+
// their worker. Resolve from the pass-wide snapshot, never by spawning a
|
|
1067
|
+
// /proc walk for every lease.
|
|
1068
|
+
if (currentLease.worker === undefined && pane !== undefined) {
|
|
1069
|
+
const worker = paneWorkerAttributionFromView(pane, procView);
|
|
1070
|
+
if (worker !== undefined) {
|
|
1071
|
+
currentLease = store.populateWorker({
|
|
1072
|
+
sessionId: currentLease.sessionId,
|
|
1073
|
+
token: currentLease.token,
|
|
1074
|
+
worker,
|
|
1075
|
+
});
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
if (!isSessionLeaseAlive(currentLease, procView, now)) {
|
|
1079
|
+
result.unreadable.push(currentLease.sessionId);
|
|
1080
|
+
continue;
|
|
1081
|
+
}
|
|
1058
1082
|
const cpuMsNow = pane ? paneTreeCpuMs(pane) : undefined;
|
|
1059
|
-
const decision = decideSessionBeat({ lease, cpuMsNow, now });
|
|
1083
|
+
const decision = decideSessionBeat({ lease: currentLease, cpuMsNow, now });
|
|
1060
1084
|
if (decision.action === "beat" && cpuMsNow !== undefined) {
|
|
1061
|
-
store.heartbeat({ sessionId:
|
|
1062
|
-
result.beaten.push(
|
|
1085
|
+
store.heartbeat({ sessionId: currentLease.sessionId, token: currentLease.token, cpuMs: cpuMsNow });
|
|
1086
|
+
result.beaten.push(currentLease.sessionId);
|
|
1063
1087
|
}
|
|
1064
1088
|
else if (decision.action === "observe" && cpuMsNow !== undefined) {
|
|
1065
|
-
store.observe({ sessionId:
|
|
1066
|
-
result.observed.push(
|
|
1089
|
+
store.observe({ sessionId: currentLease.sessionId, token: currentLease.token, cpuMs: cpuMsNow });
|
|
1090
|
+
result.observed.push(currentLease.sessionId);
|
|
1067
1091
|
}
|
|
1068
1092
|
else {
|
|
1069
|
-
result.unreadable.push(
|
|
1093
|
+
result.unreadable.push(currentLease.sessionId);
|
|
1070
1094
|
}
|
|
1071
1095
|
}
|
|
1072
1096
|
catch {
|
|
1073
1097
|
// a rejected write (raced re-acquisition, torn store) must not stop the pass
|
|
1074
1098
|
}
|
|
1075
1099
|
}
|
|
1076
|
-
result.proposals = reclaimProposals(store.readAll(), now);
|
|
1100
|
+
result.proposals = reclaimProposals(store.readAll(), now, procView);
|
|
1077
1101
|
return result;
|
|
1078
1102
|
}
|
|
1103
|
+
/** Relaunch one managed session while its lease carries the bounded protection. */
|
|
1104
|
+
export function relaunchSessionWithLease(input) {
|
|
1105
|
+
const store = sessionLeaseStore();
|
|
1106
|
+
const lease = store.forSession(input.sessionId);
|
|
1107
|
+
if (lease === undefined)
|
|
1108
|
+
return relaunchInSession(input.tmuxSession, input.command);
|
|
1109
|
+
const pane = resolveAgentPane(input.tmuxSession);
|
|
1110
|
+
const outcome = store.relaunch({
|
|
1111
|
+
sessionId: lease.sessionId,
|
|
1112
|
+
token: lease.token,
|
|
1113
|
+
...(input.resolvingBoundMs !== undefined
|
|
1114
|
+
? { resolvingBoundMs: input.resolvingBoundMs }
|
|
1115
|
+
: {}),
|
|
1116
|
+
act: () => relaunchInSession(input.tmuxSession, input.command),
|
|
1117
|
+
resolveWorker: () => (pane ? paneWorkerAttribution(pane) : undefined),
|
|
1118
|
+
});
|
|
1119
|
+
return outcome.succeeded;
|
|
1120
|
+
}
|
|
1121
|
+
/** One on-demand fleet-wide read-back audit; it never runs from rendering. */
|
|
1122
|
+
export function auditLiveSessionLeases(sessions = listJobs(), now = new Date().toISOString()) {
|
|
1123
|
+
const store = sessionLeaseStore();
|
|
1124
|
+
const leases = store.readAll();
|
|
1125
|
+
const procView = readFleetProcView();
|
|
1126
|
+
const report = auditSessionLeaseFleet(sessions.map((session) => ({
|
|
1127
|
+
sessionId: session.id,
|
|
1128
|
+
...(session.tmuxSession !== undefined ? { tmuxSession: session.tmuxSession } : {}),
|
|
1129
|
+
...(session.jobState !== undefined ? { jobState: session.jobState } : {}),
|
|
1130
|
+
})), leases, listH2aStatusSurfaces(), procView, now);
|
|
1131
|
+
const liveLeaseCount = countLiveSessionLeases(leases, procView, now);
|
|
1132
|
+
const reaped = store.reapDeadBootLeases(procView);
|
|
1133
|
+
return { ...report, reaped, liveLeaseCount, procView };
|
|
1134
|
+
}
|
|
1079
1135
|
/** Human-readable duration for lease reporting ("41m", "2h13m"). */
|
|
1080
1136
|
function formatIdle(ms) {
|
|
1081
1137
|
const minutes = Math.floor(ms / 60_000);
|
|
@@ -4553,11 +4609,14 @@ export async function main(argv) {
|
|
|
4553
4609
|
.command("leases")
|
|
4554
4610
|
.description("Session slot leases (machine-scoped): who holds a concurrency slot, when it last proved it was working, and which slots are reclaimable. READ-ONLY — it kills nothing and frees nothing.")
|
|
4555
4611
|
.option("--probe", "before reporting, run one supervising pass: beat the leases of sessions whose pane is burning CPU, record the others")
|
|
4612
|
+
.option("--audit", "run one on-demand fleet-wide read-back audit (one tmux FORMAT call and one /proc scan)")
|
|
4556
4613
|
.action((opts) => {
|
|
4557
4614
|
const now = new Date().toISOString();
|
|
4558
4615
|
if (opts.probe === true)
|
|
4559
4616
|
superviseSessionLeases(listJobs(), now);
|
|
4560
|
-
const
|
|
4617
|
+
const audit = opts.audit === true ? auditLiveSessionLeases(listJobs(), now) : undefined;
|
|
4618
|
+
const store = sessionLeaseStore();
|
|
4619
|
+
const leases = store.readAll();
|
|
4561
4620
|
if (leases.length === 0) {
|
|
4562
4621
|
process.stdout.write("no session leases\n");
|
|
4563
4622
|
return;
|
|
@@ -4568,17 +4627,33 @@ export async function main(argv) {
|
|
|
4568
4627
|
.sort((a, b) => Date.parse(a.heartbeatAt) - Date.parse(b.heartbeatAt))
|
|
4569
4628
|
.map((lease) => {
|
|
4570
4629
|
const idle = nowMs - Date.parse(lease.heartbeatAt);
|
|
4571
|
-
const state =
|
|
4630
|
+
const state = audit
|
|
4631
|
+
? isSessionLeaseAlive(lease, audit.procView, now)
|
|
4632
|
+
? "held"
|
|
4633
|
+
: "RECLAIMABLE"
|
|
4634
|
+
: isSessionLeaseAbandoned(lease, now)
|
|
4635
|
+
? "RECLAIMABLE"
|
|
4636
|
+
: "held";
|
|
4572
4637
|
return (`${lease.sessionId.padEnd(24)} ${state.padEnd(12)} ` +
|
|
4573
4638
|
`idle ${formatIdle(idle).padEnd(7)} ttl ${formatIdle(lease.ttlMs).padEnd(6)} ` +
|
|
4574
4639
|
`holder ${lease.holder}`);
|
|
4575
4640
|
});
|
|
4576
4641
|
process.stdout.write(`${rows.join("\n")}\n`);
|
|
4577
|
-
const reclaimable =
|
|
4642
|
+
const reclaimable = audit
|
|
4643
|
+
? reclaimProposals(leases, now, audit.procView).length
|
|
4644
|
+
: reclaimProposals(leases, now).length;
|
|
4578
4645
|
if (reclaimable > 0) {
|
|
4579
4646
|
process.stderr.write(`[h2a] ${reclaimable} slot(s) reclaimable — free one with: h2a jobs reclaim <id> ` +
|
|
4580
4647
|
"(the session's tmux window is left running and can still be attached)\n");
|
|
4581
4648
|
}
|
|
4649
|
+
if (audit) {
|
|
4650
|
+
if (audit.reaped.length > 0) {
|
|
4651
|
+
process.stderr.write(`[h2a] audit reaped prior-boot lease(s): ${audit.reaped.join(", ")}\n`);
|
|
4652
|
+
}
|
|
4653
|
+
if (audit.missingStatusSurface.length > 0) {
|
|
4654
|
+
process.stderr.write(`[h2a] audit status-surface drift: ${audit.missingStatusSurface.join(", ")}\n`);
|
|
4655
|
+
}
|
|
4656
|
+
}
|
|
4582
4657
|
});
|
|
4583
4658
|
jobsCommand
|
|
4584
4659
|
.command("reclaim <id>")
|
|
@@ -4586,15 +4661,21 @@ export async function main(argv) {
|
|
|
4586
4661
|
.action((id) => {
|
|
4587
4662
|
const now = new Date().toISOString();
|
|
4588
4663
|
const store = sessionLeaseStore();
|
|
4589
|
-
|
|
4664
|
+
let lease = store.forSession(id);
|
|
4590
4665
|
if (!lease) {
|
|
4591
4666
|
process.stderr.write(`[h2a] no session lease for "${id}" — nothing to reclaim (see: h2a jobs leases)\n`);
|
|
4592
4667
|
process.exitCode = 1;
|
|
4593
4668
|
return;
|
|
4594
4669
|
}
|
|
4670
|
+
const procView = readFleetProcView();
|
|
4671
|
+
if (isSessionLeaseDeadBoot(lease, procView)) {
|
|
4672
|
+
store.reapDeadBootLeases(procView);
|
|
4673
|
+
process.stdout.write(`reaped ${id} — lease belonged to a prior boot\n`);
|
|
4674
|
+
return;
|
|
4675
|
+
}
|
|
4595
4676
|
// The one guard that matters: a session still proving it works keeps its
|
|
4596
4677
|
// slot, whatever the operator believes. Reclaiming is not arbitration.
|
|
4597
|
-
if (
|
|
4678
|
+
if (isSessionLeaseAlive(lease, procView, now)) {
|
|
4598
4679
|
const idle = Date.parse(now) - Date.parse(lease.heartbeatAt);
|
|
4599
4680
|
process.stderr.write(`[h2a] refusing to reclaim ${id}: its lease still beats (last proof of work ` +
|
|
4600
4681
|
`${formatIdle(idle)} ago, TTL ${formatIdle(lease.ttlMs)}). It is working.\n`);
|
|
@@ -5477,7 +5558,10 @@ export async function main(argv) {
|
|
|
5477
5558
|
})(),
|
|
5478
5559
|
slug: s.slug,
|
|
5479
5560
|
name: s.name,
|
|
5480
|
-
|
|
5561
|
+
...(() => {
|
|
5562
|
+
const safety = sessionRelaunchSafety(s.name);
|
|
5563
|
+
return { ...safety, livenessReason: safety.reason };
|
|
5564
|
+
})(),
|
|
5481
5565
|
})), {
|
|
5482
5566
|
...(forced ? { force: true } : {}),
|
|
5483
5567
|
...(opts.all && !opts.includeAgents
|
|
@@ -5510,7 +5594,15 @@ export async function main(argv) {
|
|
|
5510
5594
|
if (!forced) {
|
|
5511
5595
|
let ok = 0;
|
|
5512
5596
|
for (const action of plan.actions) {
|
|
5513
|
-
|
|
5597
|
+
const sessionId = entryByTmuxSession.get(action.name)?.id;
|
|
5598
|
+
const relaunched = sessionId !== undefined
|
|
5599
|
+
? relaunchSessionWithLease({
|
|
5600
|
+
sessionId,
|
|
5601
|
+
tmuxSession: action.name,
|
|
5602
|
+
command: action.cmd,
|
|
5603
|
+
})
|
|
5604
|
+
: relaunchInSession(action.name, action.cmd);
|
|
5605
|
+
if (relaunched) {
|
|
5514
5606
|
ok += 1;
|
|
5515
5607
|
process.stderr.write(`[h2a] relaunched ${action.slug}: ${action.cmd}\n`);
|
|
5516
5608
|
}
|
|
@@ -5576,8 +5668,17 @@ export async function main(argv) {
|
|
|
5576
5668
|
process.exitCode = 1;
|
|
5577
5669
|
return;
|
|
5578
5670
|
}
|
|
5671
|
+
const finalReady = [];
|
|
5672
|
+
for (const item of ready) {
|
|
5673
|
+
const safety = sessionRelaunchSafety(item.action.name);
|
|
5674
|
+
if (safety.activelyWorking) {
|
|
5675
|
+
process.stderr.write(`[h2a] skipped ${item.action.slug}: ${safety.reason}\n`);
|
|
5676
|
+
continue;
|
|
5677
|
+
}
|
|
5678
|
+
finalReady.push(item);
|
|
5679
|
+
}
|
|
5579
5680
|
let ok = 0;
|
|
5580
|
-
for (const { action, entry, sessionClass } of
|
|
5681
|
+
for (const { action, entry, sessionClass } of finalReady) {
|
|
5581
5682
|
if (!killLocalSession(action.name)) {
|
|
5582
5683
|
process.stderr.write(`[h2a] FAILED to force-restart ${action.slug}: tmux session ${action.name} could not be killed.\n`);
|
|
5583
5684
|
continue;
|
|
@@ -5701,6 +5802,9 @@ export async function main(argv) {
|
|
|
5701
5802
|
});
|
|
5702
5803
|
let nudged = 0;
|
|
5703
5804
|
if (apply) {
|
|
5805
|
+
const leaseIdByTmuxSession = new Map(loadRegistry()
|
|
5806
|
+
.filter((entry) => entry.tmuxSession !== undefined)
|
|
5807
|
+
.map((entry) => [entry.tmuxSession, entry.id]));
|
|
5704
5808
|
for (const r of plan.toResume) {
|
|
5705
5809
|
// DOUBLE-CHECK the attached guard at the moment of action (the pure plan
|
|
5706
5810
|
// already excluded attached panes, but re-read in case a human just
|
|
@@ -5709,7 +5813,14 @@ export async function main(argv) {
|
|
|
5709
5813
|
process.stderr.write(`[h2a] ${r.name} (${r.type}) just got attached — skipping the nudge\n`);
|
|
5710
5814
|
continue;
|
|
5711
5815
|
}
|
|
5712
|
-
const
|
|
5816
|
+
const sessionId = leaseIdByTmuxSession.get(r.name);
|
|
5817
|
+
const ok = sessionId !== undefined
|
|
5818
|
+
? relaunchSessionWithLease({
|
|
5819
|
+
sessionId,
|
|
5820
|
+
tmuxSession: r.name,
|
|
5821
|
+
command: interactiveResumeNudge(r.type),
|
|
5822
|
+
})
|
|
5823
|
+
: relaunchInSession(r.name, interactiveResumeNudge(r.type));
|
|
5713
5824
|
if (ok) {
|
|
5714
5825
|
nudged += 1;
|
|
5715
5826
|
throttleState.set(r.name, r.next);
|