@sentropic/h2a-runtime 0.89.0 → 0.90.1
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 +7 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +223 -46
- package/dist/index.js.map +1 -1
- package/dist/relaunch.d.ts +20 -1
- package/dist/relaunch.d.ts.map +1 -1
- package/dist/relaunch.js +31 -10
- package/dist/relaunch.js.map +1 -1
- package/dist/tmux.d.ts +40 -1
- package/dist/tmux.d.ts.map +1 -1
- package/dist/tmux.js +130 -6
- package/dist/tmux.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -142,6 +142,13 @@ export { main as dispatch };
|
|
|
142
142
|
*/
|
|
143
143
|
export declare function projectAgentsForH2a(): ReturnType<typeof projectRemoteAgents>;
|
|
144
144
|
export { projectStatusForH2a };
|
|
145
|
+
/**
|
|
146
|
+
* Status-bar writer surface, consumed by `@sentropic/h2a`'s
|
|
147
|
+
* `status --write-bars` loop via lazy `import()`: enumerate the sessions whose
|
|
148
|
+
* installed bar expects prepared files, and resolve where those files live.
|
|
149
|
+
*/
|
|
150
|
+
export { listStatusBarTargets, statusBarFilesForSession, statusBarRoot } from "./tmux.js";
|
|
151
|
+
export type { StatusBarTarget } from "./tmux.js";
|
|
145
152
|
/** Open the detailed watcher only for an exact, currently managed session. */
|
|
146
153
|
export declare function openStatusWindowForH2a(session: string): boolean;
|
|
147
154
|
/**
|
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,16 +15,16 @@ 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, 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, 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";
|
|
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";
|
|
22
22
|
import { projectStatusForH2a } from "./status-projection.js";
|
|
23
23
|
import { buildAgentLaunchArgs, isAgentLaunchEffort, isAgentLaunchProfile, } from "./agent-launch-args.js";
|
|
24
24
|
import { planRelaunch } from "./relaunch.js";
|
|
25
|
-
import { isHumanFacingSession, readLastLayout, restore as restoreLayout, } from "./restore.js";
|
|
25
|
+
import { isHumanFacingSession, readConversationCustomTitle, readLastLayout, reconcileRunConvIds, restore as restoreLayout, } from "./restore.js";
|
|
26
26
|
import { getLayoutConfig } from "./config.js";
|
|
27
|
-
import { advanceJob, coerceRegistryTool, enroll, enrollFromRun, isLive, listJobs, listLocalForLs, loadRegistry, resolveLocalTmuxSessionForName, tryClaimSlot, withRegistryLock, resolveRegistryPath, } from "./registry.js";
|
|
27
|
+
import { advanceJob, coerceRegistryTool, enroll, enrollFromRun, isLive, listJobs, listLocalForLs, loadRegistry, loadRegistryWithDiagnostics, resolveLocalTmuxSessionForName, tryClaimSlot, withRegistryLock, resolveRegistryPath, } from "./registry.js";
|
|
28
28
|
import { SessionLeaseStore, decideSessionBeat, isSessionLeaseAbandoned, 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";
|
|
@@ -1422,7 +1422,7 @@ async function prepareLlmMeshForRestore(opts = {}) {
|
|
|
1422
1422
|
process.stderr.write(`[h2a] llm-mesh: restore context active (${gateway})\n`);
|
|
1423
1423
|
}
|
|
1424
1424
|
}
|
|
1425
|
-
function
|
|
1425
|
+
function registryEntriesForLocalTmuxTarget(target, local, entries = loadRegistry()) {
|
|
1426
1426
|
const parsedTarget = parseManagedSessionName(target);
|
|
1427
1427
|
const canonicalSlug = local?.slug ?? parsedTarget?.slug ?? target;
|
|
1428
1428
|
const tmuxSession = local?.name ?? target;
|
|
@@ -1431,19 +1431,8 @@ function registryEntryForResumeTarget(target, local) {
|
|
|
1431
1431
|
: parsedTarget
|
|
1432
1432
|
? [target]
|
|
1433
1433
|
: managedSessionCandidates(canonicalSlug);
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
// or an explicit background launch must never become resumable here.
|
|
1437
|
-
//
|
|
1438
|
-
// The class test is SHARED with restore rather than written inline, because
|
|
1439
|
-
// written inline it was an opt-in on an explicit class and every record
|
|
1440
|
-
// enrolled before the class existed failed closed — measured as "cannot
|
|
1441
|
-
// resume <slug>: registry has no human session" on entries that are plainly
|
|
1442
|
-
// human sessions. Two gates judging the same question in two ways is how one
|
|
1443
|
-
// of them ends up wrong on its own.
|
|
1444
|
-
if (e.role !== undefined || e.kind !== "local-tmux")
|
|
1445
|
-
return false;
|
|
1446
|
-
if (!isHumanFacingSession(e))
|
|
1434
|
+
return entries.filter((e) => {
|
|
1435
|
+
if (e.kind !== "local-tmux")
|
|
1447
1436
|
return false;
|
|
1448
1437
|
// Full managed names are exact targets; never reinterpret one as an id
|
|
1449
1438
|
// or label that happens to share a prefix-shaped string.
|
|
@@ -1458,6 +1447,16 @@ function registryEntryForResumeTarget(target, local) {
|
|
|
1458
1447
|
e.tmuxSession === tmuxSession ||
|
|
1459
1448
|
(e.tmuxSession !== undefined && candidates.includes(e.tmuxSession)));
|
|
1460
1449
|
});
|
|
1450
|
+
}
|
|
1451
|
+
function registryEntryForResumeTarget(target, local) {
|
|
1452
|
+
const matches = registryEntriesForLocalTmuxTarget(target, local).filter((e) => {
|
|
1453
|
+
// Resume is a human-facing operation, not a promotion path. Share the
|
|
1454
|
+
// durable class test with restore so legacy human rows remain resumable,
|
|
1455
|
+
// while delegated jobs and explicit background launches stay excluded.
|
|
1456
|
+
if (e.role !== undefined)
|
|
1457
|
+
return false;
|
|
1458
|
+
return isHumanFacingSession(e);
|
|
1459
|
+
});
|
|
1461
1460
|
const ids = new Set(matches.map((e) => e.id));
|
|
1462
1461
|
if (ids.size !== 1)
|
|
1463
1462
|
return undefined;
|
|
@@ -1512,6 +1511,23 @@ async function confirmReplace(slug) {
|
|
|
1512
1511
|
rl.close();
|
|
1513
1512
|
}
|
|
1514
1513
|
}
|
|
1514
|
+
async function confirmRelaunchAll() {
|
|
1515
|
+
if (process.stdin.isTTY !== true || process.stderr.isTTY !== true) {
|
|
1516
|
+
return false;
|
|
1517
|
+
}
|
|
1518
|
+
const { createInterface: createPromisesInterface } = await import("node:readline/promises");
|
|
1519
|
+
const rl = createPromisesInterface({
|
|
1520
|
+
input: process.stdin,
|
|
1521
|
+
output: process.stderr,
|
|
1522
|
+
});
|
|
1523
|
+
try {
|
|
1524
|
+
const answer = await rl.question('Type "RELAUNCH" to force-restart every listed session: ');
|
|
1525
|
+
return answer.trim() === "RELAUNCH";
|
|
1526
|
+
}
|
|
1527
|
+
finally {
|
|
1528
|
+
rl.close();
|
|
1529
|
+
}
|
|
1530
|
+
}
|
|
1515
1531
|
function resolveUrlAndSessionId(first, second) {
|
|
1516
1532
|
if (second !== undefined) {
|
|
1517
1533
|
return { url: getConfiguredRemote(first), sessionId: second };
|
|
@@ -5372,42 +5388,105 @@ export async function main(argv) {
|
|
|
5372
5388
|
process.stderr.write("[h2a] wake-request watch stopped\n");
|
|
5373
5389
|
});
|
|
5374
5390
|
// ---------------------------------------------------------------------------
|
|
5375
|
-
// relaunch —
|
|
5391
|
+
// relaunch — recover idle sessions, or explicitly force-recreate one/all
|
|
5376
5392
|
// ---------------------------------------------------------------------------
|
|
5377
5393
|
program
|
|
5378
5394
|
.command("relaunch [filter]")
|
|
5379
|
-
.description("
|
|
5395
|
+
.description("Dry-run local session recovery by default. With an exact [filter], --apply force-recreates that one session from its own registry conversation. --all force-recreates managed sessions only after a warning and typed confirmation (or --yes); interactive agent CLIs stay excluded unless --include-agents is explicit.")
|
|
5380
5396
|
.option("--apply", "actually relaunch (default: dry-run, just print the plan)")
|
|
5381
|
-
.
|
|
5397
|
+
.option("--all", "force-restart every managed local tmux session")
|
|
5398
|
+
.option("--yes", "confirm --all non-interactively (requires --apply)")
|
|
5399
|
+
.option("--include-agents", "with --all, include claude/codex/agy sessions that are excluded by default")
|
|
5400
|
+
.action(async (filter, opts) => {
|
|
5382
5401
|
if (!tmuxAvailable()) {
|
|
5383
5402
|
process.stderr.write("[h2a] tmux is not installed locally\n");
|
|
5384
5403
|
process.exitCode = 1;
|
|
5385
5404
|
return;
|
|
5386
5405
|
}
|
|
5387
|
-
|
|
5388
|
-
|
|
5389
|
-
|
|
5390
|
-
|
|
5391
|
-
|
|
5392
|
-
|
|
5393
|
-
|
|
5394
|
-
|
|
5395
|
-
|
|
5396
|
-
|
|
5397
|
-
|
|
5406
|
+
if (filter && opts.all) {
|
|
5407
|
+
process.stderr.write("[h2a] relaunch accepts either one exact session name or --all, not both.\n");
|
|
5408
|
+
process.exitCode = 1;
|
|
5409
|
+
return;
|
|
5410
|
+
}
|
|
5411
|
+
const inventory = listLocalSessionsWithDiagnostics();
|
|
5412
|
+
if (!inventory.known) {
|
|
5413
|
+
process.stderr.write(`[h2a] cannot safely relaunch: managed tmux inventory is unknown (${inventory.reason ?? "no detail"}).\n`);
|
|
5414
|
+
process.exitCode = 1;
|
|
5415
|
+
return;
|
|
5416
|
+
}
|
|
5417
|
+
let sessions = inventory.sessions;
|
|
5418
|
+
if (filter) {
|
|
5419
|
+
const exactName = parseManagedSessionName(filter) ? filter : undefined;
|
|
5420
|
+
const matches = exactName
|
|
5421
|
+
? sessions.filter((session) => session.name === exactName)
|
|
5422
|
+
: sessions.filter((session) => session.slug === filter);
|
|
5423
|
+
if (matches.length !== 1) {
|
|
5424
|
+
const detail = matches.length
|
|
5425
|
+
? `ambiguous (${matches.map((session) => session.name).join(", ")})`
|
|
5426
|
+
: "not found";
|
|
5427
|
+
process.stderr.write(`[h2a] cannot force-relaunch ${filter}: exact managed session is ${detail}.\n`);
|
|
5428
|
+
process.exitCode = 1;
|
|
5429
|
+
return;
|
|
5430
|
+
}
|
|
5431
|
+
sessions = matches;
|
|
5398
5432
|
}
|
|
5399
|
-
const
|
|
5433
|
+
const forced = Boolean(filter || opts.all);
|
|
5434
|
+
const registry = loadRegistryWithDiagnostics();
|
|
5435
|
+
if (forced && !registry.known) {
|
|
5436
|
+
process.stderr.write(`[h2a] cannot safely force-relaunch: registry is unknown (${registry.reason ?? "no detail"}).\n`);
|
|
5437
|
+
process.exitCode = 1;
|
|
5438
|
+
return;
|
|
5439
|
+
}
|
|
5440
|
+
const registryEntries = registry.entries;
|
|
5441
|
+
const resolution = forced
|
|
5442
|
+
? reconcileRunConvIds(registryEntries, (cwd, convId) => readConversationCustomTitle(homedir(), cwd, convId))
|
|
5443
|
+
: undefined;
|
|
5444
|
+
const entryByTmuxSession = new Map();
|
|
5400
5445
|
const plan = planRelaunch(sessions.map((s) => ({
|
|
5446
|
+
...(() => {
|
|
5447
|
+
const matches = registryEntriesForLocalTmuxTarget(s.name, s, registryEntries);
|
|
5448
|
+
const ids = new Set(matches.map((entry) => entry.id));
|
|
5449
|
+
const entry = matches.length === 1 && ids.size === 1
|
|
5450
|
+
? matches
|
|
5451
|
+
.slice()
|
|
5452
|
+
.sort((a, b) => b.lastSeenAt.localeCompare(a.lastSeenAt))[0]
|
|
5453
|
+
: undefined;
|
|
5454
|
+
if (entry)
|
|
5455
|
+
entryByTmuxSession.set(s.name, entry);
|
|
5456
|
+
const resolvedConvId = entry
|
|
5457
|
+
? resolution?.resolvedSid.get(entry.id) ?? entry.convId
|
|
5458
|
+
: undefined;
|
|
5459
|
+
const labelOnly = entry !== undefined &&
|
|
5460
|
+
(!resolvedConvId ||
|
|
5461
|
+
resolvedConvId === entry.id ||
|
|
5462
|
+
resolvedConvId === entry.label);
|
|
5463
|
+
return {
|
|
5464
|
+
profile: entry?.tool ?? s.profile,
|
|
5465
|
+
...(matches.length > 1 || ids.size > 1
|
|
5466
|
+
? { unresumableReason: "multiple registry rows match this tmux session — relaunch refused" }
|
|
5467
|
+
: !entry
|
|
5468
|
+
? { unresumableReason: "no matching registry conversation — relaunch refused" }
|
|
5469
|
+
: entry.sessionClass === undefined
|
|
5470
|
+
? { unresumableReason: "registry session class is missing — relaunch refused" }
|
|
5471
|
+
: labelOnly || resolution?.unresolvedRunIds.has(entry.id)
|
|
5472
|
+
? { unresumableReason: "no resumable conversation in the registry — relaunch refused" }
|
|
5473
|
+
: resolvedConvId !== undefined
|
|
5474
|
+
? { convId: resolvedConvId }
|
|
5475
|
+
: { unresumableReason: "no resumable conversation in the registry — relaunch refused" }),
|
|
5476
|
+
};
|
|
5477
|
+
})(),
|
|
5401
5478
|
slug: s.slug,
|
|
5402
5479
|
name: s.name,
|
|
5403
|
-
profile: s.profile,
|
|
5404
5480
|
idle: localSessionIdle(s.name),
|
|
5405
|
-
|
|
5406
|
-
|
|
5407
|
-
|
|
5408
|
-
|
|
5409
|
-
|
|
5410
|
-
})
|
|
5481
|
+
})), {
|
|
5482
|
+
...(forced ? { force: true } : {}),
|
|
5483
|
+
...(opts.all && !opts.includeAgents
|
|
5484
|
+
? { excludeInteractiveAgents: true }
|
|
5485
|
+
: {}),
|
|
5486
|
+
});
|
|
5487
|
+
if (opts.all) {
|
|
5488
|
+
process.stderr.write(`[h2a] WARNING: force-restart plan covers ${sessions.length} managed session(s): ${sessions.map((session) => session.name).join(", ") || "none"}\n`);
|
|
5489
|
+
}
|
|
5411
5490
|
if (plan.actions.length === 0) {
|
|
5412
5491
|
process.stderr.write(`[h2a] nothing to relaunch${filter ? ` matching "${filter}"` : ""} (${plan.skipped.length} skipped)\n`);
|
|
5413
5492
|
for (const s of plan.skipped) {
|
|
@@ -5416,7 +5495,7 @@ export async function main(argv) {
|
|
|
5416
5495
|
return;
|
|
5417
5496
|
}
|
|
5418
5497
|
if (!opts.apply) {
|
|
5419
|
-
process.stderr.write(`[h2a] would relaunch ${plan.actions.length} session(s) — dry-run, pass --apply:\n`);
|
|
5498
|
+
process.stderr.write(`[h2a] would${forced ? " force-restart" : " relaunch"} ${plan.actions.length} session(s) — dry-run, pass --apply:\n`);
|
|
5420
5499
|
for (const a of plan.actions) {
|
|
5421
5500
|
process.stderr.write(` ${a.slug}: ${a.cmd}\n`);
|
|
5422
5501
|
}
|
|
@@ -5425,17 +5504,109 @@ export async function main(argv) {
|
|
|
5425
5504
|
}
|
|
5426
5505
|
return;
|
|
5427
5506
|
}
|
|
5507
|
+
// Backward-compatible crash recovery: bare `relaunch --apply` only
|
|
5508
|
+
// retypes into idle shells. Forced kill/recreate is available solely for
|
|
5509
|
+
// one exact target or explicit `--all`.
|
|
5510
|
+
if (!forced) {
|
|
5511
|
+
let ok = 0;
|
|
5512
|
+
for (const action of plan.actions) {
|
|
5513
|
+
if (relaunchInSession(action.name, action.cmd)) {
|
|
5514
|
+
ok += 1;
|
|
5515
|
+
process.stderr.write(`[h2a] relaunched ${action.slug}: ${action.cmd}\n`);
|
|
5516
|
+
}
|
|
5517
|
+
else {
|
|
5518
|
+
process.stderr.write(`[h2a] FAILED to relaunch ${action.slug}\n`);
|
|
5519
|
+
}
|
|
5520
|
+
}
|
|
5521
|
+
process.stderr.write(`[h2a] relaunched ${ok}/${plan.actions.length}${plan.skipped.length ? `, ${plan.skipped.length} skipped` : ""}\n`);
|
|
5522
|
+
if (ok !== plan.actions.length)
|
|
5523
|
+
process.exitCode = 1;
|
|
5524
|
+
return;
|
|
5525
|
+
}
|
|
5526
|
+
if (opts.all && !opts.yes && !(await confirmRelaunchAll())) {
|
|
5527
|
+
process.stderr.write("[h2a] bulk force-restart requires typing RELAUNCH, or pass --yes for non-interactive use. No session was changed.\n");
|
|
5528
|
+
process.exitCode = 1;
|
|
5529
|
+
return;
|
|
5530
|
+
}
|
|
5531
|
+
for (const skipped of plan.skipped) {
|
|
5532
|
+
process.stderr.write(`[h2a] skipped ${skipped.slug}: ${skipped.reason}\n`);
|
|
5533
|
+
}
|
|
5534
|
+
if (filter) {
|
|
5535
|
+
process.stderr.write(`[h2a] confirmed force-restart of ${plan.actions[0].slug}; its tmux session will be replaced.\n`);
|
|
5536
|
+
}
|
|
5537
|
+
const ready = [];
|
|
5538
|
+
let preflightFailed = false;
|
|
5539
|
+
for (const action of plan.actions) {
|
|
5540
|
+
const entry = entryByTmuxSession.get(action.name);
|
|
5541
|
+
const sessionClass = entry?.sessionClass;
|
|
5542
|
+
if (!entry || sessionClass === undefined) {
|
|
5543
|
+
preflightFailed = true;
|
|
5544
|
+
process.stderr.write(`[h2a] cannot force-restart ${action.slug}: its registry launch context changed; no session was killed.\n`);
|
|
5545
|
+
continue;
|
|
5546
|
+
}
|
|
5547
|
+
const allowed = await guardConvWriters({
|
|
5548
|
+
convId: action.convId,
|
|
5549
|
+
cwd: entry.cwd,
|
|
5550
|
+
excludeId: entry.id,
|
|
5551
|
+
fetchRemoteSessions: async () => {
|
|
5552
|
+
const url = getConfiguredRemoteOptional();
|
|
5553
|
+
return url ? await listRemoteSessions(url) : [];
|
|
5554
|
+
},
|
|
5555
|
+
});
|
|
5556
|
+
if (!allowed) {
|
|
5557
|
+
preflightFailed = true;
|
|
5558
|
+
continue;
|
|
5559
|
+
}
|
|
5560
|
+
ready.push({ action, entry, sessionClass });
|
|
5561
|
+
}
|
|
5562
|
+
const rechecked = listLocalSessionsWithDiagnostics();
|
|
5563
|
+
if (!rechecked.known) {
|
|
5564
|
+
process.stderr.write(`[h2a] cannot safely force-restart: managed tmux inventory changed (${rechecked.reason ?? "no detail"}). No session was killed.\n`);
|
|
5565
|
+
process.exitCode = 1;
|
|
5566
|
+
return;
|
|
5567
|
+
}
|
|
5568
|
+
for (const { action } of ready) {
|
|
5569
|
+
if (!rechecked.sessions.some((session) => session.name === action.name)) {
|
|
5570
|
+
preflightFailed = true;
|
|
5571
|
+
process.stderr.write(`[h2a] cannot force-restart ${action.slug}: its tmux session changed before replacement. No session was killed.\n`);
|
|
5572
|
+
}
|
|
5573
|
+
}
|
|
5574
|
+
if (preflightFailed) {
|
|
5575
|
+
process.stderr.write("[h2a] force-restart aborted before any session was killed.\n");
|
|
5576
|
+
process.exitCode = 1;
|
|
5577
|
+
return;
|
|
5578
|
+
}
|
|
5428
5579
|
let ok = 0;
|
|
5429
|
-
for (const
|
|
5430
|
-
if (
|
|
5580
|
+
for (const { action, entry, sessionClass } of ready) {
|
|
5581
|
+
if (!killLocalSession(action.name)) {
|
|
5582
|
+
process.stderr.write(`[h2a] FAILED to force-restart ${action.slug}: tmux session ${action.name} could not be killed.\n`);
|
|
5583
|
+
continue;
|
|
5584
|
+
}
|
|
5585
|
+
try {
|
|
5586
|
+
const gatewayMode = gatewayModeForProfile(action.profile, entry.gatewayMode ?? "auto");
|
|
5587
|
+
await injectLlmMeshGatewayEnv(gatewayMode, true, localSessionName(action.slug));
|
|
5588
|
+
const { name } = startLocalSession(action.profile, action.command, entry.cwd, action.args, action.slug, undefined, { sessionClass });
|
|
5589
|
+
enrollFromRun({
|
|
5590
|
+
profile: action.profile,
|
|
5591
|
+
slug: action.slug,
|
|
5592
|
+
tmuxSession: name,
|
|
5593
|
+
cwd: entry.cwd,
|
|
5594
|
+
sessionClass,
|
|
5595
|
+
convId: action.convId,
|
|
5596
|
+
...(entry.gatewayMode !== undefined
|
|
5597
|
+
? { gatewayMode: entry.gatewayMode }
|
|
5598
|
+
: {}),
|
|
5599
|
+
});
|
|
5431
5600
|
ok += 1;
|
|
5432
|
-
process.stderr.write(`[h2a]
|
|
5601
|
+
process.stderr.write(`[h2a] force-restarted ${action.slug}: ${action.cmd}\n`);
|
|
5433
5602
|
}
|
|
5434
|
-
|
|
5435
|
-
process.stderr.write(`[h2a] FAILED to
|
|
5603
|
+
catch (error) {
|
|
5604
|
+
process.stderr.write(`[h2a] FAILED to force-restart ${action.slug} after killing ${action.name}: ${error.message}\n`);
|
|
5436
5605
|
}
|
|
5437
5606
|
}
|
|
5438
|
-
process.stderr.write(`[h2a]
|
|
5607
|
+
process.stderr.write(`[h2a] force-restarted ${ok}/${plan.actions.length}${plan.skipped.length ? `, ${plan.skipped.length} skipped` : ""}\n`);
|
|
5608
|
+
if (ok !== plan.actions.length)
|
|
5609
|
+
process.exitCode = 1;
|
|
5439
5610
|
});
|
|
5440
5611
|
// ---------------------------------------------------------------------------
|
|
5441
5612
|
// Reliability slice 2 (throttle phase 2) — auto-resume RATE-LIMITED INTERACTIVE
|
|
@@ -6864,6 +7035,12 @@ export function projectAgentsForH2a() {
|
|
|
6864
7035
|
return projectRemoteAgents({ jobs: listJobs(), localRows: listLocalForLs() });
|
|
6865
7036
|
}
|
|
6866
7037
|
export { projectStatusForH2a };
|
|
7038
|
+
/**
|
|
7039
|
+
* Status-bar writer surface, consumed by `@sentropic/h2a`'s
|
|
7040
|
+
* `status --write-bars` loop via lazy `import()`: enumerate the sessions whose
|
|
7041
|
+
* installed bar expects prepared files, and resolve where those files live.
|
|
7042
|
+
*/
|
|
7043
|
+
export { listStatusBarTargets, statusBarFilesForSession, statusBarRoot } from "./tmux.js";
|
|
6867
7044
|
/** Open the detailed watcher only for an exact, currently managed session. */
|
|
6868
7045
|
export function openStatusWindowForH2a(session) {
|
|
6869
7046
|
const exact = listLocalSessions().find((item) => item.name === session);
|