@sentropic/h2a-runtime 0.85.0 → 0.85.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.
Files changed (60) hide show
  1. package/dist/attach.js +9 -9
  2. package/dist/attach.js.map +1 -1
  3. package/dist/auth-bundle.js +1 -1
  4. package/dist/auth-bundle.js.map +1 -1
  5. package/dist/auth-refresh.js +1 -1
  6. package/dist/auth-refresh.js.map +1 -1
  7. package/dist/browser-bridge-local.js +1 -1
  8. package/dist/browser-bridge-local.js.map +1 -1
  9. package/dist/browser.js +4 -4
  10. package/dist/browser.js.map +1 -1
  11. package/dist/conv-guard.js +6 -6
  12. package/dist/conv-guard.js.map +1 -1
  13. package/dist/convsync.js +1 -1
  14. package/dist/convsync.js.map +1 -1
  15. package/dist/cred-health.js +6 -6
  16. package/dist/cred-health.js.map +1 -1
  17. package/dist/delegate.js +1 -1
  18. package/dist/delegate.js.map +1 -1
  19. package/dist/forward.js +5 -5
  20. package/dist/forward.js.map +1 -1
  21. package/dist/h2a-bridge.js +4 -4
  22. package/dist/h2a-bridge.js.map +1 -1
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +413 -404
  25. package/dist/index.js.map +1 -1
  26. package/dist/interactive-throttle.js +6 -6
  27. package/dist/interactive-throttle.js.map +1 -1
  28. package/dist/llm-mesh.js +1 -1
  29. package/dist/llm-mesh.js.map +1 -1
  30. package/dist/migrate.js +32 -32
  31. package/dist/migrate.js.map +1 -1
  32. package/dist/plugin.js +27 -27
  33. package/dist/plugin.js.map +1 -1
  34. package/dist/pod-liveness.js +1 -1
  35. package/dist/pod-liveness.js.map +1 -1
  36. package/dist/profile-menu.js +3 -3
  37. package/dist/profile-menu.js.map +1 -1
  38. package/dist/registry.d.ts +16 -0
  39. package/dist/registry.d.ts.map +1 -1
  40. package/dist/registry.js +32 -0
  41. package/dist/registry.js.map +1 -1
  42. package/dist/restore.js +9 -9
  43. package/dist/restore.js.map +1 -1
  44. package/dist/skills-sync.js +6 -6
  45. package/dist/skills-sync.js.map +1 -1
  46. package/dist/smoke.js +4 -4
  47. package/dist/smoke.js.map +1 -1
  48. package/dist/soft-refresh.js +15 -15
  49. package/dist/soft-refresh.js.map +1 -1
  50. package/dist/sync.js +1 -1
  51. package/dist/sync.js.map +1 -1
  52. package/dist/tmux.d.ts +1 -1
  53. package/dist/tmux.d.ts.map +1 -1
  54. package/dist/tmux.js +12 -12
  55. package/dist/tmux.js.map +1 -1
  56. package/dist/tunnel.js +2 -2
  57. package/dist/tunnel.js.map +1 -1
  58. package/dist/workspace-sync.js +1 -1
  59. package/dist/workspace-sync.js.map +1 -1
  60. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -18,7 +18,7 @@ import { attachLocalSession, attachPodTmux, capturePane, conductorRunning, curre
18
18
  import { planRelaunch } from "./relaunch.js";
19
19
  import { readLastLayout, restore as restoreLayout, } from "./restore.js";
20
20
  import { getLayoutConfig } from "./config.js";
21
- import { advanceJob, coerceRegistryTool, enroll, enrollFromRun, isLive, listJobs, listLocalForLs, loadRegistry, tryClaimSlot, withRegistryLock, resolveRegistryPath, } from "./registry.js";
21
+ import { advanceJob, coerceRegistryTool, enroll, enrollFromRun, isLive, listJobs, listLocalForLs, loadRegistry, localTmuxSessionForName, tryClaimSlot, withRegistryLock, resolveRegistryPath, } from "./registry.js";
22
22
  import { aimdEffectiveCap, 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";
23
23
  import { detectThrottle, THROTTLE_TAIL_LINES } from "./throttle-signatures.js";
24
24
  import { interactiveResumeNudge, planInteractiveResume, } from "./interactive-throttle.js";
@@ -135,12 +135,12 @@ async function startRemoteFanout(remote, profileName, members, spec) {
135
135
  ].join("\t");
136
136
  });
137
137
  process.stdout.write(`NAME\tSESSION\tWORKSPACE\tSTATUS\n${rows.join("\n")}\n`);
138
- process.stderr.write(`[remote] ${ok.length}/${members.length} remote ${profileName} sessions created on ${remote} (shared RWX, subPath per session)\n`);
138
+ process.stderr.write(`[h2a] ${ok.length}/${members.length} remote ${profileName} sessions created on ${remote} (shared RWX, subPath per session)\n`);
139
139
  if (ok.length > 0) {
140
- process.stderr.write(`[remote] attach one with: remote attach <session> (list: remote ls)\n`);
140
+ process.stderr.write(`[h2a] attach one with: h2a attach <session> (list: h2a ls)\n`);
141
141
  }
142
142
  if (failed.length > 0) {
143
- process.stderr.write(`[remote] ${failed.length} session(s) failed to create — re-run --count for the shortfall, or check the control-plane\n`);
143
+ process.stderr.write(`[h2a] ${failed.length} session(s) failed to create — re-run --count for the shortfall, or check the control-plane\n`);
144
144
  process.exitCode = 1;
145
145
  }
146
146
  }
@@ -152,7 +152,7 @@ async function runProfile(profileName, opts, commandArgs = []) {
152
152
  throw new Error("--count must be a whole number >= 1");
153
153
  }
154
154
  if (opts.count > 1 && !opts.remote) {
155
- throw new Error("--count > 1 is a REMOTE fan-out — it needs a configured remote (use `remote run --count` for LOCAL tmux fan-out)");
155
+ throw new Error("--count > 1 is a REMOTE fan-out — it needs a configured remote (use `h2a run --count` for LOCAL tmux fan-out)");
156
156
  }
157
157
  }
158
158
  if (opts.remote) {
@@ -162,7 +162,7 @@ async function runProfile(profileName, opts, commandArgs = []) {
162
162
  if (opts.authRefresh !== false) {
163
163
  const result = await ensureProfileAuthFresh(profileName);
164
164
  if (result.checked) {
165
- process.stderr.write(`[remote] auth status ok: ${result.command}\n`);
165
+ process.stderr.write(`[h2a] auth status ok: ${result.command}\n`);
166
166
  }
167
167
  }
168
168
  const bundle = await collectProfileAuth(profileName);
@@ -210,9 +210,9 @@ async function runProfile(profileName, opts, commandArgs = []) {
210
210
  }
211
211
  let archive;
212
212
  if (opts.sync) {
213
- process.stderr.write(`[remote] packing ${process.cwd()} (respecting .gitignore)\n`);
213
+ process.stderr.write(`[h2a] packing ${process.cwd()} (respecting .gitignore)\n`);
214
214
  archive = await buildWorkspaceArchive(process.cwd());
215
- process.stderr.write(`[remote] workspace archive: ${(archive.byteLength / 1024).toFixed(0)} KiB\n`);
215
+ process.stderr.write(`[h2a] workspace archive: ${(archive.byteLength / 1024).toFixed(0)} KiB\n`);
216
216
  }
217
217
  const session = await createRemoteSession(opts.remote, {
218
218
  profile: profileName,
@@ -226,13 +226,13 @@ async function runProfile(profileName, opts, commandArgs = []) {
226
226
  });
227
227
  if (archive) {
228
228
  await uploadWorkspaceArchive(opts.remote, session.id, archive);
229
- process.stderr.write(`[remote] uploaded workspace to ${opts.remote}/sessions/${session.id}/workspace\n`);
229
+ process.stderr.write(`[h2a] uploaded workspace to ${opts.remote}/sessions/${session.id}/workspace\n`);
230
230
  }
231
231
  if (credentials) {
232
- process.stderr.write(`[remote] sending ${profileName} creds to ${opts.remote}: ${Object.keys(credentials).join(", ")}\n`);
233
- process.stderr.write(`[remote] (use --no-auth to start without credentials)\n`);
232
+ process.stderr.write(`[h2a] sending ${profileName} creds to ${opts.remote}: ${Object.keys(credentials).join(", ")}\n`);
233
+ process.stderr.write(`[h2a] (use --no-auth to start without credentials)\n`);
234
234
  }
235
- process.stderr.write(`[remote] attached to ${opts.remote}/sessions/${session.id}\n`);
235
+ process.stderr.write(`[h2a] attached to ${opts.remote}/sessions/${session.id}\n`);
236
236
  const attachSession = await attach({
237
237
  baseUrl: opts.remote,
238
238
  sessionId: session.id,
@@ -247,7 +247,7 @@ async function runProfile(profileName, opts, commandArgs = []) {
247
247
  ...(commandArgs.length > 0 ? { startupArgs: commandArgs } : {}),
248
248
  };
249
249
  const result = await run(runOptions);
250
- process.stderr.write(`[remote] session ${result.sessionId} attach at http://127.0.0.1:${result.port}\n`);
250
+ process.stderr.write(`[h2a] session ${result.sessionId} attach at http://127.0.0.1:${result.port}\n`);
251
251
  const { exitCode } = await result.exit;
252
252
  process.exitCode = exitCode;
253
253
  }
@@ -257,7 +257,7 @@ async function refreshProfileSession(baseUrl, sessionId, opts) {
257
257
  const requestedProfile = opts.profile ?? remoteProfile;
258
258
  if (opts.profile &&
259
259
  coerceCliProfileName(opts.profile) !== coerceCliProfileName(remoteProfile)) {
260
- process.stderr.write(`[remote] warning: --profile ${opts.profile} does not match the session profile ${remoteProfile}; bundling ${opts.profile} credentials anyway\n`);
260
+ process.stderr.write(`[h2a] warning: --profile ${opts.profile} does not match the session profile ${remoteProfile}; bundling ${opts.profile} credentials anyway\n`);
261
261
  }
262
262
  const profileName = coerceCliProfileName(requestedProfile);
263
263
  if (!profileName) {
@@ -268,7 +268,7 @@ async function refreshProfileSession(baseUrl, sessionId, opts) {
268
268
  if (opts.authRefresh !== false) {
269
269
  const result = await ensureProfileAuthFresh(profileName);
270
270
  if (result.checked) {
271
- process.stderr.write(`[remote] auth status ok: ${result.command}\n`);
271
+ process.stderr.write(`[h2a] auth status ok: ${result.command}\n`);
272
272
  }
273
273
  }
274
274
  const bundle = await collectProfileAuth(profileName);
@@ -277,11 +277,11 @@ async function refreshProfileSession(baseUrl, sessionId, opts) {
277
277
  credentials = bundle;
278
278
  }
279
279
  if (!credentials || Object.keys(credentials).length === 0) {
280
- process.stderr.write(`[remote] no credentials to refresh for session ${sessionId} (${profileName})\n`);
280
+ process.stderr.write(`[h2a] no credentials to refresh for session ${sessionId} (${profileName})\n`);
281
281
  return;
282
282
  }
283
283
  const response = await refreshRemoteSession(baseUrl, sessionId, credentials, fetch, opts.name);
284
- process.stderr.write(`[remote] refresh ${response.accepted ? "accepted" : "rejected"} for ${response.sessionId}` +
284
+ process.stderr.write(`[h2a] refresh ${response.accepted ? "accepted" : "rejected"} for ${response.sessionId}` +
285
285
  (opts.name ? ` (renamed → ${opts.name})` : "") +
286
286
  "\n");
287
287
  }
@@ -305,12 +305,12 @@ async function pushAllProfiles(baseUrl, sessionId, opts) {
305
305
  sent.push(profile);
306
306
  }
307
307
  if (sent.length === 0) {
308
- process.stderr.write(`[remote] no local credentials found for any profile; nothing to push\n`);
308
+ process.stderr.write(`[h2a] no local credentials found for any profile; nothing to push\n`);
309
309
  return;
310
310
  }
311
- process.stderr.write(`[remote] sending creds for ${sent.join(", ")} to ${baseUrl}/sessions/${sessionId}: ${Object.keys(merged).join(", ")}\n`);
311
+ process.stderr.write(`[h2a] sending creds for ${sent.join(", ")} to ${baseUrl}/sessions/${sessionId}: ${Object.keys(merged).join(", ")}\n`);
312
312
  const response = await refreshRemoteSession(baseUrl, sessionId, merged);
313
- process.stderr.write(`[remote] push ${response.accepted ? "accepted" : "rejected"} for ${response.sessionId}\n`);
313
+ process.stderr.write(`[h2a] push ${response.accepted ? "accepted" : "rejected"} for ${response.sessionId}\n`);
314
314
  }
315
315
  /** Validate `--watch <minutes>`: a whole number of minutes >= 1. */
316
316
  export function parseWatchMinutes(raw) {
@@ -415,12 +415,12 @@ async function preflightOrWarn(profile) {
415
415
  try {
416
416
  const fresh = await ensureProfileAuthFresh(profile);
417
417
  if (fresh.checked) {
418
- process.stderr.write(`[remote] auth status ok: ${fresh.command}\n`);
418
+ process.stderr.write(`[h2a] auth status ok: ${fresh.command}\n`);
419
419
  }
420
420
  }
421
421
  catch (error) {
422
422
  const detail = error instanceof Error ? error.message : String(error);
423
- process.stderr.write(`[remote] auth preflight failed (${detail.slice(0, 120)}) — pushing current creds anyway\n`);
423
+ process.stderr.write(`[h2a] auth preflight failed (${detail.slice(0, 120)}) — pushing current creds anyway\n`);
424
424
  }
425
425
  }
426
426
  /**
@@ -440,7 +440,7 @@ export async function softRefreshAllSessions(url, opts, hashes) {
440
440
  process.stderr.write(`${claudeWarn}\n`);
441
441
  const sessions = await listRemoteSessions(url);
442
442
  if (sessions.length === 0) {
443
- process.stderr.write("[remote] no live remote sessions to refresh\n");
443
+ process.stderr.write("[h2a] no live remote sessions to refresh\n");
444
444
  return { failed: 0 };
445
445
  }
446
446
  const preflighted = new Set();
@@ -492,12 +492,12 @@ export async function softRefreshAllSessions(url, opts, hashes) {
492
492
  const actions = await probePodCredHealth(s.id);
493
493
  for (const a of actions) {
494
494
  if (!a.health.ok) {
495
- process.stderr.write(`[remote] ${s.id}: ${a.health.reason}${a.pushed ? " — pushed" : ""}\n`);
495
+ process.stderr.write(`[h2a] ${s.id}: ${a.health.reason}${a.pushed ? " — pushed" : ""}\n`);
496
496
  }
497
497
  }
498
498
  }
499
499
  catch (probeError) {
500
- process.stderr.write(`[remote] ${s.id}: tool health probe skipped: ${String(probeError).slice(0, 120)}\n`);
500
+ process.stderr.write(`[h2a] ${s.id}: tool health probe skipped: ${String(probeError).slice(0, 120)}\n`);
501
501
  }
502
502
  // Slice 3: ADDITIONAL trigger — compare the local desired-state manifest
503
503
  // hash with the Pod's recorded sidecar; on drift, re-run the EXISTING
@@ -506,7 +506,7 @@ export async function softRefreshAllSessions(url, opts, hashes) {
506
506
  reconcileSessionPlugins(s.id, profile);
507
507
  }
508
508
  catch (driftError) {
509
- process.stderr.write(`[remote] ${s.id}: plugin drift reconcile skipped: ${String(driftError).slice(0, 120)}\n`);
509
+ process.stderr.write(`[h2a] ${s.id}: plugin drift reconcile skipped: ${String(driftError).slice(0, 120)}\n`);
510
510
  }
511
511
  outcomes.push({
512
512
  sessionId: s.id,
@@ -524,7 +524,7 @@ export async function softRefreshAllSessions(url, opts, hashes) {
524
524
  }
525
525
  }
526
526
  const failed = outcomes.filter((o) => o.status === "failed").length;
527
- process.stderr.write(`[remote] soft refresh recap — ${outcomes.length} session(s), ${failed} failed:\n`);
527
+ process.stderr.write(`[h2a] soft refresh recap — ${outcomes.length} session(s), ${failed} failed:\n`);
528
528
  for (const o of outcomes) {
529
529
  process.stderr.write(` ${o.sessionId} (${o.profile}) ${o.status}${o.detail ? ` — ${o.detail}` : ""}\n`);
530
530
  }
@@ -566,12 +566,12 @@ async function softRefreshOneGated(url, sessionId, opts, hashes) {
566
566
  const actions = await probePodCredHealth(sessionId);
567
567
  for (const a of actions) {
568
568
  if (!a.health.ok) {
569
- process.stderr.write(`[remote] ${sessionId}: ${a.health.reason}${a.pushed ? " — pushed" : ""}\n`);
569
+ process.stderr.write(`[h2a] ${sessionId}: ${a.health.reason}${a.pushed ? " — pushed" : ""}\n`);
570
570
  }
571
571
  }
572
572
  }
573
573
  catch (probeError) {
574
- process.stderr.write(`[remote] ${sessionId}: tool health probe skipped: ${String(probeError).slice(0, 120)}\n`);
574
+ process.stderr.write(`[h2a] ${sessionId}: tool health probe skipped: ${String(probeError).slice(0, 120)}\n`);
575
575
  }
576
576
  // Slice 3: ADDITIONAL trigger — plugin/MCP drift reconcile (manifest hash
577
577
  // mismatch → re-run the EXISTING buildPodSyncScript). Best-effort.
@@ -579,13 +579,13 @@ async function softRefreshOneGated(url, sessionId, opts, hashes) {
579
579
  reconcileSessionPlugins(sessionId, profile);
580
580
  }
581
581
  catch (driftError) {
582
- process.stderr.write(`[remote] ${sessionId}: plugin drift reconcile skipped: ${String(driftError).slice(0, 120)}\n`);
582
+ process.stderr.write(`[h2a] ${sessionId}: plugin drift reconcile skipped: ${String(driftError).slice(0, 120)}\n`);
583
583
  }
584
- process.stderr.write(`[remote] ${sessionId} (${profile}) ${result.changed ? "refreshed" : "unchanged"}\n`);
584
+ process.stderr.write(`[h2a] ${sessionId} (${profile}) ${result.changed ? "refreshed" : "unchanged"}\n`);
585
585
  return { failed: 0 };
586
586
  }
587
587
  catch (error) {
588
- process.stderr.write(`[remote] ${sessionId} refresh failed: ${(error instanceof Error ? error.message : String(error)).slice(0, 200)}\n`);
588
+ process.stderr.write(`[h2a] ${sessionId} refresh failed: ${(error instanceof Error ? error.message : String(error)).slice(0, 200)}\n`);
589
589
  return { failed: 1 };
590
590
  }
591
591
  }
@@ -607,16 +607,16 @@ signals = process) {
607
607
  signals.on("SIGINT", onSigint);
608
608
  try {
609
609
  while (!stopped) {
610
- process.stderr.write(`[remote] refresh pass — ${new Date().toISOString()}\n`);
610
+ process.stderr.write(`[h2a] refresh pass — ${new Date().toISOString()}\n`);
611
611
  try {
612
612
  await pass();
613
613
  }
614
614
  catch (error) {
615
- process.stderr.write(`[remote] refresh pass failed: ${(error instanceof Error ? error.message : String(error)).slice(0, 200)}\n`);
615
+ process.stderr.write(`[h2a] refresh pass failed: ${(error instanceof Error ? error.message : String(error)).slice(0, 200)}\n`);
616
616
  }
617
617
  if (stopped)
618
618
  break;
619
- process.stderr.write(`[remote] next pass in ${minutes} min (Ctrl-C to stop)\n`);
619
+ process.stderr.write(`[h2a] next pass in ${minutes} min (Ctrl-C to stop)\n`);
620
620
  await new Promise((resolve) => {
621
621
  const timer = setTimeout(resolve, minutes * 60_000);
622
622
  wake = () => {
@@ -630,7 +630,7 @@ signals = process) {
630
630
  finally {
631
631
  signals.removeListener("SIGINT", onSigint);
632
632
  }
633
- process.stderr.write("[remote] watch stopped (SIGINT)\n");
633
+ process.stderr.write("[h2a] watch stopped (SIGINT)\n");
634
634
  return 0;
635
635
  }
636
636
  /**
@@ -651,17 +651,17 @@ export async function conductLoop(minutes, pass, signals = process) {
651
651
  signals.on("SIGINT", onSigint);
652
652
  try {
653
653
  while (!stopped) {
654
- process.stderr.write(`[remote] conduct pass — ${new Date().toISOString()}\n`);
654
+ process.stderr.write(`[h2a] conduct pass — ${new Date().toISOString()}\n`);
655
655
  try {
656
656
  const { started, finished } = await pass();
657
- process.stderr.write(`[remote] conduct: ${started} started, ${finished} finished this pass\n`);
657
+ process.stderr.write(`[h2a] conduct: ${started} started, ${finished} finished this pass\n`);
658
658
  }
659
659
  catch (error) {
660
- process.stderr.write(`[remote] conduct pass failed: ${(error instanceof Error ? error.message : String(error)).slice(0, 200)}\n`);
660
+ process.stderr.write(`[h2a] conduct pass failed: ${(error instanceof Error ? error.message : String(error)).slice(0, 200)}\n`);
661
661
  }
662
662
  if (stopped)
663
663
  break;
664
- process.stderr.write(`[remote] next conduct pass in ${minutes} min (Ctrl-C to stop)\n`);
664
+ process.stderr.write(`[h2a] next conduct pass in ${minutes} min (Ctrl-C to stop)\n`);
665
665
  await new Promise((resolve) => {
666
666
  const timer = setTimeout(resolve, minutes * 60_000);
667
667
  wake = () => {
@@ -675,7 +675,7 @@ export async function conductLoop(minutes, pass, signals = process) {
675
675
  finally {
676
676
  signals.removeListener("SIGINT", onSigint);
677
677
  }
678
- process.stderr.write("[remote] conduct stopped (SIGINT)\n");
678
+ process.stderr.write("[h2a] conduct stopped (SIGINT)\n");
679
679
  return 0;
680
680
  }
681
681
  /**
@@ -809,7 +809,7 @@ export async function startJob(job) {
809
809
  : selectAccountWithFallback(preferredProvider, job.id);
810
810
  if (!("allExhausted" in sel) && sel.candidate !== undefined) {
811
811
  if (sel.crossProvider) {
812
- const msg = `[remote] account-pool: all ${preferredProvider} accounts exhausted — ` +
812
+ const msg = `[h2a] account-pool: all ${preferredProvider} accounts exhausted — ` +
813
813
  `falling back to ${sel.candidate.provider} (${sel.candidate.label})`;
814
814
  process.stderr.write(msg + "\n");
815
815
  // Notify the current tmux pane (non-blocking, best-effort).
@@ -959,11 +959,11 @@ export function resumeThrottledJob(job) {
959
959
  const sel = selectAccountWithFallback(preferredProvider, job.id);
960
960
  const accountEnvOverrides = {};
961
961
  if ("allExhausted" in sel) {
962
- process.stderr.write(`[remote] account-pool: all accounts exhausted for job ${job.id} — resuming without account override (may throttle again)\n`);
962
+ process.stderr.write(`[h2a] account-pool: all accounts exhausted for job ${job.id} — resuming without account override (may throttle again)\n`);
963
963
  }
964
964
  else if (sel.candidate !== undefined) {
965
965
  if (sel.crossProvider) {
966
- const msg = `[remote] account-pool: all ${preferredProvider} accounts exhausted — falling back to ${sel.candidate.provider} (${sel.candidate.label}) for resume`;
966
+ const msg = `[h2a] account-pool: all ${preferredProvider} accounts exhausted — falling back to ${sel.candidate.provider} (${sel.candidate.label}) for resume`;
967
967
  process.stderr.write(msg + "\n");
968
968
  if (process.env.TMUX)
969
969
  spawnSync("tmux", ["display-message", msg], { stdio: "ignore" });
@@ -1047,7 +1047,7 @@ export function resumeThrottledJob(job) {
1047
1047
  function getConfiguredRemote(overrideUrl) {
1048
1048
  const remote = overrideUrl ?? getConfiguredRemoteOptional();
1049
1049
  if (!remote) {
1050
- throw new Error("No remote URL configured. Set one with `remote config set <url>` (or `remote install <url>`) or pass --remote/URL explicitly.");
1050
+ throw new Error("No remote URL configured. Set one with `h2a config set <url>` (or `h2a install <url>`) or pass --remote/URL explicitly.");
1051
1051
  }
1052
1052
  return remote;
1053
1053
  }
@@ -1075,7 +1075,7 @@ function resolveTools(withOpt) {
1075
1075
  : getDefaultTools();
1076
1076
  const { known, unknown } = partitionTools(raw);
1077
1077
  if (unknown.length > 0) {
1078
- process.stderr.write(`[remote] ignoring unknown tools: ${unknown.join(", ")} (known: ${KNOWN_TOOLS.join(", ")})\n`);
1078
+ process.stderr.write(`[h2a] ignoring unknown tools: ${unknown.join(", ")} (known: ${KNOWN_TOOLS.join(", ")})\n`);
1079
1079
  }
1080
1080
  return known;
1081
1081
  }
@@ -1183,16 +1183,16 @@ async function injectLlmMeshGatewayEnv(mode = "auto") {
1183
1183
  ANTHROPIC_AUTH_TOKEN: result.gatewayToken,
1184
1184
  ANTHROPIC_API_KEY: result.gatewayToken,
1185
1185
  };
1186
- process.stderr.write(`[remote] llm-mesh: gateway was stopped; started on ${meshEnv.ANTHROPIC_BASE_URL}\n`);
1186
+ process.stderr.write(`[h2a] llm-mesh: gateway was stopped; started on ${meshEnv.ANTHROPIC_BASE_URL}\n`);
1187
1187
  }
1188
1188
  catch (err) {
1189
- process.stderr.write(`[remote] llm-mesh: gateway env unavailable (${String(err)}); Claude may ask for login.\n`);
1189
+ process.stderr.write(`[h2a] llm-mesh: gateway env unavailable (${String(err)}); Claude may ask for login.\n`);
1190
1190
  }
1191
1191
  }
1192
1192
  }
1193
1193
  if (!meshEnv) {
1194
1194
  if (mode === "gateway") {
1195
- process.stderr.write("[remote] llm-mesh: --gw requested but no gateway env is available; continuing direct. Run `remote llm-mesh start` or enroll an account first.\n");
1195
+ process.stderr.write("[h2a] llm-mesh: --gw requested but no gateway env is available; continuing direct. Run `h2a llm-mesh start` or enroll an account first.\n");
1196
1196
  }
1197
1197
  return undefined;
1198
1198
  }
@@ -1202,7 +1202,7 @@ async function injectLlmMeshGatewayEnv(mode = "auto") {
1202
1202
  for (const [k, v] of Object.entries(meshEnv))
1203
1203
  process.env[k] = v;
1204
1204
  if (!alreadyCurrent) {
1205
- process.stderr.write(`[remote] llm-mesh: injecting gateway env (${meshEnv.ANTHROPIC_BASE_URL})\n`);
1205
+ process.stderr.write(`[h2a] llm-mesh: injecting gateway env (${meshEnv.ANTHROPIC_BASE_URL})\n`);
1206
1206
  }
1207
1207
  return meshEnv.ANTHROPIC_BASE_URL;
1208
1208
  }
@@ -1211,20 +1211,20 @@ async function prepareLlmMeshForRestore(opts = {}) {
1211
1211
  return;
1212
1212
  const config = readLlmMeshConfig();
1213
1213
  if (!config?.accounts.length) {
1214
- process.stderr.write("[remote] llm-mesh: restore config enabled, but no llm-mesh account is enrolled; Claude may ask for login.\n");
1214
+ process.stderr.write("[h2a] llm-mesh: restore config enabled, but no llm-mesh account is enrolled; Claude may ask for login.\n");
1215
1215
  return;
1216
1216
  }
1217
1217
  const port = config.port ?? 3002;
1218
1218
  const pid = readGatewayPid();
1219
1219
  if (opts.dryRun) {
1220
1220
  process.stderr.write(pid
1221
- ? `[remote] llm-mesh: restore config enabled; gateway running (pid ${pid}, port ${port})\n`
1222
- : `[remote] llm-mesh: restore config enabled; gateway would be started on port ${port}\n`);
1221
+ ? `[h2a] llm-mesh: restore config enabled; gateway running (pid ${pid}, port ${port})\n`
1222
+ : `[h2a] llm-mesh: restore config enabled; gateway would be started on port ${port}\n`);
1223
1223
  return;
1224
1224
  }
1225
1225
  const gateway = await injectLlmMeshGatewayEnv();
1226
1226
  if (gateway) {
1227
- process.stderr.write(`[remote] llm-mesh: restore context active (${gateway})\n`);
1227
+ process.stderr.write(`[h2a] llm-mesh: restore context active (${gateway})\n`);
1228
1228
  }
1229
1229
  }
1230
1230
  function registryEntryForResumeTarget(target, local) {
@@ -1275,7 +1275,7 @@ function resolveUrlAndSessionId(first, second) {
1275
1275
  function setAndReportDefaultRemote(url) {
1276
1276
  const configured = setDefaultRemote(url);
1277
1277
  ensureManagedTmuxProfile(getTmuxProfileConfig().profile);
1278
- process.stderr.write(`[remote] default remote set to ${configured}\n`);
1278
+ process.stderr.write(`[h2a] default remote set to ${configured}\n`);
1279
1279
  }
1280
1280
  export async function main(argv) {
1281
1281
  // ②b — auto-migrate the config home remote-cli/ -> h2a/ on first run (backup +
@@ -1292,7 +1292,7 @@ export async function main(argv) {
1292
1292
  if (shouldShowProfileMenu(argv, process.stdin.isTTY === true && process.stdout.isTTY === true)) {
1293
1293
  const profile = await promptProfileMenu(process.stdin, process.stderr, process.cwd());
1294
1294
  if (!profile) {
1295
- process.stderr.write("[remote] no profile selected\n");
1295
+ process.stderr.write("[h2a] no profile selected\n");
1296
1296
  return 1;
1297
1297
  }
1298
1298
  return main([
@@ -1306,7 +1306,7 @@ export async function main(argv) {
1306
1306
  const program = new Command();
1307
1307
  program
1308
1308
  .name("remote")
1309
- .description("Wrap a local agent CLI (codex/claude/agy/gemini/mistral) and expose its session for remote attach.")
1309
+ .description("Wrap a local agent CLI (codex/claude/agy/gemini/mistral) and expose its session for h2a attach.")
1310
1310
  .version("0.0.0");
1311
1311
  for (const [profileName, alias] of [
1312
1312
  ["codex", undefined],
@@ -1341,7 +1341,7 @@ export async function main(argv) {
1341
1341
  await runProfile(profileName, { ...rest }, commandArgs ?? []);
1342
1342
  }
1343
1343
  catch (err) {
1344
- process.stderr.write(`[remote] ${err.message}\n`);
1344
+ process.stderr.write(`[h2a] ${err.message}\n`);
1345
1345
  process.exitCode = 1;
1346
1346
  }
1347
1347
  return;
@@ -1355,7 +1355,7 @@ export async function main(argv) {
1355
1355
  : readWorkspaceMarker(process.cwd());
1356
1356
  const remote = getConfiguredRemote(remoteOverride ?? marker?.remote);
1357
1357
  if (marker) {
1358
- process.stderr.write(`[remote] cwd mapped to ${marker.workspaceId} (reusing workspace)\n`);
1358
+ process.stderr.write(`[h2a] cwd mapped to ${marker.workspaceId} (reusing workspace)\n`);
1359
1359
  }
1360
1360
  try {
1361
1361
  await runProfile(profileName, {
@@ -1365,7 +1365,7 @@ export async function main(argv) {
1365
1365
  }, commandArgs ?? []);
1366
1366
  }
1367
1367
  catch (err) {
1368
- process.stderr.write(`[remote] ${err.message}\n`);
1368
+ process.stderr.write(`[h2a] ${err.message}\n`);
1369
1369
  process.exitCode = 1;
1370
1370
  }
1371
1371
  });
@@ -1390,13 +1390,13 @@ export async function main(argv) {
1390
1390
  const cwd = process.cwd();
1391
1391
  const existing = readWorkspaceMarker(cwd);
1392
1392
  if (existing) {
1393
- process.stderr.write(`[remote] ${cwd} already mapped to ${existing.workspaceId} (${existing.remote})\n`);
1393
+ process.stderr.write(`[h2a] ${cwd} already mapped to ${existing.workspaceId} (${existing.remote})\n`);
1394
1394
  return;
1395
1395
  }
1396
1396
  const remote = getConfiguredRemote(opts.remote);
1397
1397
  const ws = await createWorkspace(remote, opts.name ? { displayName: opts.name } : {});
1398
1398
  writeWorkspaceMarker(cwd, { remote, workspaceId: ws.id });
1399
- process.stderr.write(`[remote] linked ${cwd} -> ${ws.id} (wrote .remote/workspace.json)\n`);
1399
+ process.stderr.write(`[h2a] linked ${cwd} -> ${ws.id} (wrote .remote/workspace.json)\n`);
1400
1400
  });
1401
1401
  workspaceCommand
1402
1402
  .command("list [url]")
@@ -1405,7 +1405,7 @@ export async function main(argv) {
1405
1405
  const remote = getConfiguredRemote(url);
1406
1406
  const workspaces = await listWorkspaces(remote);
1407
1407
  if (workspaces.length === 0) {
1408
- process.stderr.write("[remote] no workspaces\n");
1408
+ process.stderr.write("[h2a] no workspaces\n");
1409
1409
  return;
1410
1410
  }
1411
1411
  const rows = workspaces.map((w) => [w.id, w.createdAt, w.displayName ?? ""].join("\t"));
@@ -1417,7 +1417,7 @@ export async function main(argv) {
1417
1417
  .action(() => {
1418
1418
  const marker = readWorkspaceMarker(process.cwd());
1419
1419
  if (!marker) {
1420
- process.stdout.write("[remote] no workspace mapped for this directory (run `remote workspace link`)\n");
1420
+ process.stdout.write("[h2a] no workspace mapped for this directory (run `h2a workspace link`)\n");
1421
1421
  return;
1422
1422
  }
1423
1423
  process.stdout.write(`workspace: ${marker.workspaceId}\n`);
@@ -1426,7 +1426,7 @@ export async function main(argv) {
1426
1426
  const requireMarker = (cwd) => {
1427
1427
  const marker = readWorkspaceMarker(cwd);
1428
1428
  if (!marker) {
1429
- throw new Error("No workspace mapped for this directory. Run `remote workspace link` first.");
1429
+ throw new Error("No workspace mapped for this directory. Run `h2a workspace link` first.");
1430
1430
  }
1431
1431
  return marker;
1432
1432
  };
@@ -1437,7 +1437,7 @@ export async function main(argv) {
1437
1437
  throw new Error(`Workspace ${workspaceId} is held by ${lock.holder} since ${lock.since}. ` +
1438
1438
  `Coordinate, or pass --force to override the soft lock.`);
1439
1439
  }
1440
- process.stderr.write(`[remote] warning: overriding soft lock held by ${lock.holder} (--force)\n`);
1440
+ process.stderr.write(`[h2a] warning: overriding soft lock held by ${lock.holder} (--force)\n`);
1441
1441
  }
1442
1442
  };
1443
1443
  workspaceCommand
@@ -1449,9 +1449,9 @@ export async function main(argv) {
1449
1449
  const marker = requireMarker(cwd);
1450
1450
  await guardLock(marker.remote, marker.workspaceId, opts.force ?? false);
1451
1451
  try {
1452
- process.stderr.write(`[remote] packing ${cwd} (respecting .gitignore)\n`);
1452
+ process.stderr.write(`[h2a] packing ${cwd} (respecting .gitignore)\n`);
1453
1453
  const archive = await buildWorkspaceArchive(cwd);
1454
- process.stderr.write(`[remote] archive: ${(archive.byteLength / 1024).toFixed(0)} KiB -> ${marker.workspaceId}\n`);
1454
+ process.stderr.write(`[h2a] archive: ${(archive.byteLength / 1024).toFixed(0)} KiB -> ${marker.workspaceId}\n`);
1455
1455
  // Reuse the --sync path via a throwaway session bound to the workspace:
1456
1456
  // the session-agent extracts the archive into the retained PVC, then the
1457
1457
  // shell exits and the session auto-cleans (the workspace PVC is kept).
@@ -1469,7 +1469,7 @@ export async function main(argv) {
1469
1469
  await attached.finished;
1470
1470
  // The pushed tree is now the shared sync base.
1471
1471
  writeBaseSnapshot(cwd, archive);
1472
- process.stderr.write(`[remote] pushed ${cwd} to ${marker.workspaceId}\n`);
1472
+ process.stderr.write(`[h2a] pushed ${cwd} to ${marker.workspaceId}\n`);
1473
1473
  }
1474
1474
  finally {
1475
1475
  await releaseWorkspaceLock(marker.remote, marker.workspaceId);
@@ -1510,7 +1510,7 @@ export async function main(argv) {
1510
1510
  await stopRemoteSession(marker.remote, session.id, "pull-complete");
1511
1511
  }
1512
1512
  if (!remoteArchive) {
1513
- process.stderr.write(`[remote] nothing to pull (workspace ${marker.workspaceId} produced no export)\n`);
1513
+ process.stderr.write(`[h2a] nothing to pull (workspace ${marker.workspaceId} produced no export)\n`);
1514
1514
  return;
1515
1515
  }
1516
1516
  const result = mergeWorkspaceArchive({
@@ -1518,9 +1518,9 @@ export async function main(argv) {
1518
1518
  remoteArchive,
1519
1519
  baseArchive: readBaseSnapshot(cwd),
1520
1520
  });
1521
- process.stderr.write(`[remote] pull: ${result.tookRemote.length} from remote, ${result.keptLocal.length} kept local, ${result.merged.length} merged\n`);
1521
+ process.stderr.write(`[h2a] pull: ${result.tookRemote.length} from remote, ${result.keptLocal.length} kept local, ${result.merged.length} merged\n`);
1522
1522
  if (result.conflicts.length > 0) {
1523
- process.stderr.write(`[remote] ${result.conflicts.length} conflict(s) (left with markers, resolve then re-run):\n`);
1523
+ process.stderr.write(`[h2a] ${result.conflicts.length} conflict(s) (left with markers, resolve then re-run):\n`);
1524
1524
  for (const f of result.conflicts)
1525
1525
  process.stderr.write(` ${f}\n`);
1526
1526
  process.exitCode = 1;
@@ -1528,7 +1528,7 @@ export async function main(argv) {
1528
1528
  }
1529
1529
  // Clean merge → the remote tree is the new shared base.
1530
1530
  writeBaseSnapshot(cwd, remoteArchive);
1531
- process.stderr.write(`[remote] pulled ${marker.workspaceId} into ${cwd}\n`);
1531
+ process.stderr.write(`[h2a] pulled ${marker.workspaceId} into ${cwd}\n`);
1532
1532
  if (opts.restoreSessions) {
1533
1533
  const onConflict = opts.onConflict === "backup"
1534
1534
  ? "backup"
@@ -1547,7 +1547,7 @@ export async function main(argv) {
1547
1547
  const touched = r.restored.length + r.backedUp.length + r.conflicts.length;
1548
1548
  if (touched === 0 && r.keptLocal.length === 0)
1549
1549
  continue;
1550
- process.stderr.write(`[remote] sessions(${profile}): ${r.restored.length} restored, ${r.backedUp.length} backed-up, ${r.keptLocal.length} kept, ${r.conflicts.length} conflict\n`);
1550
+ process.stderr.write(`[h2a] sessions(${profile}): ${r.restored.length} restored, ${r.backedUp.length} backed-up, ${r.keptLocal.length} kept, ${r.conflicts.length} conflict\n`);
1551
1551
  for (const b of r.backedUp)
1552
1552
  process.stderr.write(` backup ${b}\n`);
1553
1553
  if (r.conflicts.length > 0) {
@@ -1557,7 +1557,7 @@ export async function main(argv) {
1557
1557
  }
1558
1558
  }
1559
1559
  if (anyConflict) {
1560
- process.stderr.write(`[remote] diverged conversations left untouched. Re-run with --on-conflict backup (keep both) or keep-local.\n`);
1560
+ process.stderr.write(`[h2a] diverged conversations left untouched. Re-run with --on-conflict backup (keep both) or keep-local.\n`);
1561
1561
  process.exitCode = 1;
1562
1562
  }
1563
1563
  }
@@ -1579,8 +1579,8 @@ export async function main(argv) {
1579
1579
  }
1580
1580
  const deleted = await deleteWorkspace(remote, id);
1581
1581
  process.stderr.write(deleted
1582
- ? `[remote] deleted workspace ${id}\n`
1583
- : `[remote] workspace ${id} not found\n`);
1582
+ ? `[h2a] deleted workspace ${id}\n`
1583
+ : `[h2a] workspace ${id} not found\n`);
1584
1584
  });
1585
1585
  workspaceCommand
1586
1586
  .command("gc")
@@ -1605,16 +1605,16 @@ export async function main(argv) {
1605
1605
  // before the candidate list has been printed (and confirmed).
1606
1606
  const dryRun = await requestWorkspaceGc(remote, { olderThanDays });
1607
1607
  if (dryRun.candidates.length === 0) {
1608
- process.stderr.write(`[remote] workspace gc: no candidates older than ${olderThanDays} day(s) — nothing to do\n`);
1608
+ process.stderr.write(`[h2a] workspace gc: no candidates older than ${olderThanDays} day(s) — nothing to do\n`);
1609
1609
  return;
1610
1610
  }
1611
- process.stderr.write(`[remote] workspace gc: ${dryRun.candidates.length} candidate(s) older than ${olderThanDays} day(s) (workspaces of known sessions are always kept):\n`);
1611
+ process.stderr.write(`[h2a] workspace gc: ${dryRun.candidates.length} candidate(s) older than ${olderThanDays} day(s) (workspaces of known sessions are always kept):\n`);
1612
1612
  process.stdout.write([
1613
1613
  "ID\tSIZE\tLAST-MODIFIED",
1614
1614
  ...dryRun.candidates.map((c) => `${c.id}\t${c.sizeH}\t${c.lastModified}`),
1615
1615
  ].join("\n") + "\n");
1616
1616
  if (!opts.apply) {
1617
- process.stderr.write("[remote] dry-run only — nothing was deleted. Re-run with --apply to archive these to the volume's .trash/ and remove them.\n");
1617
+ process.stderr.write("[h2a] dry-run only — nothing was deleted. Re-run with --apply to archive these to the volume's .trash/ and remove them.\n");
1618
1618
  return;
1619
1619
  }
1620
1620
  if (!opts.yes) {
@@ -1623,12 +1623,12 @@ export async function main(argv) {
1623
1623
  input: process.stdin,
1624
1624
  output: process.stderr,
1625
1625
  });
1626
- const answer = (await rl.question(`[remote] archive ${dryRun.candidates.length} director(y/ies) to on-volume .trash/ and DELETE them? [y/N] `))
1626
+ const answer = (await rl.question(`[h2a] archive ${dryRun.candidates.length} director(y/ies) to on-volume .trash/ and DELETE them? [y/N] `))
1627
1627
  .trim()
1628
1628
  .toLowerCase();
1629
1629
  rl.close();
1630
1630
  if (answer !== "y" && answer !== "yes") {
1631
- process.stderr.write("[remote] aborted — nothing was deleted\n");
1631
+ process.stderr.write("[h2a] aborted — nothing was deleted\n");
1632
1632
  return;
1633
1633
  }
1634
1634
  }
@@ -1639,18 +1639,18 @@ export async function main(argv) {
1639
1639
  // The janitor re-checks keep-list and age at apply time, so the applied
1640
1640
  // set can legitimately be smaller than the dry-run shown above.
1641
1641
  for (const c of report.candidates) {
1642
- process.stderr.write(`[remote] archived ${c.id} (${c.sizeH}) -> ${c.archivedTo ?? ".trash/"} then removed\n`);
1642
+ process.stderr.write(`[h2a] archived ${c.id} (${c.sizeH}) -> ${c.archivedTo ?? ".trash/"} then removed\n`);
1643
1643
  }
1644
1644
  for (const f of report.failed ?? []) {
1645
- process.stderr.write(`[remote] FAILED ${f.id}: ${f.reason} — directory left untouched\n`);
1645
+ process.stderr.write(`[h2a] FAILED ${f.id}: ${f.reason} — directory left untouched\n`);
1646
1646
  }
1647
1647
  if ((report.failed ?? []).length > 0)
1648
1648
  process.exitCode = 1;
1649
1649
  if (report.candidates.length === 0) {
1650
- process.stderr.write("[remote] nothing collected (candidates became active or protected since the dry-run)\n");
1650
+ process.stderr.write("[h2a] nothing collected (candidates became active or protected since the dry-run)\n");
1651
1651
  }
1652
1652
  else {
1653
- process.stderr.write(`[remote] workspace gc done: ${report.candidates.length} archived+removed (recoverable from the volume's .trash/)\n`);
1653
+ process.stderr.write(`[h2a] workspace gc done: ${report.candidates.length} archived+removed (recoverable from the volume's .trash/)\n`);
1654
1654
  }
1655
1655
  });
1656
1656
  const authCommand = program
@@ -1683,7 +1683,7 @@ export async function main(argv) {
1683
1683
  return;
1684
1684
  }
1685
1685
  if (!profileName) {
1686
- throw new Error("Specify a profile (e.g. `remote auth status codex`) or pass --all.");
1686
+ throw new Error("Specify a profile (e.g. `h2a auth status codex`) or pass --all.");
1687
1687
  }
1688
1688
  const profile = coerceCliProfileName(profileName);
1689
1689
  if (!profile) {
@@ -1701,13 +1701,13 @@ export async function main(argv) {
1701
1701
  }
1702
1702
  const loginCommand = getLoginCommand(profile);
1703
1703
  if (!loginCommand) {
1704
- process.stderr.write(`[remote] ${profile} has no scripted login. Run \`${profile}\` directly and complete its sign-in flow (browser / SSH-mode URL), then \`remote auth status ${profile}\`.\n`);
1704
+ process.stderr.write(`[h2a] ${profile} has no scripted login. Run \`${profile}\` directly and complete its sign-in flow (browser / SSH-mode URL), then \`h2a auth status ${profile}\`.\n`);
1705
1705
  return;
1706
1706
  }
1707
- process.stderr.write(`[remote] running ${loginCommand.command} ${loginCommand.args.join(" ")}\n`);
1707
+ process.stderr.write(`[h2a] running ${loginCommand.command} ${loginCommand.args.join(" ")}\n`);
1708
1708
  const code = await runInteractiveLogin(loginCommand);
1709
1709
  if (code !== 0) {
1710
- process.stderr.write(`[remote] login exited with code ${code}; check the output above.\n`);
1710
+ process.stderr.write(`[h2a] login exited with code ${code}; check the output above.\n`);
1711
1711
  process.exitCode = code;
1712
1712
  return;
1713
1713
  }
@@ -1732,7 +1732,7 @@ export async function main(argv) {
1732
1732
  if (opts.authRefresh !== false) {
1733
1733
  const fresh = await ensureProfileAuthFresh(resolved);
1734
1734
  if (fresh.checked)
1735
- process.stderr.write(`[remote] auth status ok: ${fresh.command}\n`);
1735
+ process.stderr.write(`[h2a] auth status ok: ${fresh.command}\n`);
1736
1736
  }
1737
1737
  await softRefreshSession(sessionId, resolved);
1738
1738
  return;
@@ -1790,14 +1790,14 @@ export async function main(argv) {
1790
1790
  .description("Store the bearer token sent as Authorization on control-plane requests (overridden by $REMOTE_TOKEN)")
1791
1791
  .action((value) => {
1792
1792
  setToken(value);
1793
- process.stderr.write("[remote] stored bearer token\n");
1793
+ process.stderr.write("[h2a] stored bearer token\n");
1794
1794
  });
1795
1795
  configCommand
1796
1796
  .command("target <target>")
1797
1797
  .description("Set the default session target label (where the workload runs), e.g. scaleway-kapsule")
1798
1798
  .action((target) => {
1799
1799
  setDefaultTarget(target);
1800
- process.stderr.write(`[remote] default target set to ${target}\n`);
1800
+ process.stderr.write(`[h2a] default target set to ${target}\n`);
1801
1801
  });
1802
1802
  configCommand
1803
1803
  .command("tools <list>")
@@ -1811,10 +1811,10 @@ export async function main(argv) {
1811
1811
  .filter(Boolean);
1812
1812
  const { known, unknown } = partitionTools(requested);
1813
1813
  if (unknown.length > 0) {
1814
- process.stderr.write(`[remote] unknown tools ignored: ${unknown.join(", ")}\n`);
1814
+ process.stderr.write(`[h2a] unknown tools ignored: ${unknown.join(", ")}\n`);
1815
1815
  }
1816
1816
  setDefaultTools(known);
1817
- process.stderr.write(`[remote] default tools: ${known.length > 0 ? known.join(", ") : "(none)"}\n`);
1817
+ process.stderr.write(`[h2a] default tools: ${known.length > 0 ? known.join(", ") : "(none)"}\n`);
1818
1818
  });
1819
1819
  configCommand
1820
1820
  .command("tmux-profile <name>")
@@ -1823,21 +1823,21 @@ export async function main(argv) {
1823
1823
  setTmuxProfileConfig({ profile: name });
1824
1824
  const profile = getTmuxProfileConfig().profile;
1825
1825
  ensureManagedTmuxProfile(profile);
1826
- process.stderr.write(`[remote] tmux profile set to ${profile}\n`);
1826
+ process.stderr.write(`[h2a] tmux profile set to ${profile}\n`);
1827
1827
  });
1828
1828
  configCommand
1829
1829
  .command("clear")
1830
1830
  .description("Clear default remote URL")
1831
1831
  .action(() => {
1832
1832
  clearDefaultRemote();
1833
- process.stderr.write("[remote] cleared default remote\n");
1833
+ process.stderr.write("[h2a] cleared default remote\n");
1834
1834
  });
1835
1835
  configCommand
1836
1836
  .command("show")
1837
1837
  .description("Display configured default remote URL, managed tmux profile, and tunnel")
1838
1838
  .action(() => {
1839
1839
  const remote = getDefaultRemote();
1840
- process.stdout.write(remote ? `${remote}\n` : "[remote] no default remote configured\n");
1840
+ process.stdout.write(remote ? `${remote}\n` : "[h2a] no default remote configured\n");
1841
1841
  process.stdout.write(`target: ${getDefaultTarget()}\n`);
1842
1842
  const tools = getDefaultTools();
1843
1843
  if (tools.length > 0)
@@ -1867,7 +1867,7 @@ export async function main(argv) {
1867
1867
  ...(opts.kubeconfig ? { kubeconfig: opts.kubeconfig } : {}),
1868
1868
  };
1869
1869
  setTunnel(tunnel);
1870
- process.stderr.write(`[remote] tunnel configured: kubectl -n ${tunnel.namespace} port-forward svc/${tunnel.service} ${tunnel.localPort}:${tunnel.remotePort}\n`);
1870
+ process.stderr.write(`[h2a] tunnel configured: kubectl -n ${tunnel.namespace} port-forward svc/${tunnel.service} ${tunnel.localPort}:${tunnel.remotePort}\n`);
1871
1871
  });
1872
1872
  // ---------------------------------------------------------------------------
1873
1873
  // connect / disconnect — manage the on-demand tunnel
@@ -1879,7 +1879,7 @@ export async function main(argv) {
1879
1879
  .action(async (opts) => {
1880
1880
  const url = getConfiguredRemote(opts.remote);
1881
1881
  await ensureConnected(url);
1882
- process.stderr.write(`[remote] connected: ${url}\n`);
1882
+ process.stderr.write(`[h2a] connected: ${url}\n`);
1883
1883
  });
1884
1884
  program
1885
1885
  .command("disconnect")
@@ -1887,8 +1887,8 @@ export async function main(argv) {
1887
1887
  .action(() => {
1888
1888
  const stopped = stopTunnel();
1889
1889
  process.stderr.write(stopped
1890
- ? "[remote] tunnel closed\n"
1891
- : "[remote] no managed tunnel was running\n");
1890
+ ? "[h2a] tunnel closed\n"
1891
+ : "[h2a] no managed tunnel was running\n");
1892
1892
  });
1893
1893
  program
1894
1894
  .command("status")
@@ -1960,7 +1960,7 @@ export async function main(argv) {
1960
1960
  if (sessionId) {
1961
1961
  const entries = transmittedSecrets(sessionId);
1962
1962
  if (entries === undefined) {
1963
- process.stdout.write(`[remote] cannot read secrets for ${sessionId} (no tunnel configured, or no auth Secret)\n`);
1963
+ process.stdout.write(`[h2a] cannot read secrets for ${sessionId} (no tunnel configured, or no auth Secret)\n`);
1964
1964
  return;
1965
1965
  }
1966
1966
  process.stdout.write(`Secrets transmitted to ${sessionId} (live, names only):\n`);
@@ -1986,7 +1986,7 @@ export async function main(argv) {
1986
1986
  for (const s of sessions) {
1987
1987
  process.stdout.write(` ${projectName(s).padEnd(22)} ${secretsSummary(s.id)} (${s.id})\n`);
1988
1988
  }
1989
- process.stdout.write("\n(⚠ = account-wide cloud cred. Detail: remote secrets status <sessionId>.)\n");
1989
+ process.stdout.write("\n(⚠ = account-wide cloud cred. Detail: h2a secrets status <sessionId>.)\n");
1990
1990
  });
1991
1991
  // ---------------------------------------------------------------------------
1992
1992
  // diff — is the remote session aligned with the local one?
@@ -1999,7 +1999,7 @@ export async function main(argv) {
1999
1999
  .option("--remote <url>", "control-plane URL (defaults to configured remote)")
2000
2000
  .action(async (sessionId, opts) => {
2001
2001
  if (opts.session && opts.files) {
2002
- process.stderr.write("[remote] --session and --files are mutually exclusive\n");
2002
+ process.stderr.write("[h2a] --session and --files are mutually exclusive\n");
2003
2003
  process.exitCode = 1;
2004
2004
  return;
2005
2005
  }
@@ -2010,7 +2010,7 @@ export async function main(argv) {
2010
2010
  ? all.filter((s) => s.id === sessionId)
2011
2011
  : all;
2012
2012
  if (sessions.length === 0) {
2013
- process.stdout.write("[remote] no matching session\n");
2013
+ process.stdout.write("[h2a] no matching session\n");
2014
2014
  return;
2015
2015
  }
2016
2016
  const icon = {
@@ -2055,12 +2055,12 @@ export async function main(argv) {
2055
2055
  .option("--interval <seconds>", "seconds between sync passes in --watch mode (default: 30)", (v) => Number(v))
2056
2056
  .action(async (sessionId, opts) => {
2057
2057
  if (opts.files) {
2058
- process.stderr.write("[remote] files: utilise git (commit/push des deux côtés) — non automatisé\n");
2058
+ process.stderr.write("[h2a] files: utilise git (commit/push des deux côtés) — non automatisé\n");
2059
2059
  process.exitCode = 1;
2060
2060
  return;
2061
2061
  }
2062
2062
  if (opts.session !== "push" && opts.session !== "pull") {
2063
- process.stderr.write("[remote] usage: remote sync <sessionId> --session <push|pull>\n");
2063
+ process.stderr.write("[h2a] usage: h2a sync <sessionId> --session <push|pull>\n");
2064
2064
  process.exitCode = 1;
2065
2065
  return;
2066
2066
  }
@@ -2069,7 +2069,7 @@ export async function main(argv) {
2069
2069
  await ensureConnected(url);
2070
2070
  const session = (await listRemoteSessions(url)).find((s) => s.id === sessionId);
2071
2071
  if (!session?.workspacePath) {
2072
- process.stderr.write(`[remote] no session ${sessionId} with a workspace path\n`);
2072
+ process.stderr.write(`[h2a] no session ${sessionId} with a workspace path\n`);
2073
2073
  process.exitCode = 1;
2074
2074
  return;
2075
2075
  }
@@ -2082,23 +2082,23 @@ export async function main(argv) {
2082
2082
  force: opts.force ?? false,
2083
2083
  });
2084
2084
  if (!result.ok) {
2085
- process.stderr.write(`[remote] refused: ${result.reason}\n`);
2085
+ process.stderr.write(`[h2a] refused: ${result.reason}\n`);
2086
2086
  return false;
2087
2087
  }
2088
2088
  if (result.backup) {
2089
- process.stderr.write(`[remote] backup: ${result.backup}\n`);
2089
+ process.stderr.write(`[h2a] backup: ${result.backup}\n`);
2090
2090
  }
2091
2091
  const lines = direction === "pull" ? result.lines.remote : result.lines.local;
2092
2092
  const mode = result.incremental ? " [incremental]" : "";
2093
- process.stderr.write(`[remote] ${direction === "pull" ? "pulled" : "pushed"} ${result.convId} (${lines} lines) → ${result.written}${mode}\n`);
2093
+ process.stderr.write(`[h2a] ${direction === "pull" ? "pulled" : "pushed"} ${result.convId} (${lines} lines) → ${result.written}${mode}\n`);
2094
2094
  if (direction === "push") {
2095
- process.stderr.write(`[remote] not relaunching the Pod CLI — relance la session pour charger : remote refresh ${sessionId} --soft\n`);
2095
+ process.stderr.write(`[h2a] not relaunching the Pod CLI — relance la session pour charger : h2a refresh ${sessionId} --soft\n`);
2096
2096
  }
2097
2097
  return true;
2098
2098
  };
2099
2099
  if (opts.watch) {
2100
2100
  const intervalSec = opts.interval ?? 30;
2101
- process.stderr.write(`[remote] sync --watch: syncing every ${intervalSec}s (Ctrl-C to stop)\n`);
2101
+ process.stderr.write(`[h2a] sync --watch: syncing every ${intervalSec}s (Ctrl-C to stop)\n`);
2102
2102
  let stopped = false;
2103
2103
  let wake;
2104
2104
  const onSigint = () => {
@@ -2108,12 +2108,12 @@ export async function main(argv) {
2108
2108
  process.on("SIGINT", onSigint);
2109
2109
  try {
2110
2110
  while (!stopped) {
2111
- process.stderr.write(`[remote] sync pass — ${new Date().toISOString()}\n`);
2111
+ process.stderr.write(`[h2a] sync pass — ${new Date().toISOString()}\n`);
2112
2112
  try {
2113
2113
  runOnce();
2114
2114
  }
2115
2115
  catch (err) {
2116
- process.stderr.write(`[remote] sync pass error: ${err.message}\n`);
2116
+ process.stderr.write(`[h2a] sync pass error: ${err.message}\n`);
2117
2117
  }
2118
2118
  if (stopped)
2119
2119
  break;
@@ -2130,7 +2130,7 @@ export async function main(argv) {
2130
2130
  finally {
2131
2131
  process.removeListener("SIGINT", onSigint);
2132
2132
  }
2133
- process.stderr.write("[remote] sync --watch stopped\n");
2133
+ process.stderr.write("[h2a] sync --watch stopped\n");
2134
2134
  return;
2135
2135
  }
2136
2136
  const ok = runOnce();
@@ -2208,7 +2208,7 @@ export async function main(argv) {
2208
2208
  program
2209
2209
  .command("sync-files")
2210
2210
  .description("Push the current git workspace to the session pod incrementally (git bundle on first push, diff on subsequent). " +
2211
- "Distinct from `remote sync` which syncs conversations.")
2211
+ "Distinct from `h2a sync` which syncs conversations.")
2212
2212
  .option("--remote <url>", "control-plane URL (defaults to configured remote)")
2213
2213
  .option("--session <id>", "session id to sync into (defaults to .remote/workspace.json marker)")
2214
2214
  .option("--dry-run", "show what would be sent without uploading")
@@ -2216,13 +2216,13 @@ export async function main(argv) {
2216
2216
  const cwd = process.cwd();
2217
2217
  const { isGitRepo: checkGit, getHeadSha, buildGitBundle, buildIncrementalManifest, buildUntrackedTarball, } = await import("./workspace-sync-incremental.js");
2218
2218
  if (!checkGit(cwd)) {
2219
- process.stderr.write("[remote] sync-files: current directory is not a git repo\n");
2219
+ process.stderr.write("[h2a] sync-files: current directory is not a git repo\n");
2220
2220
  process.exitCode = 1;
2221
2221
  return;
2222
2222
  }
2223
2223
  const headSha = getHeadSha(cwd);
2224
2224
  if (!headSha) {
2225
- process.stderr.write("[remote] sync-files: cannot resolve HEAD — make at least one commit\n");
2225
+ process.stderr.write("[h2a] sync-files: cannot resolve HEAD — make at least one commit\n");
2226
2226
  process.exitCode = 1;
2227
2227
  return;
2228
2228
  }
@@ -2231,7 +2231,7 @@ export async function main(argv) {
2231
2231
  // Resolve session id: explicit --session, or from marker
2232
2232
  const sessionId = opts.session ?? marker?.workspaceId;
2233
2233
  if (!sessionId) {
2234
- process.stderr.write("[remote] sync-files: no session id — pass --session or run `remote workspace link` first\n");
2234
+ process.stderr.write("[h2a] sync-files: no session id — pass --session or run `h2a workspace link` first\n");
2235
2235
  process.exitCode = 1;
2236
2236
  return;
2237
2237
  }
@@ -2245,18 +2245,18 @@ export async function main(argv) {
2245
2245
  }
2246
2246
  if (opts.dryRun) {
2247
2247
  if (baseSha) {
2248
- process.stderr.write(`[remote] sync-files (dry-run): incremental push HEAD=${headSha} base=${baseSha}\n`);
2248
+ process.stderr.write(`[h2a] sync-files (dry-run): incremental push HEAD=${headSha} base=${baseSha}\n`);
2249
2249
  }
2250
2250
  else {
2251
- process.stderr.write(`[remote] sync-files (dry-run): bootstrap push HEAD=${headSha} (no base on CP)\n`);
2251
+ process.stderr.write(`[h2a] sync-files (dry-run): bootstrap push HEAD=${headSha} (no base on CP)\n`);
2252
2252
  }
2253
2253
  return;
2254
2254
  }
2255
2255
  if (!baseSha) {
2256
2256
  // Bootstrap: upload full git bundle.
2257
- process.stderr.write(`[remote] sync-files: bootstrap — building git bundle (HEAD=${headSha})\n`);
2257
+ process.stderr.write(`[h2a] sync-files: bootstrap — building git bundle (HEAD=${headSha})\n`);
2258
2258
  const bundle = buildGitBundle(cwd);
2259
- process.stderr.write(`[remote] sync-files: bundle ${(bundle.byteLength / 1024).toFixed(0)} KiB → ${sessionId}\n`);
2259
+ process.stderr.write(`[h2a] sync-files: bundle ${(bundle.byteLength / 1024).toFixed(0)} KiB → ${sessionId}\n`);
2260
2260
  const uploadRes = await fetch(`${remote.replace(/\/$/, "")}/sessions/${sessionId}/workspace/incremental/bundle`, {
2261
2261
  method: "POST",
2262
2262
  headers: {
@@ -2287,7 +2287,7 @@ export async function main(argv) {
2287
2287
  if (!manifestRes.ok) {
2288
2288
  throw new Error(`manifest record failed: ${manifestRes.status} ${manifestRes.statusText}`);
2289
2289
  }
2290
- process.stderr.write(`[remote] sync-files: bootstrap complete, base=${headSha}\n`);
2290
+ process.stderr.write(`[h2a] sync-files: bootstrap complete, base=${headSha}\n`);
2291
2291
  const { writeSyncStatus, emptyMetrics: em } = await import("./sync-status.js");
2292
2292
  writeSyncStatus(sessionId, {
2293
2293
  state: "synced",
@@ -2300,11 +2300,11 @@ export async function main(argv) {
2300
2300
  }
2301
2301
  else {
2302
2302
  // Incremental push.
2303
- process.stderr.write(`[remote] sync-files: incremental push base=${baseSha} → HEAD=${headSha}\n`);
2303
+ process.stderr.write(`[h2a] sync-files: incremental push base=${baseSha} → HEAD=${headSha}\n`);
2304
2304
  const manifest = buildIncrementalManifest(cwd, baseSha);
2305
2305
  const untrackedPaths = manifest.untrackedManifest.map((e) => e.path);
2306
2306
  const trackedBytes = Buffer.from(manifest.tracked, "base64").byteLength;
2307
- process.stderr.write(`[remote] sync-files: tracked diff ${(trackedBytes / 1024).toFixed(1)} KiB, ${untrackedPaths.length} untracked file(s)\n`);
2307
+ process.stderr.write(`[h2a] sync-files: tracked diff ${(trackedBytes / 1024).toFixed(1)} KiB, ${untrackedPaths.length} untracked file(s)\n`);
2308
2308
  // Upload manifest.
2309
2309
  const manifestRes = await fetch(`${remote.replace(/\/$/, "")}/sessions/${sessionId}/workspace/incremental`, {
2310
2310
  method: "POST",
@@ -2332,7 +2332,7 @@ export async function main(argv) {
2332
2332
  throw new Error(`untracked upload failed: ${tarRes.status} ${tarRes.statusText}`);
2333
2333
  }
2334
2334
  }
2335
- process.stderr.write(`[remote] sync-files: incremental complete, base=${headSha}\n`);
2335
+ process.stderr.write(`[h2a] sync-files: incremental complete, base=${headSha}\n`);
2336
2336
  const { writeSyncStatus: wss, emptyMetrics: em2 } = await import("./sync-status.js");
2337
2337
  wss(sessionId, {
2338
2338
  state: "synced",
@@ -2355,7 +2355,7 @@ export async function main(argv) {
2355
2355
  .action(async (sessionId, podPort, localPort, opts) => {
2356
2356
  const port = Number(podPort);
2357
2357
  if (!Number.isInteger(port) || port < 1 || port > 65535) {
2358
- process.stderr.write(`[remote] invalid pod port "${podPort}"\n`);
2358
+ process.stderr.write(`[h2a] invalid pod port "${podPort}"\n`);
2359
2359
  process.exitCode = 1;
2360
2360
  return;
2361
2361
  }
@@ -2363,7 +2363,7 @@ export async function main(argv) {
2363
2363
  if (localPort !== undefined) {
2364
2364
  local = Number(localPort);
2365
2365
  if (!Number.isInteger(local) || local < 1 || local > 65535) {
2366
- process.stderr.write(`[remote] invalid local port "${localPort}"\n`);
2366
+ process.stderr.write(`[h2a] invalid local port "${localPort}"\n`);
2367
2367
  process.exitCode = 1;
2368
2368
  return;
2369
2369
  }
@@ -2387,7 +2387,7 @@ export async function main(argv) {
2387
2387
  browserCommand
2388
2388
  .command("open <sessionId>")
2389
2389
  .description("Print the steps to open the headful browser view for a session: the " +
2390
- "`remote forward` command to run and the token-gated noVNC URL to open. " +
2390
+ "`h2a forward` command to run and the token-gated noVNC URL to open. " +
2391
2391
  "Default exposure is session-private (owner only, token-gated) and interactive (you drive the 2FA).")
2392
2392
  .option("--local-port <port>", "local port to bind the forward to")
2393
2393
  .option("--policy <policy>", "uat exposure policy: operator-only | session-private | public-expiring (default session-private)")
@@ -2400,7 +2400,7 @@ export async function main(argv) {
2400
2400
  if (!Number.isInteger(localPort) ||
2401
2401
  localPort < 1 ||
2402
2402
  localPort > 65535) {
2403
- process.stderr.write(`[remote] invalid local port "${opts.localPort}"\n`);
2403
+ process.stderr.write(`[h2a] invalid local port "${opts.localPort}"\n`);
2404
2404
  process.exitCode = 1;
2405
2405
  return;
2406
2406
  }
@@ -2410,7 +2410,7 @@ export async function main(argv) {
2410
2410
  policy !== "operator-only" &&
2411
2411
  policy !== "session-private" &&
2412
2412
  policy !== "public-expiring") {
2413
- process.stderr.write(`[remote] invalid --policy "${policy}" (operator-only | session-private | public-expiring)\n`);
2413
+ process.stderr.write(`[h2a] invalid --policy "${policy}" (operator-only | session-private | public-expiring)\n`);
2414
2414
  process.exitCode = 1;
2415
2415
  return;
2416
2416
  }
@@ -2418,7 +2418,7 @@ export async function main(argv) {
2418
2418
  if (opts.ttl !== undefined) {
2419
2419
  ttlMs = Number(opts.ttl);
2420
2420
  if (!Number.isFinite(ttlMs) || ttlMs <= 0) {
2421
- process.stderr.write(`[remote] invalid --ttl "${opts.ttl}"\n`);
2421
+ process.stderr.write(`[h2a] invalid --ttl "${opts.ttl}"\n`);
2422
2422
  process.exitCode = 1;
2423
2423
  return;
2424
2424
  }
@@ -2431,7 +2431,7 @@ export async function main(argv) {
2431
2431
  ...(opts.viewOnly ? { interactive: false } : {}),
2432
2432
  });
2433
2433
  if (!plan.ok) {
2434
- process.stderr.write(`[remote] ${plan.reason}\n`);
2434
+ process.stderr.write(`[h2a] ${plan.reason}\n`);
2435
2435
  process.exitCode = 1;
2436
2436
  return;
2437
2437
  }
@@ -2454,7 +2454,7 @@ export async function main(argv) {
2454
2454
  .option("-r, --resume [convId]", "resume the most recent (or a specific) conversation on the remote CLI")
2455
2455
  .option("--no-attach", "create the remote session without hijacking this terminal; print the attach command instead (for bulk migration / reconnecting your own terminal)")
2456
2456
  .option("--reconnect", "revive a session on the EXISTING workspace without re-pushing files (preserves work done remotely) — use after an accidental exit (Ctrl+C/Ctrl+D) to bring the session back from its retained PVC with path parity + --resume")
2457
- .option("--with <tools>", `comma-separated tool CLIs whose local auth to also bundle into the Pod (known: ${KNOWN_TOOLS.join(", ")}); defaults to 'remote config tools'`)
2457
+ .option("--with <tools>", `comma-separated tool CLIs whose local auth to also bundle into the Pod (known: ${KNOWN_TOOLS.join(", ")}); defaults to 'h2a config tools'`)
2458
2458
  .option("--force", "migrate even if the conversation already has a live writer (risk: conversation .jsonl corruption)")
2459
2459
  .action(async (profile, opts) => {
2460
2460
  const remoteUrl = getConfiguredRemote(opts.remote);
@@ -2503,7 +2503,7 @@ export async function main(argv) {
2503
2503
  const now = Date.now();
2504
2504
  const candidates = listMigrationCandidates();
2505
2505
  if (candidates.length === 0) {
2506
- process.stdout.write("[remote] no local claude sessions found under ~/.claude/projects\n");
2506
+ process.stdout.write("[h2a] no local claude sessions found under ~/.claude/projects\n");
2507
2507
  return;
2508
2508
  }
2509
2509
  process.stdout.write("# AGE SIZE CONVS GIT MIGRATED PATH\n");
@@ -2517,8 +2517,8 @@ export async function main(argv) {
2517
2517
  const missing = c.exists ? "" : " (dir missing)";
2518
2518
  process.stdout.write(`${n} ${age} ${size} ${convs} ${git} ${mig} ${c.path}${missing}\n`);
2519
2519
  });
2520
- process.stdout.write("\nMigrate one: cd <path> && remote migrate forward claude --resume\n" +
2521
- "Pick interactively: remote migrate pick\n");
2520
+ process.stdout.write("\nMigrate one: cd <path> && h2a migrate forward claude --resume\n" +
2521
+ "Pick interactively: h2a migrate pick\n");
2522
2522
  });
2523
2523
  migrateCommand
2524
2524
  .command("pick")
@@ -2526,7 +2526,7 @@ export async function main(argv) {
2526
2526
  .option("--remote <url>", "control-plane URL (defaults to configured remote)")
2527
2527
  .option("--profile <profile>", "CLI profile to start remotely", "claude")
2528
2528
  .option("--no-resume", "do not resume the conversation on the remote CLI")
2529
- .option("--with <tools>", `comma-separated tool CLIs whose auth to bundle (known: ${KNOWN_TOOLS.join(", ")}); defaults to 'remote config tools'`)
2529
+ .option("--with <tools>", `comma-separated tool CLIs whose auth to bundle (known: ${KNOWN_TOOLS.join(", ")}); defaults to 'h2a config tools'`)
2530
2530
  .action(async (opts) => {
2531
2531
  const remoteUrl = getConfiguredRemote(opts.remote);
2532
2532
  await ensureConnected(remoteUrl);
@@ -2535,7 +2535,7 @@ export async function main(argv) {
2535
2535
  const now = Date.now();
2536
2536
  const candidates = listMigrationCandidates().filter((c) => c.exists && c.isGit);
2537
2537
  if (candidates.length === 0) {
2538
- process.stdout.write("[remote] no migratable git-backed sessions found\n");
2538
+ process.stdout.write("[h2a] no migratable git-backed sessions found\n");
2539
2539
  return;
2540
2540
  }
2541
2541
  process.stdout.write("Local sessions you can migrate:\n\n");
@@ -2559,7 +2559,7 @@ export async function main(argv) {
2559
2559
  .filter((n) => Number.isInteger(n) && n >= 1 && n <= candidates.length)
2560
2560
  .map((n) => candidates[n - 1]);
2561
2561
  if (chosen.length === 0) {
2562
- process.stdout.write("[remote] nothing selected — cancelled\n");
2562
+ process.stdout.write("[h2a] nothing selected — cancelled\n");
2563
2563
  return;
2564
2564
  }
2565
2565
  for (const c of chosen) {
@@ -2575,7 +2575,7 @@ export async function main(argv) {
2575
2575
  });
2576
2576
  }
2577
2577
  catch (err) {
2578
- process.stderr.write(`[remote] failed to migrate ${c.path}: ${String(err)}\n`);
2578
+ process.stderr.write(`[h2a] failed to migrate ${c.path}: ${String(err)}\n`);
2579
2579
  }
2580
2580
  }
2581
2581
  });
@@ -2604,15 +2604,15 @@ export async function main(argv) {
2604
2604
  if (match) {
2605
2605
  const lastWs = match.wsHistory[match.wsHistory.length - 1];
2606
2606
  if (lastWs && !workspaceId) {
2607
- process.stderr.write(`[remote] --lineage ${lineageId} → workspace history includes ${lastWs}\n`);
2607
+ process.stderr.write(`[h2a] --lineage ${lineageId} → workspace history includes ${lastWs}\n`);
2608
2608
  }
2609
2609
  if (match.incarnation.remote?.sessionId) {
2610
2610
  knownSessionId = match.incarnation.remote.sessionId;
2611
- process.stderr.write(`[remote] targeting session ${knownSessionId} (from lineage)\n`);
2611
+ process.stderr.write(`[h2a] targeting session ${knownSessionId} (from lineage)\n`);
2612
2612
  }
2613
2613
  }
2614
2614
  else {
2615
- process.stderr.write(`[remote] warning: lineage ${lineageId} not found locally; proceeding without lineage filter\n`);
2615
+ process.stderr.write(`[h2a] warning: lineage ${lineageId} not found locally; proceeding without lineage filter\n`);
2616
2616
  }
2617
2617
  }
2618
2618
  await migrateBack({
@@ -2637,14 +2637,14 @@ export async function main(argv) {
2637
2637
  // Step 1: checkReadiness
2638
2638
  const readiness = checkReadiness({ cwd, profile: resolvedProfile });
2639
2639
  if (readiness.blockers.length > 0) {
2640
- process.stderr.write(`[remote] migration blocked:\n${readiness.blockers.map((b) => ` • ${b}`).join("\n")}\n`);
2640
+ process.stderr.write(`[h2a] migration blocked:\n${readiness.blockers.map((b) => ` • ${b}`).join("\n")}\n`);
2641
2641
  process.exitCode = 1;
2642
2642
  return;
2643
2643
  }
2644
2644
  if (readiness.warnings.length > 0) {
2645
- process.stderr.write(`[remote] readiness warnings:\n${readiness.warnings.map((w) => ` ⚠ ${w}`).join("\n")}\n`);
2645
+ process.stderr.write(`[h2a] readiness warnings:\n${readiness.warnings.map((w) => ` ⚠ ${w}`).join("\n")}\n`);
2646
2646
  }
2647
- process.stderr.write(`[remote] readiness ok (mode: ${readiness.mode}, pending: ${readiness.pending.files} files / ${(readiness.pending.bytes / 1024).toFixed(0)} KiB)\n`);
2647
+ process.stderr.write(`[h2a] readiness ok (mode: ${readiness.mode}, pending: ${readiness.pending.files} files / ${(readiness.pending.bytes / 1024).toFixed(0)} KiB)\n`);
2648
2648
  // Step 2: compute durable workspace id (best-effort)
2649
2649
  let wsHex = "ws:unknown";
2650
2650
  try {
@@ -2685,21 +2685,21 @@ export async function main(argv) {
2685
2685
  const TTL_MS = 300_000; // 5 minutes
2686
2686
  const leaseResult = acquireLease(lineageId, thisInstance, localSlug, "local", TTL_MS, cwd);
2687
2687
  if ("error" in leaseResult) {
2688
- process.stderr.write(`[remote] lease conflict: lineage ${lineageId} is held by ${leaseResult.current.holder} (${leaseResult.current.location}) until ${leaseResult.current.expiresAt}\n` +
2689
- `[remote] wait for the lease to expire or use \`remote migrate to-remote --force\` (not yet implemented)\n`);
2688
+ process.stderr.write(`[h2a] lease conflict: lineage ${lineageId} is held by ${leaseResult.current.holder} (${leaseResult.current.location}) until ${leaseResult.current.expiresAt}\n` +
2689
+ `[h2a] wait for the lease to expire or use \`h2a migrate to-remote --force\` (not yet implemented)\n`);
2690
2690
  process.exitCode = 1;
2691
2691
  return;
2692
2692
  }
2693
- process.stderr.write(`[remote] lineage: ${lineageId} (epoch ${leaseResult.epoch}, holder ${thisInstance})\n`);
2693
+ process.stderr.write(`[h2a] lineage: ${lineageId} (epoch ${leaseResult.epoch}, holder ${thisInstance})\n`);
2694
2694
  if (opts.dryRun) {
2695
- process.stderr.write(`[remote] --dry-run: would call migrate forward ${resolvedProfile} (mode: ${readiness.mode})\n`);
2696
- process.stderr.write(`[remote] dry-run complete — no session created, lease NOT handed off\n`);
2695
+ process.stderr.write(`[h2a] --dry-run: would call migrate forward ${resolvedProfile} (mode: ${readiness.mode})\n`);
2696
+ process.stderr.write(`[h2a] dry-run complete — no session created, lease NOT handed off\n`);
2697
2697
  // Release the lease on dry-run (we only acquired it to check)
2698
2698
  return;
2699
2699
  }
2700
2700
  // Step 5: suspend local incarnation BEFORE handoff
2701
2701
  suspendLocalIncarnation(lineageId, cwd);
2702
- process.stderr.write(`[remote] local incarnation suspended (sentinel written)\n`);
2702
+ process.stderr.write(`[h2a] local incarnation suspended (sentinel written)\n`);
2703
2703
  // Step 6: call existing migrate forward
2704
2704
  const remoteUrl = getConfiguredRemote(opts.remote);
2705
2705
  await ensureConnected(remoteUrl);
@@ -2718,8 +2718,8 @@ export async function main(argv) {
2718
2718
  catch (err) {
2719
2719
  // On error: resume local incarnation (undo suspend)
2720
2720
  resumeLocalIncarnation(lineageId, cwd);
2721
- process.stderr.write(`[remote] migrate forward failed: ${err.message}\n` +
2722
- `[remote] local incarnation resumed\n`);
2721
+ process.stderr.write(`[h2a] migrate forward failed: ${err.message}\n` +
2722
+ `[h2a] local incarnation resumed\n`);
2723
2723
  process.exitCode = 1;
2724
2724
  return;
2725
2725
  }
@@ -2727,10 +2727,10 @@ export async function main(argv) {
2727
2727
  const remoteHolder = `remote:pod:${sessionId ?? "unknown"}`;
2728
2728
  const handoffResult = handoffLease(lineageId, thisInstance, leaseResult.epoch, remoteHolder, sessionId ?? "unknown", "remote", TTL_MS, cwd);
2729
2729
  if ("error" in handoffResult) {
2730
- process.stderr.write(`[remote] warning: lease handoff failed (${handoffResult.error}) — lease may be stale\n`);
2730
+ process.stderr.write(`[h2a] warning: lease handoff failed (${handoffResult.error}) — lease may be stale\n`);
2731
2731
  }
2732
2732
  else {
2733
- process.stderr.write(`[remote] lease handed off to remote (epoch ${handoffResult.epoch})\n`);
2733
+ process.stderr.write(`[h2a] lease handed off to remote (epoch ${handoffResult.epoch})\n`);
2734
2734
  }
2735
2735
  // Persist remote session id in the lineage record so `migrate back
2736
2736
  // --lineage <id>` can target it without listing all sessions.
@@ -2748,7 +2748,7 @@ export async function main(argv) {
2748
2748
  }
2749
2749
  }
2750
2750
  // Step 8: summary
2751
- process.stderr.write(`[remote] migration complete:\n` +
2751
+ process.stderr.write(`[h2a] migration complete:\n` +
2752
2752
  ` lineage: ${lineageId}\n` +
2753
2753
  ` session: ${sessionId ?? "unknown"}\n` +
2754
2754
  ` remote: ${remoteUrl}\n`);
@@ -2770,7 +2770,7 @@ export async function main(argv) {
2770
2770
  const repoBlocker = readiness.blockers.find((b) => b.startsWith("repo:"));
2771
2771
  const toLocalBlockers = [authBlocker, repoBlocker].filter((b) => b !== undefined);
2772
2772
  if (toLocalBlockers.length > 0) {
2773
- process.stderr.write(`[remote] migration blocked:\n${toLocalBlockers.map((b) => ` • ${b}`).join("\n")}\n`);
2773
+ process.stderr.write(`[h2a] migration blocked:\n${toLocalBlockers.map((b) => ` • ${b}`).join("\n")}\n`);
2774
2774
  process.exitCode = 1;
2775
2775
  return;
2776
2776
  }
@@ -2778,13 +2778,13 @@ export async function main(argv) {
2778
2778
  const lineages = listLineages(cwd);
2779
2779
  const remoteLineage = lineages.find((l) => l.incarnation.remote !== null);
2780
2780
  if (!remoteLineage) {
2781
- process.stderr.write(`[remote] no active remote lineage found in ${cwd}/.remote/lineages/\n` +
2782
- `[remote] run \`remote migrate to-remote\` first, or check \`ls .remote/lineages/\`\n`);
2781
+ process.stderr.write(`[h2a] no active remote lineage found in ${cwd}/.remote/lineages/\n` +
2782
+ `[h2a] run \`h2a migrate to-remote\` first, or check \`ls .remote/lineages/\`\n`);
2783
2783
  process.exitCode = 1;
2784
2784
  return;
2785
2785
  }
2786
2786
  const lineageId = remoteLineage.lineage;
2787
- process.stderr.write(`[remote] found remote lineage ${lineageId} (session ${remoteLineage.incarnation.remote?.sessionId ?? "unknown"})\n`);
2787
+ process.stderr.write(`[h2a] found remote lineage ${lineageId} (session ${remoteLineage.incarnation.remote?.sessionId ?? "unknown"})\n`);
2788
2788
  // Step 3: try to acquire the local lease
2789
2789
  const localSlug = basename(cwd);
2790
2790
  const thisInstance = `claude:local:${randomUUID().replace(/-/g, "").slice(0, 12)}`;
@@ -2793,23 +2793,23 @@ export async function main(argv) {
2793
2793
  if ("error" in leaseResult) {
2794
2794
  const current = leaseResult.current;
2795
2795
  if (current.location === "remote") {
2796
- process.stderr.write(`[remote] un agent remote tient le lease ; attendez l'expiry (${current.expiresAt}) ou utilisez \`remote migrate to-local --force\` pour forcer\n`);
2796
+ process.stderr.write(`[h2a] un agent remote tient le lease ; attendez l'expiry (${current.expiresAt}) ou utilisez \`h2a migrate to-local --force\` pour forcer\n`);
2797
2797
  }
2798
2798
  else {
2799
- process.stderr.write(`[remote] lease conflict: held by ${current.holder} until ${current.expiresAt}\n`);
2799
+ process.stderr.write(`[h2a] lease conflict: held by ${current.holder} until ${current.expiresAt}\n`);
2800
2800
  }
2801
2801
  process.exitCode = 1;
2802
2802
  return;
2803
2803
  }
2804
- process.stderr.write(`[remote] lease acquired (epoch ${leaseResult.epoch}, holder ${thisInstance})\n`);
2804
+ process.stderr.write(`[h2a] lease acquired (epoch ${leaseResult.epoch}, holder ${thisInstance})\n`);
2805
2805
  if (opts.dryRun) {
2806
- process.stderr.write(`[remote] --dry-run: would call migrate back (lineage ${lineageId})\n`);
2807
- process.stderr.write(`[remote] dry-run complete — no session stopped, lease NOT committed\n`);
2806
+ process.stderr.write(`[h2a] --dry-run: would call migrate back (lineage ${lineageId})\n`);
2807
+ process.stderr.write(`[h2a] dry-run complete — no session stopped, lease NOT committed\n`);
2808
2808
  return;
2809
2809
  }
2810
2810
  // Step 4: reset suspend sentinel (local incarnation resumes)
2811
2811
  resumeLocalIncarnation(lineageId, cwd);
2812
- process.stderr.write(`[remote] local incarnation resumed (sentinel cleared)\n`);
2812
+ process.stderr.write(`[h2a] local incarnation resumed (sentinel cleared)\n`);
2813
2813
  // Step 5: call existing migrate back
2814
2814
  const remoteUrl = getConfiguredRemote(opts.remote);
2815
2815
  const onConflict = opts.onConflict === "backup"
@@ -2824,12 +2824,12 @@ export async function main(argv) {
2824
2824
  });
2825
2825
  }
2826
2826
  catch (err) {
2827
- process.stderr.write(`[remote] migrate back failed: ${err.message}\n`);
2827
+ process.stderr.write(`[h2a] migrate back failed: ${err.message}\n`);
2828
2828
  process.exitCode = 1;
2829
2829
  return;
2830
2830
  }
2831
2831
  // Step 6: summary
2832
- process.stderr.write(`[remote] to-local complete (lineage ${lineageId})\n`);
2832
+ process.stderr.write(`[h2a] to-local complete (lineage ${lineageId})\n`);
2833
2833
  });
2834
2834
  // ---------------------------------------------------------------------------
2835
2835
  // plugin — npm packages providing a CLI + an MCP server, for all agent CLIs
@@ -2847,7 +2847,7 @@ export async function main(argv) {
2847
2847
  .option("--install <shell>", "install in Pods by running this shell command (non-npm)")
2848
2848
  .action((pkgOrName, opts) => {
2849
2849
  if (opts.curl !== undefined && opts.install !== undefined) {
2850
- process.stderr.write("[remote] pass only one of --curl / --install\n");
2850
+ process.stderr.write("[h2a] pass only one of --curl / --install\n");
2851
2851
  process.exitCode = 1;
2852
2852
  return;
2853
2853
  }
@@ -2936,7 +2936,7 @@ export async function main(argv) {
2936
2936
  .option("--no-gw", "alias for --no-llm-gateway")
2937
2937
  .action(async (slug, opts) => {
2938
2938
  if (!tmuxAvailable()) {
2939
- process.stderr.write("[remote] tmux is not installed locally — `remote resume` needs it (e.g. `sudo apt install tmux`).\n");
2939
+ process.stderr.write("[h2a] tmux is not installed locally — `h2a resume` needs it (e.g. `sudo apt install tmux`).\n");
2940
2940
  process.exitCode = 1;
2941
2941
  return;
2942
2942
  }
@@ -2946,7 +2946,7 @@ export async function main(argv) {
2946
2946
  ? "codex"
2947
2947
  : undefined;
2948
2948
  if (opts.claude !== undefined && opts.codex !== undefined) {
2949
- process.stderr.write("[remote] pass only one of --claude or --codex.\n");
2949
+ process.stderr.write("[h2a] pass only one of --claude or --codex.\n");
2950
2950
  process.exitCode = 1;
2951
2951
  return;
2952
2952
  }
@@ -2956,17 +2956,17 @@ export async function main(argv) {
2956
2956
  ? opts.codex.trim()
2957
2957
  : undefined;
2958
2958
  if (opts.last && !explicitProfile) {
2959
- process.stderr.write("[remote] --last is only valid with --claude or --codex.\n");
2959
+ process.stderr.write("[h2a] --last is only valid with --claude or --codex.\n");
2960
2960
  process.exitCode = 1;
2961
2961
  return;
2962
2962
  }
2963
2963
  if (opts.last && explicitConvId) {
2964
- process.stderr.write("[remote] pass either a conversation id or --last, not both.\n");
2964
+ process.stderr.write("[h2a] pass either a conversation id or --last, not both.\n");
2965
2965
  process.exitCode = 1;
2966
2966
  return;
2967
2967
  }
2968
2968
  if (slug === undefined && !explicitProfile) {
2969
- process.stderr.write("[remote] resume needs a known session name, or `remote resume --claude [conversation-id]` / `remote resume --codex [conversation-id]` from the repo directory.\n");
2969
+ process.stderr.write("[h2a] resume needs a known session name, or `h2a resume --claude [conversation-id]` / `h2a resume --codex [conversation-id]` from the repo directory.\n");
2970
2970
  process.exitCode = 1;
2971
2971
  return;
2972
2972
  }
@@ -2978,7 +2978,7 @@ export async function main(argv) {
2978
2978
  ? localConvStat(process.cwd())?.convId
2979
2979
  : explicitConvId;
2980
2980
  if (explicitProfile === "claude" && opts.last && !resolvedConvId) {
2981
- process.stderr.write(`[remote] cannot resume last Claude conversation: no local Claude conversation found for ${process.cwd()}.\n`);
2981
+ process.stderr.write(`[h2a] cannot resume last Claude conversation: no local Claude conversation found for ${process.cwd()}.\n`);
2982
2982
  process.exitCode = 1;
2983
2983
  return;
2984
2984
  }
@@ -2998,31 +2998,31 @@ export async function main(argv) {
2998
2998
  : registryEntry;
2999
2999
  const localIdle = local ? localSessionIdle(local.name) : false;
3000
3000
  if (!entry && local && !localIdle && !opts.replace) {
3001
- process.stderr.write(`[remote] local session ${displaySlug} already exists and does not look idle; no new CLI was started.\n`);
3001
+ process.stderr.write(`[h2a] local session ${displaySlug} already exists and does not look idle; no new CLI was started.\n`);
3002
3002
  if (opts.attach || explicitProfile) {
3003
- process.stderr.write(`[remote] switching to existing session ${displaySlug}\n`);
3003
+ process.stderr.write(`[h2a] switching to existing session ${displaySlug}\n`);
3004
3004
  process.exitCode = attachLocalSession(local.name);
3005
3005
  return;
3006
3006
  }
3007
- process.stderr.write(`[remote] attach: remote attach ${displaySlug}\n`);
3007
+ process.stderr.write(`[h2a] attach: h2a attach ${displaySlug}\n`);
3008
3008
  process.exitCode = 2;
3009
3009
  return;
3010
3010
  }
3011
3011
  if (!entry) {
3012
- process.stderr.write(`[remote] cannot resume ${displaySlug}: registry has no single local-tmux entry with profile, cwd and convId.\n`);
3012
+ process.stderr.write(`[h2a] cannot resume ${displaySlug}: registry has no single local-tmux entry with profile, cwd and convId.\n`);
3013
3013
  if (local) {
3014
- process.stderr.write(`[remote] local session ${displaySlug} exists but cannot be relaunched without a recorded convId.\n`);
3015
- process.stderr.write(`[remote] attach: remote attach ${displaySlug}\n`);
3014
+ process.stderr.write(`[h2a] local session ${displaySlug} exists but cannot be relaunched without a recorded convId.\n`);
3015
+ process.stderr.write(`[h2a] attach: h2a attach ${displaySlug}\n`);
3016
3016
  }
3017
3017
  else {
3018
- process.stderr.write(`[remote] start explicitly: remote resume ${displaySlug} --claude [convId]\n`);
3018
+ process.stderr.write(`[h2a] start explicitly: h2a resume ${displaySlug} --claude [convId]\n`);
3019
3019
  }
3020
3020
  process.exitCode = 1;
3021
3021
  return;
3022
3022
  }
3023
3023
  const resumeSlug = entry.id;
3024
3024
  if (!entry.cwd || !entry.tool || (!entry.convId && !explicitProfile)) {
3025
- process.stderr.write(`[remote] cannot resume ${displaySlug}: registry entry is incomplete (need profile, cwd and convId).\n`);
3025
+ process.stderr.write(`[h2a] cannot resume ${displaySlug}: registry entry is incomplete (need profile, cwd and convId).\n`);
3026
3026
  process.exitCode = 1;
3027
3027
  return;
3028
3028
  }
@@ -3032,7 +3032,7 @@ export async function main(argv) {
3032
3032
  gatewayMode = gatewayModeFromOptions(opts);
3033
3033
  }
3034
3034
  catch (error) {
3035
- process.stderr.write(`[remote] ${error.message}.\n`);
3035
+ process.stderr.write(`[h2a] ${error.message}.\n`);
3036
3036
  process.exitCode = 1;
3037
3037
  return;
3038
3038
  }
@@ -3044,7 +3044,7 @@ export async function main(argv) {
3044
3044
  last: Boolean(opts.last),
3045
3045
  });
3046
3046
  if (args.length === 0) {
3047
- process.stderr.write(`[remote] cannot resume ${displaySlug}: profile "${profile}" has no verified local resume argv.\n`);
3047
+ process.stderr.write(`[h2a] cannot resume ${displaySlug}: profile "${profile}" has no verified local resume argv.\n`);
3048
3048
  process.exitCode = 1;
3049
3049
  return;
3050
3050
  }
@@ -3059,49 +3059,49 @@ export async function main(argv) {
3059
3059
  })
3060
3060
  : "unknown";
3061
3061
  if (gatewayEnvStatus === "missing" || gatewayEnvStatus === "stale") {
3062
- process.stderr.write(`[remote] local session ${displaySlug} is running without current llm-mesh env (${gatewayEnvStatus}); not restarting an active session automatically.\n`);
3063
- process.stderr.write(`[remote] to relaunch with gateway env, confirm explicitly: remote resume ${displaySlug} --replace\n`);
3062
+ process.stderr.write(`[h2a] local session ${displaySlug} is running without current llm-mesh env (${gatewayEnvStatus}); not restarting an active session automatically.\n`);
3063
+ process.stderr.write(`[h2a] to relaunch with gateway env, confirm explicitly: h2a resume ${displaySlug} --replace\n`);
3064
3064
  }
3065
- process.stderr.write(`[remote] local session ${displaySlug} already exists and does not look idle; no new ${profile} was started.\n`);
3065
+ process.stderr.write(`[h2a] local session ${displaySlug} already exists and does not look idle; no new ${profile} was started.\n`);
3066
3066
  if (opts.attach || explicitProfile) {
3067
3067
  if (explicitProfile && currentTmuxSessionIs(local.name)) {
3068
- process.stderr.write(`[remote] already inside ${displaySlug}; running ${profile} resume in this pane\n`);
3068
+ process.stderr.write(`[h2a] already inside ${displaySlug}; running ${profile} resume in this pane\n`);
3069
3069
  process.exitCode = runLocalCliForeground(command, args);
3070
3070
  return;
3071
3071
  }
3072
- process.stderr.write(`[remote] switching to existing session ${displaySlug}\n`);
3072
+ process.stderr.write(`[h2a] switching to existing session ${displaySlug}\n`);
3073
3073
  process.exitCode = attachLocalSession(local.name);
3074
3074
  return;
3075
3075
  }
3076
- process.stderr.write(`[remote] attach: remote attach ${displaySlug}\n`);
3076
+ process.stderr.write(`[h2a] attach: h2a attach ${displaySlug}\n`);
3077
3077
  process.exitCode = 2;
3078
3078
  return;
3079
3079
  }
3080
3080
  if (local) {
3081
3081
  if (!localIdle && opts.replace) {
3082
- process.stderr.write(`[remote] local session ${displaySlug} does not look idle; --replace will kill tmux session ${local.name} before resuming${entry.convId ? ` ${entry.convId}` : ""}.\n`);
3082
+ process.stderr.write(`[h2a] local session ${displaySlug} does not look idle; --replace will kill tmux session ${local.name} before resuming${entry.convId ? ` ${entry.convId}` : ""}.\n`);
3083
3083
  }
3084
3084
  if (!opts.replace) {
3085
- process.stderr.write(`[remote] local session ${displaySlug} already exists.\n`);
3086
- process.stderr.write(`[remote] pane appears idle; replacing it will kill tmux session ${local.name} and resume${entry.convId ? ` conversation ${entry.convId}` : ""} in ${entry.cwd}.\n`);
3087
- process.stderr.write(`[remote] If another CLI is still writing this conversation, replacing can corrupt the .jsonl.\n`);
3085
+ process.stderr.write(`[h2a] local session ${displaySlug} already exists.\n`);
3086
+ process.stderr.write(`[h2a] pane appears idle; replacing it will kill tmux session ${local.name} and resume${entry.convId ? ` conversation ${entry.convId}` : ""} in ${entry.cwd}.\n`);
3087
+ process.stderr.write(`[h2a] If another CLI is still writing this conversation, replacing can corrupt the .jsonl.\n`);
3088
3088
  if (!(await confirmReplace(displaySlug))) {
3089
- process.stderr.write(`[remote] takeover requires confirmation.\n`);
3090
- process.stderr.write(`[remote] interactive: remote resume ${displaySlug}\n`);
3091
- process.stderr.write(`[remote] explicit replace: remote resume ${displaySlug} --replace\n`);
3092
- process.stderr.write(`[remote] manual path: remote stop ${displaySlug} --reason restart && remote resume ${displaySlug}\n`);
3089
+ process.stderr.write(`[h2a] takeover requires confirmation.\n`);
3090
+ process.stderr.write(`[h2a] interactive: h2a resume ${displaySlug}\n`);
3091
+ process.stderr.write(`[h2a] explicit replace: h2a resume ${displaySlug} --replace\n`);
3092
+ process.stderr.write(`[h2a] manual path: h2a stop ${displaySlug} --reason restart && h2a resume ${displaySlug}\n`);
3093
3093
  process.exitCode = 1;
3094
3094
  return;
3095
3095
  }
3096
3096
  }
3097
3097
  const rechecked = findLocalSession(displaySlug);
3098
3098
  if (!rechecked) {
3099
- process.stderr.write(`[remote] local session ${displaySlug} changed state before replace; aborting.\n`);
3099
+ process.stderr.write(`[h2a] local session ${displaySlug} changed state before replace; aborting.\n`);
3100
3100
  process.exitCode = 1;
3101
3101
  return;
3102
3102
  }
3103
3103
  if (!opts.replace && !localSessionIdle(rechecked.name)) {
3104
- process.stderr.write(`[remote] local session ${displaySlug} is no longer idle; aborting.\n`);
3104
+ process.stderr.write(`[h2a] local session ${displaySlug} is no longer idle; aborting.\n`);
3105
3105
  process.exitCode = 1;
3106
3106
  return;
3107
3107
  }
@@ -3121,11 +3121,11 @@ export async function main(argv) {
3121
3121
  }
3122
3122
  }
3123
3123
  if (!killLocalSession(rechecked.name)) {
3124
- process.stderr.write(`[remote] local session ${displaySlug} could not be killed; no new ${profile} was started.\n`);
3124
+ process.stderr.write(`[h2a] local session ${displaySlug} could not be killed; no new ${profile} was started.\n`);
3125
3125
  process.exitCode = 1;
3126
3126
  return;
3127
3127
  }
3128
- process.stderr.write(`[remote] replaced local session ${displaySlug} (${rechecked.name})\n`);
3128
+ process.stderr.write(`[h2a] replaced local session ${displaySlug} (${rechecked.name})\n`);
3129
3129
  }
3130
3130
  else {
3131
3131
  if (entry.convId) {
@@ -3153,19 +3153,19 @@ export async function main(argv) {
3153
3153
  ...(entry.convId ? { convId: entry.convId } : {}),
3154
3154
  ...(gatewayMode !== "auto" ? { gatewayMode } : {}),
3155
3155
  });
3156
- process.stderr.write(`[remote] resumed local session ${resumeSlug} (${profile} resume${entry.convId ? ` ${entry.convId}` : ""} in ${entry.cwd})\n`);
3156
+ process.stderr.write(`[h2a] resumed local session ${resumeSlug} (${profile} resume${entry.convId ? ` ${entry.convId}` : ""} in ${entry.cwd})\n`);
3157
3157
  if (gateway) {
3158
- process.stderr.write(`[remote] gateway active: ${gateway}\n`);
3158
+ process.stderr.write(`[h2a] gateway active: ${gateway}\n`);
3159
3159
  }
3160
3160
  if (opts.attach) {
3161
3161
  process.exitCode = attachLocalSession(name);
3162
3162
  return;
3163
3163
  }
3164
- process.stderr.write(`[remote] attach: remote attach ${resumeSlug}\n`);
3164
+ process.stderr.write(`[h2a] attach: h2a attach ${resumeSlug}\n`);
3165
3165
  });
3166
3166
  program
3167
3167
  .command("run <profile> [path]")
3168
- .description("Start a LOCAL session in tmux (claude/codex/…) in <path> (default: cwd), then attach this terminal by default. Remote applies its embedded scroll-safe tmux profile at launch; no ~/.tmux.conf is required. Manage it like a remote one: `remote ls`, `remote attach <slug>`, `remote stop <slug>`. Detach with Ctrl-b d; the session keeps running.")
3168
+ .description("Start a LOCAL session in tmux (claude/codex/…) in <path> (default: cwd), then attach this terminal by default. Remote applies its embedded scroll-safe tmux profile at launch; no ~/.tmux.conf is required. Manage it like a remote one: `h2a ls`, `h2a attach <slug>`, `h2a stop <slug>`. Detach with Ctrl-b d; the session keeps running.")
3169
3169
  .option("--no-attach", "start detached and print the attach command instead (for scripts / fan-out orchestration)")
3170
3170
  .option("-r, --resume <convId>", "resume a specific conversation in the CLI")
3171
3171
  .option("--force", "start even if the conversation already has a live writer (risk: conversation .jsonl corruption)")
@@ -3178,7 +3178,7 @@ export async function main(argv) {
3178
3178
  .option("--no-gw", "alias for --no-llm-gateway")
3179
3179
  .action(async (profile, path, opts) => {
3180
3180
  if (!tmuxAvailable()) {
3181
- process.stderr.write("[remote] tmux is not installed locally — `remote run` needs it (e.g. `sudo apt install tmux`).\n");
3181
+ process.stderr.write("[h2a] tmux is not installed locally — `h2a run` needs it (e.g. `sudo apt install tmux`).\n");
3182
3182
  process.exitCode = 1;
3183
3183
  return;
3184
3184
  }
@@ -3186,14 +3186,14 @@ export async function main(argv) {
3186
3186
  if (opts.count !== undefined) {
3187
3187
  count = Number(opts.count);
3188
3188
  if (!Number.isInteger(count) || count < 1) {
3189
- process.stderr.write(`[remote] --count must be a whole number ≥ 1\n`);
3189
+ process.stderr.write(`[h2a] --count must be a whole number ≥ 1\n`);
3190
3190
  process.exitCode = 1;
3191
3191
  return;
3192
3192
  }
3193
3193
  // Fanning out N agents on the SAME conversation = N writers on one
3194
3194
  // .jsonl (corruption). A fan-out is N FRESH conversations.
3195
3195
  if (count > 1 && opts.resume) {
3196
- process.stderr.write(`[remote] --count > 1 cannot combine with -r/--resume (each fanned agent is a fresh conversation; resuming one into N would corrupt it)\n`);
3196
+ process.stderr.write(`[h2a] --count > 1 cannot combine with -r/--resume (each fanned agent is a fresh conversation; resuming one into N would corrupt it)\n`);
3197
3197
  process.exitCode = 1;
3198
3198
  return;
3199
3199
  }
@@ -3213,9 +3213,9 @@ export async function main(argv) {
3213
3213
  .filter((slug) => slug !== undefined);
3214
3214
  if (existingLocalSessions.length > 0) {
3215
3215
  for (const slug of existingLocalSessions) {
3216
- process.stderr.write(`[remote] local session ${slug} already exists; no new ${profile} was started.\n`);
3217
- process.stderr.write(`[remote] attach: remote attach ${slug}\n`);
3218
- process.stderr.write(`[remote] stop first: remote stop ${slug} --reason restart\n`);
3216
+ process.stderr.write(`[h2a] local session ${slug} already exists; no new ${profile} was started.\n`);
3217
+ process.stderr.write(`[h2a] attach: h2a attach ${slug}\n`);
3218
+ process.stderr.write(`[h2a] stop first: h2a stop ${slug} --reason restart\n`);
3219
3219
  }
3220
3220
  process.exitCode = 1;
3221
3221
  return;
@@ -3244,7 +3244,7 @@ export async function main(argv) {
3244
3244
  gatewayMode = gatewayModeFromOptions(opts);
3245
3245
  }
3246
3246
  catch (error) {
3247
- process.stderr.write(`[remote] ${error.message}.\n`);
3247
+ process.stderr.write(`[h2a] ${error.message}.\n`);
3248
3248
  process.exitCode = 1;
3249
3249
  return;
3250
3250
  }
@@ -3255,7 +3255,7 @@ export async function main(argv) {
3255
3255
  ? localResumeArgs(profile, opts.resume, { bare: useBare })
3256
3256
  : localStartArgs(profile, { bare: useBare });
3257
3257
  if (opts.resume && args.length === 0) {
3258
- process.stderr.write(`[remote] profile "${profile}" has no verified local resume argv; start it without -r/--resume\n`);
3258
+ process.stderr.write(`[h2a] profile "${profile}" has no verified local resume argv; start it without -r/--resume\n`);
3259
3259
  process.exitCode = 1;
3260
3260
  return;
3261
3261
  }
@@ -3279,22 +3279,22 @@ export async function main(argv) {
3279
3279
  // it the default. Never fails the run.
3280
3280
  if (opts.h2a || h2a.enabled) {
3281
3281
  if (startH2aWindow(name, cwd, h2a.command)) {
3282
- process.stderr.write(`[remote] h2a window started in ${slug} (${h2a.command})\n`);
3282
+ process.stderr.write(`[h2a] h2a window started in ${slug} (${h2a.command})\n`);
3283
3283
  }
3284
3284
  }
3285
3285
  }
3286
3286
  if (count > 1) {
3287
- process.stderr.write(`[remote] ${started.length} ${profile} agents started in ${cwd}: ${started.map((s) => s.slug).join(", ")}\n` +
3288
- `[remote] attach one with: remote attach <slug>\n`);
3287
+ process.stderr.write(`[h2a] ${started.length} ${profile} agents started in ${cwd}: ${started.map((s) => s.slug).join(", ")}\n` +
3288
+ `[h2a] attach one with: h2a attach <slug>\n`);
3289
3289
  return; // never auto-attach a fleet
3290
3290
  }
3291
3291
  const only = started[0];
3292
- process.stderr.write(`[remote] local session ${only.slug} started (${profile}${opts.resume ? ` --resume ${opts.resume}` : ""} in ${cwd})\n`);
3292
+ process.stderr.write(`[h2a] local session ${only.slug} started (${profile}${opts.resume ? ` --resume ${opts.resume}` : ""} in ${cwd})\n`);
3293
3293
  // Single local runs hand off the terminal by default: there is no value
3294
3294
  // in making users type a second `remote attach <slug>` command. Commander
3295
3295
  // sets opts.attach=false only for --no-attach.
3296
3296
  if (opts.attach === false) {
3297
- process.stderr.write(`[remote] attach with: remote attach ${only.slug}\n`);
3297
+ process.stderr.write(`[h2a] attach with: h2a attach ${only.slug}\n`);
3298
3298
  return;
3299
3299
  }
3300
3300
  process.exitCode = attachLocalSession(only.name);
@@ -3320,7 +3320,7 @@ export async function main(argv) {
3320
3320
  .option("--account <id>", "force a specific account from the pool (bypass selectAccountWithFallback). E.g. claude-code-1234567890.")
3321
3321
  .action(async (type, task, opts) => {
3322
3322
  if (!isDelegateType(type)) {
3323
- process.stderr.write(`[remote] unknown agent type "${type}" (use: claude | codex | agy)\n`);
3323
+ process.stderr.write(`[h2a] unknown agent type "${type}" (use: claude | codex | agy)\n`);
3324
3324
  process.exitCode = 1;
3325
3325
  return;
3326
3326
  }
@@ -3332,7 +3332,7 @@ export async function main(argv) {
3332
3332
  assertSafeName(jobId);
3333
3333
  }
3334
3334
  catch (err) {
3335
- process.stderr.write(`[remote] ${err.message}\n`);
3335
+ process.stderr.write(`[h2a] ${err.message}\n`);
3336
3336
  process.exitCode = 1;
3337
3337
  return;
3338
3338
  }
@@ -3345,7 +3345,7 @@ export async function main(argv) {
3345
3345
  : undefined;
3346
3346
  const depthBudget = inheritedDepthBudget(requestedDepth, process.env);
3347
3347
  if (!canDelegateAtDepth(depthBudget)) {
3348
- process.stderr.write("[remote] spawn-depth budget exhausted (REMOTE_DELEGATE_DEPTH=0) — this job has reached its --max-depth and may not delegate further.\n");
3348
+ process.stderr.write("[h2a] spawn-depth budget exhausted (REMOTE_DELEGATE_DEPTH=0) — this job has reached its --max-depth and may not delegate further.\n");
3349
3349
  process.exitCode = 1;
3350
3350
  return;
3351
3351
  }
@@ -3370,13 +3370,13 @@ export async function main(argv) {
3370
3370
  remoteTarget = getConfiguredRemote(typeof opts.remote === "string" ? opts.remote : undefined);
3371
3371
  }
3372
3372
  catch (err) {
3373
- process.stderr.write(`[remote] ${err.message}\n`);
3373
+ process.stderr.write(`[h2a] ${err.message}\n`);
3374
3374
  process.exitCode = 1;
3375
3375
  return;
3376
3376
  }
3377
3377
  }
3378
3378
  else if (!tmuxAvailable()) {
3379
- process.stderr.write("[remote] tmux is not installed locally — local `remote delegate` needs it (use --remote for a Pod).\n");
3379
+ process.stderr.write("[h2a] tmux is not installed locally — local `h2a delegate` needs it (use --remote for a Pod).\n");
3380
3380
  process.exitCode = 1;
3381
3381
  return;
3382
3382
  }
@@ -3444,7 +3444,7 @@ export async function main(argv) {
3444
3444
  }
3445
3445
  if (!claimed && listJobs().some((e) => e.id === jobId)) {
3446
3446
  // The entry exists but the cap is full → stay queued.
3447
- process.stderr.write(`[remote] queued job ${jobId} (${jobType}${opts.headless ? " headless" : ""}${isRemote ? " remote" : ""}) — ` +
3447
+ process.stderr.write(`[h2a] queued job ${jobId} (${jobType}${opts.headless ? " headless" : ""}${isRemote ? " remote" : ""}) — ` +
3448
3448
  `${effectiveCap} concurrent slot(s) busy. Start it with: remote jobs conduct\n`);
3449
3449
  process.stdout.write(`${jobId}\n`);
3450
3450
  return;
@@ -3452,7 +3452,7 @@ export async function main(argv) {
3452
3452
  if (!claimed) {
3453
3453
  // Registry write failed entirely (no entry at all) — fall back to a
3454
3454
  // throwaway in-memory entry so the launch still happens.
3455
- process.stderr.write(`[remote] job ${jobId} not in registry after enroll — launching from an in-memory spec\n`);
3455
+ process.stderr.write(`[h2a] job ${jobId} not in registry after enroll — launching from an in-memory spec\n`);
3456
3456
  }
3457
3457
  const launchEntry = claimed ??
3458
3458
  {
@@ -3484,12 +3484,12 @@ export async function main(argv) {
3484
3484
  // only reclaim it on a later reconcile pass).
3485
3485
  if (claimed)
3486
3486
  advanceJob(jobId, "failed");
3487
- process.stderr.write(`[remote] failed to start job ${jobId}: ${result.error}\n`);
3487
+ process.stderr.write(`[h2a] failed to start job ${jobId}: ${result.error}\n`);
3488
3488
  process.exitCode = 1;
3489
3489
  return;
3490
3490
  }
3491
- process.stderr.write(`[remote] delegated ${result.target === "remote" ? "REMOTE " : ""}job ${jobId} (${jobType}${opts.headless ? " headless" : ""}) ${result.target === "remote" ? "→ " : "in "}${result.detail}\n` +
3492
- `[remote] supervise: remote jobs status ${jobId}` +
3491
+ process.stderr.write(`[h2a] delegated ${result.target === "remote" ? "REMOTE " : ""}job ${jobId} (${jobType}${opts.headless ? " headless" : ""}) ${result.target === "remote" ? "→ " : "in "}${result.detail}\n` +
3492
+ `[h2a] supervise: remote jobs status ${jobId}` +
3493
3493
  (opts.headless ? "" : ` attach: remote jobs attach ${jobId}`) +
3494
3494
  "\n");
3495
3495
  process.stdout.write(`${jobId}\n`);
@@ -3517,7 +3517,7 @@ export async function main(argv) {
3517
3517
  const projection = buildAgentsProjection();
3518
3518
  const agent = findProjectedAgent(projection.agents, id);
3519
3519
  if (!agent) {
3520
- process.stderr.write(`[remote] no projected agent \"${id}\" (see: remote agents ls --json)\n`);
3520
+ process.stderr.write(`[h2a] no projected agent \"${id}\" (see: h2a agents ls --json)\n`);
3521
3521
  process.exitCode = 1;
3522
3522
  return;
3523
3523
  }
@@ -3528,7 +3528,7 @@ export async function main(argv) {
3528
3528
  // ---------------------------------------------------------------------------
3529
3529
  const jobsCommand = program
3530
3530
  .command("jobs")
3531
- .description("Supervise delegated agent jobs (see `remote delegate`).");
3531
+ .description("Supervise delegated agent jobs (see `h2a delegate`).");
3532
3532
  const jobLive = (e) => isLive(e);
3533
3533
  // S1 — resolve the `actor.instance` a job envelope MUST carry to be trusted,
3534
3534
  // off the registry: `job.done`/`decision.requested` (ABOUT the job) must come
@@ -3627,7 +3627,7 @@ export async function main(argv) {
3627
3627
  if (step.action === "fail") {
3628
3628
  const advanced = advanceJob(job.id, "failed");
3629
3629
  if (advanced) {
3630
- process.stderr.write(`[remote] job ${job.id} (${job.tool}) failed: rate-limited ` +
3630
+ process.stderr.write(`[h2a] job ${job.id} (${job.tool}) failed: rate-limited ` +
3631
3631
  `(gave up after ${job.throttle?.attempts ?? THROTTLE_MAX_ATTEMPTS} resume attempts)\n`);
3632
3632
  emitJobDone(advanced, { state: "failed" });
3633
3633
  realizeTrackMirror(advanced);
@@ -3655,7 +3655,7 @@ export async function main(argv) {
3655
3655
  return { entries, result: true };
3656
3656
  });
3657
3657
  if (ok) {
3658
- process.stderr.write(`[remote] job ${job.id} (${job.tool}) throttled (${verdict.signature ?? "rate-limited"}) — ` +
3658
+ process.stderr.write(`[h2a] job ${job.id} (${job.tool}) throttled (${verdict.signature ?? "rate-limited"}) — ` +
3659
3659
  `attempt ${step.attempts}/${THROTTLE_MAX_ATTEMPTS}, resume at ${step.nextRetryAt}\n`);
3660
3660
  }
3661
3661
  return ok;
@@ -3805,7 +3805,7 @@ export async function main(argv) {
3805
3805
  await reconcileJobs();
3806
3806
  const job = listJobs().find((e) => e.id === id);
3807
3807
  if (!job) {
3808
- process.stderr.write(`[remote] no job "${id}" (see: remote jobs ls)\n`);
3808
+ process.stderr.write(`[h2a] no job "${id}" (see: remote jobs ls)\n`);
3809
3809
  process.exitCode = 1;
3810
3810
  return;
3811
3811
  }
@@ -3854,7 +3854,7 @@ export async function main(argv) {
3854
3854
  });
3855
3855
  jobsCommand
3856
3856
  .command("attach <id>")
3857
- .description("Attach into the job's tmux session (Ctrl-b d to detach). Remote jobs exec into the Pod's tmux, like `remote attach <id> --exec`.")
3857
+ .description("Attach into the job's tmux session (Ctrl-b d to detach). Remote jobs exec into the Pod's tmux, like `h2a attach <id> --exec`.")
3858
3858
  .action(async (id) => {
3859
3859
  const job = listJobs().find((e) => e.id === id);
3860
3860
  // REMOTE job (P2): exec into the Pod's tmux over the configured tunnel,
@@ -3862,13 +3862,13 @@ export async function main(argv) {
3862
3862
  if (job?.kind === "remote") {
3863
3863
  const remoteId = job.remoteId;
3864
3864
  if (!remoteId) {
3865
- process.stderr.write(`[remote] remote job "${id}" has no session id recorded (see: remote jobs status ${id})\n`);
3865
+ process.stderr.write(`[h2a] remote job "${id}" has no session id recorded (see: remote jobs status ${id})\n`);
3866
3866
  process.exitCode = 1;
3867
3867
  return;
3868
3868
  }
3869
3869
  const tunnel = getTunnel();
3870
3870
  if (!tunnel) {
3871
- process.stderr.write("[remote] attaching a remote job needs a tunnel configured (remote config tunnel …)\n");
3871
+ process.stderr.write("[h2a] attaching a remote job needs a tunnel configured (h2a config tunnel …)\n");
3872
3872
  process.exitCode = 1;
3873
3873
  return;
3874
3874
  }
@@ -3878,14 +3878,14 @@ export async function main(argv) {
3878
3878
  catch {
3879
3879
  // best-effort: the kubectl exec below works off the tunnel regardless
3880
3880
  }
3881
- process.stderr.write(`[remote] exec-attaching into Pod tmux for job ${id} (${remoteId}) (Ctrl-b d to detach)\n`);
3881
+ process.stderr.write(`[h2a] exec-attaching into Pod tmux for job ${id} (${remoteId}) (Ctrl-b d to detach)\n`);
3882
3882
  process.exitCode = attachPodTmux(tunnel, remoteId);
3883
3883
  return;
3884
3884
  }
3885
3885
  // LOCAL job (P1): attach into the detached tmux session.
3886
3886
  const name = job?.tmuxSession ?? localSessionName(id);
3887
3887
  if (!findLocalSession(name)) {
3888
- process.stderr.write(`[remote] no live tmux session for job "${id}" (it may have ended; see: remote jobs status ${id})\n`);
3888
+ process.stderr.write(`[h2a] no live tmux session for job "${id}" (it may have ended; see: remote jobs status ${id})\n`);
3889
3889
  process.exitCode = 1;
3890
3890
  return;
3891
3891
  }
@@ -3897,7 +3897,7 @@ export async function main(argv) {
3897
3897
  .action((id) => {
3898
3898
  const job = listJobs().find((e) => e.id === id);
3899
3899
  if (!job) {
3900
- process.stderr.write(`[remote] no job "${id}" (see: remote jobs ls)\n`);
3900
+ process.stderr.write(`[h2a] no job "${id}" (see: remote jobs ls)\n`);
3901
3901
  process.exitCode = 1;
3902
3902
  return;
3903
3903
  }
@@ -3909,7 +3909,7 @@ export async function main(argv) {
3909
3909
  }
3910
3910
  // REMOTE job: the output lives in the Pod's tmux, not a local pane.
3911
3911
  if (job.kind === "remote") {
3912
- process.stderr.write(`[remote] remote job "${id}" runs in a Pod — view it live with: remote jobs attach ${id}\n`);
3912
+ process.stderr.write(`[h2a] remote job "${id}" runs in a Pod — view it live with: remote jobs attach ${id}\n`);
3913
3913
  process.exitCode = 1;
3914
3914
  return;
3915
3915
  }
@@ -3919,7 +3919,7 @@ export async function main(argv) {
3919
3919
  process.stdout.write(pane);
3920
3920
  return;
3921
3921
  }
3922
- process.stderr.write(`[remote] no logs for job "${id}" (no output.log and the tmux pane is gone)\n`);
3922
+ process.stderr.write(`[h2a] no logs for job "${id}" (no output.log and the tmux pane is gone)\n`);
3923
3923
  process.exitCode = 1;
3924
3924
  });
3925
3925
  // P3 — the decision channel (h2a). A delegated INTERACTIVE job can emit a
@@ -3937,7 +3937,7 @@ export async function main(argv) {
3937
3937
  envelopes = authenticateJobEnvelopes(readInboxEnvelopes(), expectedInstanceOf);
3938
3938
  }
3939
3939
  catch (error) {
3940
- process.stderr.write(`[remote] cannot read the h2a inbox: ${(error instanceof Error ? error.message : String(error)).slice(0, 160)}\n`);
3940
+ process.stderr.write(`[h2a] cannot read the h2a inbox: ${(error instanceof Error ? error.message : String(error)).slice(0, 160)}\n`);
3941
3941
  process.exitCode = 1;
3942
3942
  return;
3943
3943
  }
@@ -3954,13 +3954,13 @@ export async function main(argv) {
3954
3954
  assertSafeName(jobId);
3955
3955
  }
3956
3956
  catch (err) {
3957
- process.stderr.write(`[remote] ${err.message}\n`);
3957
+ process.stderr.write(`[h2a] ${err.message}\n`);
3958
3958
  process.exitCode = 1;
3959
3959
  return;
3960
3960
  }
3961
3961
  const job = listJobs().find((e) => e.id === jobId);
3962
3962
  if (!job) {
3963
- process.stderr.write(`[remote] no job "${jobId}" (see: remote jobs ls)\n`);
3963
+ process.stderr.write(`[h2a] no job "${jobId}" (see: remote jobs ls)\n`);
3964
3964
  process.exitCode = 1;
3965
3965
  return;
3966
3966
  }
@@ -3972,13 +3972,13 @@ export async function main(argv) {
3972
3972
  });
3973
3973
  try {
3974
3974
  const { path, written } = dropEnvelope(envelope, envelopeFileName("decision.reply", jobId, Date.now()));
3975
- process.stderr.write(`[remote] decision.reply for ${jobId} → ${envelope.to}` +
3975
+ process.stderr.write(`[h2a] decision.reply for ${jobId} → ${envelope.to}` +
3976
3976
  `${written ? "" : " (already present, not overwritten)"}\n` +
3977
- `[remote] envelope: ${path}\n` +
3978
- `[remote] delivered to the job's Pod on the next: remote h2a bridge\n`);
3977
+ `[h2a] envelope: ${path}\n` +
3978
+ `[h2a] delivered to the job's Pod on the next: remote h2a bridge\n`);
3979
3979
  }
3980
3980
  catch (error) {
3981
- process.stderr.write(`[remote] failed to write decision.reply: ${(error instanceof Error ? error.message : String(error)).slice(0, 160)}\n`);
3981
+ process.stderr.write(`[h2a] failed to write decision.reply: ${(error instanceof Error ? error.message : String(error)).slice(0, 160)}\n`);
3982
3982
  process.exitCode = 1;
3983
3983
  }
3984
3984
  });
@@ -4033,10 +4033,10 @@ export async function main(argv) {
4033
4033
  const r = resumeThrottledJob(job);
4034
4034
  if (r.started) {
4035
4035
  resumed += 1;
4036
- process.stderr.write(`[remote] conduct: resumed throttled ${job.id} (${job.tool}) in ${r.detail}\n`);
4036
+ process.stderr.write(`[h2a] conduct: resumed throttled ${job.id} (${job.tool}) in ${r.detail}\n`);
4037
4037
  }
4038
4038
  else {
4039
- process.stderr.write(`[remote] conduct: could not resume throttled ${job.id}: ${r.error}\n`);
4039
+ process.stderr.write(`[h2a] conduct: could not resume throttled ${job.id}: ${r.error}\n`);
4040
4040
  }
4041
4041
  }
4042
4042
  // AIMD effective cap: a provider rate-limit is account-wide, so admit FEWER
@@ -4046,7 +4046,7 @@ export async function main(argv) {
4046
4046
  const effectiveCap = aimdEffectiveCap(cap, recentThrottleEvents(refreshed), nowMs, aimdLastCap);
4047
4047
  aimdLastCap = effectiveCap;
4048
4048
  if (effectiveCap < cap) {
4049
- process.stderr.write(`[remote] conduct: AIMD breaker — admitting up to ${effectiveCap}/${cap} (rate-limit pressure)\n`);
4049
+ process.stderr.write(`[h2a] conduct: AIMD breaker — admitting up to ${effectiveCap}/${cap} (rate-limit pressure)\n`);
4050
4050
  }
4051
4051
  // (b) start pending jobs under the EFFECTIVE cap (oldest-first FIFO).
4052
4052
  let started = 0;
@@ -4057,13 +4057,13 @@ export async function main(argv) {
4057
4057
  const result = await startJob(job);
4058
4058
  if (result.started) {
4059
4059
  started += 1;
4060
- process.stderr.write(`[remote] conduct: started ${id} (${job.tool}) ${result.target === "remote" ? "→ " : "in "}${result.detail}\n`);
4060
+ process.stderr.write(`[h2a] conduct: started ${id} (${job.tool}) ${result.target === "remote" ? "→ " : "in "}${result.detail}\n`);
4061
4061
  }
4062
4062
  else {
4063
4063
  // A launch failure fails the job (frees nothing it didn't hold) so the
4064
4064
  // queue keeps moving; the error is recorded on stderr.
4065
4065
  advanceJob(id, "failed");
4066
- process.stderr.write(`[remote] conduct: failed to start ${id}: ${result.error}\n`);
4066
+ process.stderr.write(`[h2a] conduct: failed to start ${id}: ${result.error}\n`);
4067
4067
  }
4068
4068
  }
4069
4069
  // Count a resume as "started" work for the pass recap.
@@ -4071,7 +4071,7 @@ export async function main(argv) {
4071
4071
  };
4072
4072
  jobsCommand
4073
4073
  .command("conduct")
4074
- .description("Conductor: reconcile job state, consume finished jobs, and START queued (pending) jobs while running < cap — the SAME launch path as `remote delegate`. One pass by default; `--watch <min>` loops in the FOREGROUND (run in a dedicated tmux window, no daemon; Ctrl-C to stop).")
4074
+ .description("Conductor: reconcile job state, consume finished jobs, and START queued (pending) jobs while running < cap — the SAME launch path as `h2a delegate`. One pass by default; `--watch <min>` loops in the FOREGROUND (run in a dedicated tmux window, no daemon; Ctrl-C to stop).")
4075
4075
  .option("--watch <minutes>", "loop in the foreground every <minutes> (whole number >= 1); without it, runs a single pass and exits")
4076
4076
  .option("--max-concurrent <n>", "concurrency cap (default: config maxConcurrent / REMOTE_MAX_CONCURRENT / 16)")
4077
4077
  .option("--max-depth <d>", "spawn-depth budget recorded for jobs this conductor starts (clamp 1–3, default: each job's recorded budget)")
@@ -4091,10 +4091,10 @@ export async function main(argv) {
4091
4091
  const minutes = opts.watch === undefined ? undefined : parseWatchMinutes(opts.watch);
4092
4092
  if (minutes === undefined) {
4093
4093
  const { started, finished } = await conductPass(cap);
4094
- process.stderr.write(`[remote] conduct: cap ${cap} — ${started} started, ${finished} finished\n`);
4094
+ process.stderr.write(`[h2a] conduct: cap ${cap} — ${started} started, ${finished} finished\n`);
4095
4095
  return;
4096
4096
  }
4097
- process.stderr.write(`[remote] conducting (cap ${cap}, every ${minutes} min) — Ctrl-C to stop\n`);
4097
+ process.stderr.write(`[h2a] conducting (cap ${cap}, every ${minutes} min) — Ctrl-C to stop\n`);
4098
4098
  process.exitCode = await conductLoop(minutes, () => conductPass(cap));
4099
4099
  });
4100
4100
  // ---------------------------------------------------------------------------
@@ -4184,7 +4184,7 @@ export async function main(argv) {
4184
4184
  // proceed — the envelope's workspaceId is authoritative for the launch.
4185
4185
  const localId = localDurableWorkspaceId();
4186
4186
  if (localId && localId !== request.workspaceId) {
4187
- process.stderr.write(`[remote] conductor-launch: workspace id mismatch — envelope ${request.workspaceId} vs local ${localId} ` +
4187
+ process.stderr.write(`[h2a] conductor-launch: workspace id mismatch — envelope ${request.workspaceId} vs local ${localId} ` +
4188
4188
  `(proceeding with the envelope's id; align canonicalization with a2a-cli)\n`);
4189
4189
  }
4190
4190
  const liveConductors = countLiveConductors(request.workspaceId, slug);
@@ -4199,7 +4199,7 @@ export async function main(argv) {
4199
4199
  if (!gate.launch) {
4200
4200
  if (confirm)
4201
4201
  markLaunchEnvelopeProcessed(env.path, `skip: ${gate.reason}`);
4202
- process.stderr.write(`[remote] conductor-launch: SKIP — ${gate.reason}\n`);
4202
+ process.stderr.write(`[h2a] conductor-launch: SKIP — ${gate.reason}\n`);
4203
4203
  return { launched: false, detail: gate.reason };
4204
4204
  }
4205
4205
  const available = detectAvailableHosts();
@@ -4208,12 +4208,12 @@ export async function main(argv) {
4208
4208
  const detail = `no preferred host available on PATH (wanted ${request.hostPref.join("/")}, found ${[...available].join("/") || "none"})`;
4209
4209
  if (confirm)
4210
4210
  markLaunchEnvelopeProcessed(env.path, `skip: ${detail}`);
4211
- process.stderr.write(`[remote] conductor-launch: SKIP — ${detail}\n`);
4211
+ process.stderr.write(`[h2a] conductor-launch: SKIP — ${detail}\n`);
4212
4212
  return { launched: false, detail };
4213
4213
  }
4214
4214
  const task = buildConductorTask(request);
4215
4215
  if (!confirm) {
4216
- process.stderr.write(`[remote] conductor-launch DRY-RUN — would launch a ${host} conductor "${slug}" ` +
4216
+ process.stderr.write(`[h2a] conductor-launch DRY-RUN — would launch a ${host} conductor "${slug}" ` +
4217
4217
  `for ${request.workspaceId} (${request.stalled.length} stalled item(s)). ` +
4218
4218
  `Re-run with --confirm to launch.\n`);
4219
4219
  return { launched: false, detail: `dry-run (${host})` };
@@ -4258,18 +4258,18 @@ export async function main(argv) {
4258
4258
  : `launch-failed: ${result.error}`);
4259
4259
  if (!result.started) {
4260
4260
  advanceJob(slug, "failed");
4261
- process.stderr.write(`[remote] conductor-launch: FAILED to launch ${slug}: ${result.error}\n`);
4261
+ process.stderr.write(`[h2a] conductor-launch: FAILED to launch ${slug}: ${result.error}\n`);
4262
4262
  return { launched: false, detail: result.error };
4263
4263
  }
4264
- process.stderr.write(`[remote] conductor-launch: launched ${host} conductor ${slug} for ${request.workspaceId} in ${result.detail}\n` +
4265
- `[remote] supervise: remote jobs status ${slug} attach: remote jobs attach ${slug}\n`);
4264
+ process.stderr.write(`[h2a] conductor-launch: launched ${host} conductor ${slug} for ${request.workspaceId} in ${result.detail}\n` +
4265
+ `[h2a] supervise: remote jobs status ${slug} attach: remote jobs attach ${slug}\n`);
4266
4266
  return { launched: true, detail: `${host} ${slug}` };
4267
4267
  };
4268
4268
  /** One pass: handle the freshest unprocessed launch envelope (or report none). */
4269
4269
  const launchPass = async (confirm, cooldownMs) => {
4270
4270
  const fresh = freshestLaunchEnvelope(readLaunchEnvelopes());
4271
4271
  if (!fresh) {
4272
- process.stderr.write("[remote] conductor-launch: no unprocessed conductor-launch-request in the h2a inbox\n");
4272
+ process.stderr.write("[h2a] conductor-launch: no unprocessed conductor-launch-request in the h2a inbox\n");
4273
4273
  return { started: 0, finished: 0 };
4274
4274
  }
4275
4275
  const r = await processLaunchRequest(fresh, confirm, cooldownMs);
@@ -4277,7 +4277,7 @@ export async function main(argv) {
4277
4277
  };
4278
4278
  program
4279
4279
  .command("conductor-launch")
4280
- .description("Handle an h2a `conductor-launch-request` envelope: when h2a reports stalled work and no live conductor, launch one (claude/codex/agy, first available on PATH) via the same delegation path as `remote delegate`, instructed to claim the conductor role at boot. DRY-RUN unless --confirm. Idempotent (skips when a conductor is already alive) and rate-limited per workspace (cooldown). One pass by default; --watch loops in the FOREGROUND (no daemon; Ctrl-C to stop).")
4280
+ .description("Handle an h2a `conductor-launch-request` envelope: when h2a reports stalled work and no live conductor, launch one (claude/codex/agy, first available on PATH) via the same delegation path as `h2a delegate`, instructed to claim the conductor role at boot. DRY-RUN unless --confirm. Idempotent (skips when a conductor is already alive) and rate-limited per workspace (cooldown). One pass by default; --watch loops in the FOREGROUND (no daemon; Ctrl-C to stop).")
4281
4281
  .option("--confirm", "actually launch (default: dry-run — decide and print what WOULD launch, launch nothing)")
4282
4282
  .option("--watch <minutes>", "loop in the foreground every <minutes> (whole number >= 1); without it, runs a single pass and exits")
4283
4283
  .option("--cooldown <min>", "minimum minutes between launches for the SAME workspace (default 30)")
@@ -4290,13 +4290,13 @@ export async function main(argv) {
4290
4290
  const cooldownMs = cooldownMin * 60_000;
4291
4291
  const minutes = opts.watch === undefined ? undefined : parseWatchMinutes(opts.watch);
4292
4292
  if (!confirm) {
4293
- process.stderr.write("[remote] conductor-launch: DRY-RUN (no --confirm) — nothing will be launched\n");
4293
+ process.stderr.write("[h2a] conductor-launch: DRY-RUN (no --confirm) — nothing will be launched\n");
4294
4294
  }
4295
4295
  if (minutes === undefined) {
4296
4296
  await launchPass(confirm, cooldownMs);
4297
4297
  return;
4298
4298
  }
4299
- process.stderr.write(`[remote] conductor-launch watching (every ${minutes} min, cooldown ${cooldownMin} min${confirm ? "" : ", DRY-RUN"}) — Ctrl-C to stop\n`);
4299
+ process.stderr.write(`[h2a] conductor-launch watching (every ${minutes} min, cooldown ${cooldownMin} min${confirm ? "" : ", DRY-RUN"}) — Ctrl-C to stop\n`);
4300
4300
  process.exitCode = await conductLoop(minutes, () => launchPass(confirm, cooldownMs));
4301
4301
  });
4302
4302
  // ---------------------------------------------------------------------------
@@ -4417,12 +4417,12 @@ export async function main(argv) {
4417
4417
  const lastWake = readWakeStampMs(localRoot, target);
4418
4418
  if (lastWake !== undefined &&
4419
4419
  now - lastWake < WAKE_REQUEST_IDEMPOTENCE_MS) {
4420
- process.stderr.write(`[remote] wake-request: skipping ${target} (already woken ${Math.round((now - lastWake) / 1000)}s ago)\n`);
4420
+ process.stderr.write(`[h2a] wake-request: skipping ${target} (already woken ${Math.round((now - lastWake) / 1000)}s ago)\n`);
4421
4421
  continue;
4422
4422
  }
4423
4423
  const pane = resolveAgentPaneForInstance(target);
4424
4424
  if (!pane) {
4425
- process.stderr.write(`[remote] wake-request: no agent pane for ${target} — not launched by remote, skipping (reason: ${reason || "none"})\n`);
4425
+ process.stderr.write(`[h2a] wake-request: no agent pane for ${target} — not launched by remote, skipping (reason: ${reason || "none"})\n`);
4426
4426
  continue;
4427
4427
  }
4428
4428
  // Guard (h2a 0.74.0 parity): defer send-keys if a human was active in the
@@ -4441,7 +4441,7 @@ export async function main(argv) {
4441
4441
  .map((s) => Number.parseInt(s.trim(), 10) * 1000)
4442
4442
  .filter((n) => !Number.isNaN(n)));
4443
4443
  if (maxActivity > now - 4000) {
4444
- process.stderr.write(`[remote] wake-request: deferring ${target} — human active ${Math.round((now - maxActivity) / 1000)}s ago, retrying next pass\n`);
4444
+ process.stderr.write(`[h2a] wake-request: deferring ${target} — human active ${Math.round((now - maxActivity) / 1000)}s ago, retrying next pass\n`);
4445
4445
  continue;
4446
4446
  }
4447
4447
  }
@@ -4466,7 +4466,7 @@ export async function main(argv) {
4466
4466
  markLaunchEnvelopeProcessed(envelopePath, `woke ${target}`);
4467
4467
  writeWakeStampMs(localRoot, target, now);
4468
4468
  woken += 1;
4469
- process.stderr.write(`[remote] wake-request: woke ${target} → pane ${pane} (reason: ${reason || "none"})\n`);
4469
+ process.stderr.write(`[h2a] wake-request: woke ${target} → pane ${pane} (reason: ${reason || "none"})\n`);
4470
4470
  }
4471
4471
  return woken;
4472
4472
  };
@@ -4482,7 +4482,7 @@ export async function main(argv) {
4482
4482
  .option("--interval <seconds>", "poll interval in seconds (default: 30)", "30")
4483
4483
  .action(async (opts) => {
4484
4484
  if (!tmuxAvailable()) {
4485
- process.stderr.write("[remote] tmux is not installed locally\n");
4485
+ process.stderr.write("[h2a] tmux is not installed locally\n");
4486
4486
  process.exitCode = 1;
4487
4487
  return;
4488
4488
  }
@@ -4490,10 +4490,10 @@ export async function main(argv) {
4490
4490
  const intervalMs = Math.max(1, Number.parseInt(opts.interval ?? "30", 10)) * 1000;
4491
4491
  if (!opts.watch) {
4492
4492
  const woken = wakeRequestPass(localRoot);
4493
- process.stderr.write(`[remote] wake-request: ${woken} pane(s) woken this pass\n`);
4493
+ process.stderr.write(`[h2a] wake-request: ${woken} pane(s) woken this pass\n`);
4494
4494
  return;
4495
4495
  }
4496
- process.stderr.write(`[remote] wake-request watching (every ${Math.round(intervalMs / 1000)}s) — Ctrl-C to stop\n`);
4496
+ process.stderr.write(`[h2a] wake-request watching (every ${Math.round(intervalMs / 1000)}s) — Ctrl-C to stop\n`);
4497
4497
  let stopped = false;
4498
4498
  const onSigint = () => {
4499
4499
  stopped = true;
@@ -4502,7 +4502,7 @@ export async function main(argv) {
4502
4502
  try {
4503
4503
  while (!stopped) {
4504
4504
  const woken = wakeRequestPass(localRoot);
4505
- process.stderr.write(`[remote] wake-request: ${woken} pane(s) woken — ${new Date().toISOString()}\n`);
4505
+ process.stderr.write(`[h2a] wake-request: ${woken} pane(s) woken — ${new Date().toISOString()}\n`);
4506
4506
  if (stopped)
4507
4507
  break;
4508
4508
  await new Promise((resolve) => {
@@ -4517,7 +4517,7 @@ export async function main(argv) {
4517
4517
  finally {
4518
4518
  process.removeListener("SIGINT", onSigint);
4519
4519
  }
4520
- process.stderr.write("[remote] wake-request watch stopped\n");
4520
+ process.stderr.write("[h2a] wake-request watch stopped\n");
4521
4521
  });
4522
4522
  // ---------------------------------------------------------------------------
4523
4523
  // relaunch — bring idle local sessions back in situ, each resuming its OWN conv
@@ -4528,7 +4528,7 @@ export async function main(argv) {
4528
4528
  .option("--apply", "actually relaunch (default: dry-run, just print the plan)")
4529
4529
  .action((filter, opts) => {
4530
4530
  if (!tmuxAvailable()) {
4531
- process.stderr.write("[remote] tmux is not installed locally\n");
4531
+ process.stderr.write("[h2a] tmux is not installed locally\n");
4532
4532
  process.exitCode = 1;
4533
4533
  return;
4534
4534
  }
@@ -4549,14 +4549,14 @@ export async function main(argv) {
4549
4549
  : {}),
4550
4550
  })));
4551
4551
  if (plan.actions.length === 0) {
4552
- process.stderr.write(`[remote] nothing to relaunch${filter ? ` matching "${filter}"` : ""} (${plan.skipped.length} skipped)\n`);
4552
+ process.stderr.write(`[h2a] nothing to relaunch${filter ? ` matching "${filter}"` : ""} (${plan.skipped.length} skipped)\n`);
4553
4553
  for (const s of plan.skipped) {
4554
4554
  process.stderr.write(` - ${s.slug}: ${s.reason}\n`);
4555
4555
  }
4556
4556
  return;
4557
4557
  }
4558
4558
  if (!opts.apply) {
4559
- process.stderr.write(`[remote] would relaunch ${plan.actions.length} session(s) — dry-run, pass --apply:\n`);
4559
+ process.stderr.write(`[h2a] would relaunch ${plan.actions.length} session(s) — dry-run, pass --apply:\n`);
4560
4560
  for (const a of plan.actions) {
4561
4561
  process.stderr.write(` ${a.slug}: ${a.cmd}\n`);
4562
4562
  }
@@ -4569,13 +4569,13 @@ export async function main(argv) {
4569
4569
  for (const a of plan.actions) {
4570
4570
  if (relaunchInSession(a.name, a.cmd)) {
4571
4571
  ok += 1;
4572
- process.stderr.write(`[remote] relaunched ${a.slug}: ${a.cmd}\n`);
4572
+ process.stderr.write(`[h2a] relaunched ${a.slug}: ${a.cmd}\n`);
4573
4573
  }
4574
4574
  else {
4575
- process.stderr.write(`[remote] FAILED to relaunch ${a.slug}\n`);
4575
+ process.stderr.write(`[h2a] FAILED to relaunch ${a.slug}\n`);
4576
4576
  }
4577
4577
  }
4578
- process.stderr.write(`[remote] relaunched ${ok}/${plan.actions.length}${plan.skipped.length ? `, ${plan.skipped.length} skipped` : ""}\n`);
4578
+ process.stderr.write(`[h2a] relaunched ${ok}/${plan.actions.length}${plan.skipped.length ? `, ${plan.skipped.length} skipped` : ""}\n`);
4579
4579
  });
4580
4580
  // ---------------------------------------------------------------------------
4581
4581
  // Reliability slice 2 (throttle phase 2) — auto-resume RATE-LIMITED INTERACTIVE
@@ -4675,17 +4675,17 @@ export async function main(argv) {
4675
4675
  // already excluded attached panes, but re-read in case a human just
4676
4676
  // attached between capture and nudge — belt and braces on a live pane).
4677
4677
  if (!isDetachedNow(r.name)) {
4678
- process.stderr.write(`[remote] ${r.name} (${r.type}) just got attached — skipping the nudge\n`);
4678
+ process.stderr.write(`[h2a] ${r.name} (${r.type}) just got attached — skipping the nudge\n`);
4679
4679
  continue;
4680
4680
  }
4681
4681
  const ok = relaunchInSession(r.name, interactiveResumeNudge(r.type));
4682
4682
  if (ok) {
4683
4683
  nudged += 1;
4684
4684
  throttleState.set(r.name, r.next);
4685
- process.stderr.write(`[remote] nudged ${r.name} (${r.type}) — continue (attempt ${r.next.attempts})\n`);
4685
+ process.stderr.write(`[h2a] nudged ${r.name} (${r.type}) — continue (attempt ${r.next.attempts})\n`);
4686
4686
  }
4687
4687
  else {
4688
- process.stderr.write(`[remote] FAILED to nudge ${r.name}\n`);
4688
+ process.stderr.write(`[h2a] FAILED to nudge ${r.name}\n`);
4689
4689
  }
4690
4690
  }
4691
4691
  }
@@ -4699,7 +4699,7 @@ export async function main(argv) {
4699
4699
  .option("--max-concurrent <n>", "AIMD cap: max sessions nudged per pass (default: config maxConcurrent / REMOTE_MAX_CONCURRENT / 16)")
4700
4700
  .action(async (filter, opts) => {
4701
4701
  if (!tmuxAvailable()) {
4702
- process.stderr.write("[remote] tmux is not installed locally\n");
4702
+ process.stderr.write("[h2a] tmux is not installed locally\n");
4703
4703
  process.exitCode = 1;
4704
4704
  return;
4705
4705
  }
@@ -4719,11 +4719,11 @@ export async function main(argv) {
4719
4719
  for (const line of plan.advisories)
4720
4720
  process.stderr.write(`${line}\n`);
4721
4721
  if (!apply) {
4722
- process.stderr.write(`[remote] DRY-RUN — would resume ${plan.toResume.length} session(s) ` +
4722
+ process.stderr.write(`[h2a] DRY-RUN — would resume ${plan.toResume.length} session(s) ` +
4723
4723
  `(${plan.throttled.length} throttled, cap ${cap}). Pass --apply to nudge them.\n`);
4724
4724
  }
4725
4725
  else {
4726
- process.stderr.write(`[remote] resume-throttled: nudged ${nudged}/${plan.toResume.length} ` +
4726
+ process.stderr.write(`[h2a] resume-throttled: nudged ${nudged}/${plan.toResume.length} ` +
4727
4727
  `(${plan.throttled.length} throttled, cap ${cap})\n`);
4728
4728
  }
4729
4729
  return { failed: 0 };
@@ -4732,7 +4732,7 @@ export async function main(argv) {
4732
4732
  runOnce();
4733
4733
  return;
4734
4734
  }
4735
- process.stderr.write(`[remote] resume-throttled watching (cap ${cap}, every ${minutes} min) — Ctrl-C to stop\n`);
4735
+ process.stderr.write(`[h2a] resume-throttled watching (cap ${cap}, every ${minutes} min) — Ctrl-C to stop\n`);
4736
4736
  process.exitCode = await watchRefreshLoop(minutes, async () => runOnce());
4737
4737
  });
4738
4738
  // ---------------------------------------------------------------------------
@@ -4756,14 +4756,14 @@ export async function main(argv) {
4756
4756
  cwd: process.cwd(),
4757
4757
  ...(opts.root !== undefined ? { localRoot: opts.root } : {}),
4758
4758
  });
4759
- process.stderr.write(`[remote] h2a ping ${ping.written ? "queued" : "already queued"} for ${instance}: ${ping.path}\n`);
4759
+ process.stderr.write(`[h2a] h2a ping ${ping.written ? "queued" : "already queued"} for ${instance}: ${ping.path}\n`);
4760
4760
  if (opts.bridge === undefined)
4761
4761
  return;
4762
4762
  const sessionId = typeof opts.bridge === "string"
4763
4763
  ? opts.bridge
4764
4764
  : remoteSessionIdFromInstance(instance);
4765
4765
  if (!sessionId) {
4766
- process.stderr.write("[remote] --bridge needs a session id, or an instance shaped like <tool>:remote:<sessionId>\n");
4766
+ process.stderr.write("[h2a] --bridge needs a session id, or an instance shaped like <tool>:remote:<sessionId>\n");
4767
4767
  process.exitCode = 1;
4768
4768
  return;
4769
4769
  }
@@ -4774,13 +4774,13 @@ export async function main(argv) {
4774
4774
  const { bridgeSession } = await import("./h2a-bridge.js");
4775
4775
  try {
4776
4776
  const r = await bridgeSession(sessionId, { profile });
4777
- process.stderr.write(`[remote] h2a bridge ${sessionId} (${profile}) pulled=${r.pulled} pushed=${r.pushed} skipped=${r.skipped}` +
4777
+ process.stderr.write(`[h2a] h2a bridge ${sessionId} (${profile}) pulled=${r.pulled} pushed=${r.pushed} skipped=${r.skipped}` +
4778
4778
  `${r.failed > 0 ? ` failed=${r.failed}` : ""}\n`);
4779
4779
  if (r.failed > 0)
4780
4780
  process.exitCode = 1;
4781
4781
  }
4782
4782
  catch (error) {
4783
- process.stderr.write(`[remote] h2a bridge ${sessionId} failed: ${(error instanceof Error ? error.message : String(error)).slice(0, 200)}\n`);
4783
+ process.stderr.write(`[h2a] h2a bridge ${sessionId} failed: ${(error instanceof Error ? error.message : String(error)).slice(0, 200)}\n`);
4784
4784
  process.exitCode = 1;
4785
4785
  }
4786
4786
  });
@@ -4821,7 +4821,7 @@ export async function main(argv) {
4821
4821
  profile: s.profile,
4822
4822
  }));
4823
4823
  if (sessions.length === 0) {
4824
- process.stderr.write("[remote] no live remote sessions to bridge\n");
4824
+ process.stderr.write("[h2a] no live remote sessions to bridge\n");
4825
4825
  return { failed: 0 };
4826
4826
  }
4827
4827
  let failed = 0;
@@ -4841,13 +4841,13 @@ export async function main(argv) {
4841
4841
  const r = await bridgeSession(s.id, { profile: s.profile });
4842
4842
  if (r.failed > 0)
4843
4843
  failed += 1;
4844
- process.stderr.write(`[remote] h2a bridge ${s.id} (${s.profile}) pulled=${r.pulled} pushed=${r.pushed} skipped=${r.skipped}` +
4844
+ process.stderr.write(`[h2a] h2a bridge ${s.id} (${s.profile}) pulled=${r.pulled} pushed=${r.pushed} skipped=${r.skipped}` +
4845
4845
  `${r.failed > 0 ? ` failed=${r.failed}` : ""}` +
4846
4846
  `${r.scaffolded ? " (pod .h2a scaffolded)" : ""}\n`);
4847
4847
  }
4848
4848
  catch (error) {
4849
4849
  failed += 1;
4850
- process.stderr.write(`[remote] h2a bridge ${s.id} failed: ${(error instanceof Error ? error.message : String(error)).slice(0, 200)}\n`);
4850
+ process.stderr.write(`[h2a] h2a bridge ${s.id} failed: ${(error instanceof Error ? error.message : String(error)).slice(0, 200)}\n`);
4851
4851
  }
4852
4852
  }
4853
4853
  return { failed };
@@ -4862,7 +4862,7 @@ export async function main(argv) {
4862
4862
  });
4863
4863
  program
4864
4864
  .command("restore [group]")
4865
- .description('Relance les sessions dev dans leur layout (fenêtre par groupe, onglet par session). Sans argument: tous les groupes. Avec [group]: ce lot seulement (ex: `remote restore "full remote"`). Groupes LOCAUX = claude/codex sous ~/src/* (tmux via `remote run`); groupes REMOTE = sessions SCW (`remote attach <id> --exec`). Layout: champ `layout` de la config.')
4865
+ .description('Relance les sessions dev dans leur layout (fenêtre par groupe, onglet par session). Sans argument: tous les groupes. Avec [group]: ce lot seulement (ex: `h2a restore "full remote"`). Groupes LOCAUX = claude/codex sous ~/src/* (tmux via `h2a run`); groupes REMOTE = sessions SCW (`h2a attach <id> --exec`). Layout: champ `layout` de la config.')
4866
4866
  .option("--dry-run", "affiche le layout calculé sans ouvrir de terminaux")
4867
4867
  .option("--reattach", "ouvre aussi un onglet pour les sessions déjà actives en tmux (ré-attache)")
4868
4868
  .option("--llm-gateway", "force TOUTES les sessions sur la gateway llm-mesh (ignore le pin par instance; relance les sessions vivantes via --replace)")
@@ -4871,7 +4871,7 @@ export async function main(argv) {
4871
4871
  .option("--no-gw", "alias de --no-llm-gateway")
4872
4872
  .action(async (group, opts) => {
4873
4873
  if (!tmuxAvailable()) {
4874
- process.stderr.write("[remote] tmux requis pour restore (sudo apt install tmux)\n");
4874
+ process.stderr.write("[h2a] tmux requis pour restore (sudo apt install tmux)\n");
4875
4875
  process.exitCode = 1;
4876
4876
  return;
4877
4877
  }
@@ -4896,7 +4896,7 @@ export async function main(argv) {
4896
4896
  forceGateway = mode;
4897
4897
  }
4898
4898
  catch (error) {
4899
- process.stderr.write(`[remote] ${error.message}.\n`);
4899
+ process.stderr.write(`[h2a] ${error.message}.\n`);
4900
4900
  process.exitCode = 1;
4901
4901
  return;
4902
4902
  }
@@ -4912,7 +4912,7 @@ export async function main(argv) {
4912
4912
  return { id: s.id, label: projectName(s), cwd };
4913
4913
  });
4914
4914
  if (restoreOpts.remoteTabs.length === 0) {
4915
- process.stderr.write("[remote] aucune session SCW (remote ls vide) pour le groupe remote\n");
4915
+ process.stderr.write("[h2a] aucune session SCW (h2a ls vide) pour le groupe remote\n");
4916
4916
  }
4917
4917
  }
4918
4918
  if (opts.reattach)
@@ -4924,10 +4924,10 @@ export async function main(argv) {
4924
4924
  }
4925
4925
  const { total } = restoreLayout(restoreOpts);
4926
4926
  if (total === 0) {
4927
- process.stderr.write(`[remote] rien à relancer${group ? ` pour le groupe "${group}"` : ""}\n`);
4927
+ process.stderr.write(`[h2a] rien à relancer${group ? ` pour le groupe "${group}"` : ""}\n`);
4928
4928
  }
4929
4929
  else {
4930
- process.stderr.write(`[remote] ${total} onglet(s)${opts.dryRun ? " (dry-run, rien ouvert)" : " relancé(s)"}\n`);
4930
+ process.stderr.write(`[h2a] ${total} onglet(s)${opts.dryRun ? " (dry-run, rien ouvert)" : " relancé(s)"}\n`);
4931
4931
  }
4932
4932
  });
4933
4933
  // ---------------------------------------------------------------------------
@@ -4935,11 +4935,11 @@ export async function main(argv) {
4935
4935
  // ---------------------------------------------------------------------------
4936
4936
  program
4937
4937
  .command("enroll")
4938
- .description("Plumbing for the live-session registry (feeds `remote ls`/`remote restore`). " +
4938
+ .description("Plumbing for the live-session registry (feeds `h2a ls`/`h2a restore`). " +
4939
4939
  "Hook mode (--hook claude-start|claude-end) is wired by --install-hooks into " +
4940
4940
  "~/.claude/settings.json (idempotent; backs up settings.json.bak.<epoch>) and " +
4941
4941
  "always exits 0 so it can never break the host claude session. " +
4942
- "codex has no reliable session hook: codex sessions are enrolled by `remote run` " +
4942
+ "codex has no reliable session hook: codex sessions are enrolled by `h2a run` " +
4943
4943
  "and by the restore filesystem-scan fallback. Manual mode: --tool/--cwd/--conv/--pid/--label.")
4944
4944
  .option("--hook <name>", "hook mode: claude-start | claude-end (reads the Claude Code hook JSON on stdin)")
4945
4945
  .option("--install-hooks", "merge the SessionStart/SessionEnd enroll hooks into Claude Code's settings.json (idempotent)")
@@ -4953,13 +4953,13 @@ export async function main(argv) {
4953
4953
  if (opts.installHooks) {
4954
4954
  const result = installClaudeHooks(opts.settings);
4955
4955
  if (!result.changed) {
4956
- process.stderr.write(`[remote] enroll hooks already installed in ${result.settingsPath}\n`);
4956
+ process.stderr.write(`[h2a] enroll hooks already installed in ${result.settingsPath}\n`);
4957
4957
  return;
4958
4958
  }
4959
4959
  if (result.backupPath) {
4960
- process.stderr.write(`[remote] backup: ${result.backupPath}\n`);
4960
+ process.stderr.write(`[h2a] backup: ${result.backupPath}\n`);
4961
4961
  }
4962
- process.stderr.write(`[remote] installed ${result.installed.join(" + ")} enroll hooks in ${result.settingsPath}\n`);
4962
+ process.stderr.write(`[h2a] installed ${result.installed.join(" + ")} enroll hooks in ${result.settingsPath}\n`);
4963
4963
  return;
4964
4964
  }
4965
4965
  if (opts.hook) {
@@ -4968,11 +4968,11 @@ export async function main(argv) {
4968
4968
  const raw = await readStdin();
4969
4969
  const result = handleClaudeHook(opts.hook, raw);
4970
4970
  if (!result.ok) {
4971
- process.stderr.write(`[remote] enroll hook ignored: ${result.error}\n`);
4971
+ process.stderr.write(`[h2a] enroll hook ignored: ${result.error}\n`);
4972
4972
  }
4973
4973
  }
4974
4974
  catch (error) {
4975
- process.stderr.write(`[remote] enroll hook ignored: ${String(error)}\n`);
4975
+ process.stderr.write(`[h2a] enroll hook ignored: ${String(error)}\n`);
4976
4976
  }
4977
4977
  return;
4978
4978
  }
@@ -4986,21 +4986,21 @@ export async function main(argv) {
4986
4986
  });
4987
4987
  if (!result.ok)
4988
4988
  throw new Error(result.error ?? "enroll failed");
4989
- process.stderr.write(`[remote] enrolled (${opts.tool})\n`);
4989
+ process.stderr.write(`[h2a] enrolled (${opts.tool})\n`);
4990
4990
  return;
4991
4991
  }
4992
- process.stderr.write("[remote] enroll: pass --hook <name>, --install-hooks, or --tool <tool> (see --help)\n");
4992
+ process.stderr.write("[h2a] enroll: pass --hook <name>, --install-hooks, or --tool <tool> (see --help)\n");
4993
4993
  });
4994
4994
  const layoutCommand = program
4995
4995
  .command("layout")
4996
- .description("Layout auto-enregistré par `remote restore` (layout-last.json)");
4996
+ .description("Layout auto-enregistré par `h2a restore` (layout-last.json)");
4997
4997
  layoutCommand
4998
4998
  .command("show")
4999
4999
  .description("Affiche le dernier layout lancé (fenêtres, onglets, commandes)")
5000
5000
  .action(() => {
5001
5001
  const last = readLastLayout();
5002
5002
  if (!last) {
5003
- process.stderr.write("[remote] aucun layout enregistré (lance `remote restore` d'abord)\n");
5003
+ process.stderr.write("[h2a] aucun layout enregistré (lance `h2a restore` d'abord)\n");
5004
5004
  return;
5005
5005
  }
5006
5006
  process.stdout.write(`at: ${last.at}\n`);
@@ -5023,13 +5023,22 @@ export async function main(argv) {
5023
5023
  // Local tmux session? (unless an explicit URL/sessionId pair is given).
5024
5024
  if (second === undefined && !looksLikeUrl(first)) {
5025
5025
  const local = findLocalSession(first);
5026
- if (opts.local || local) {
5027
- if (!local) {
5028
- process.stderr.write(`[remote] no local session "${first}" (see: remote ls)\n`);
5026
+ // A session started by `remote run` is enrolled in the registry as
5027
+ // kind:"local-tmux". Trust that durable record even when a transient
5028
+ // `tmux list-sessions` miss hides the live session otherwise a purely
5029
+ // LOCAL session is mis-routed to a k8s Pod (kubectl exec session-<name>
5030
+ // → NotFound → reconnect loop) as soon as a tunnel is configured.
5031
+ // `remote ls` already merges registry + tmux; attach must not be less
5032
+ // reliable. When the registry says local but tmux can't reach it,
5033
+ // attachLocalSession fails with a clear LOCAL error (never a Pod).
5034
+ const localName = local?.name ?? localTmuxSessionForName(first, loadRegistry());
5035
+ if (opts.local || localName) {
5036
+ if (!localName) {
5037
+ process.stderr.write(`[h2a] no local session "${first}" (see: h2a ls)\n`);
5029
5038
  process.exitCode = 1;
5030
5039
  return;
5031
5040
  }
5032
- process.exitCode = attachLocalSession(local.name);
5041
+ process.exitCode = attachLocalSession(localName);
5033
5042
  return;
5034
5043
  }
5035
5044
  }
@@ -5052,15 +5061,15 @@ export async function main(argv) {
5052
5061
  // fallback. Force either path with --exec / --ws.
5053
5062
  if (!opts.ws && (opts.exec || tunnel)) {
5054
5063
  if (!tunnel) {
5055
- process.stderr.write("[remote] --exec needs a tunnel configured (remote config tunnel …)\n");
5064
+ process.stderr.write("[h2a] --exec needs a tunnel configured (h2a config tunnel …)\n");
5056
5065
  process.exitCode = 1;
5057
5066
  return;
5058
5067
  }
5059
- process.stderr.write(`[remote] exec-attaching into Pod tmux for ${sessionId} (Ctrl-b d to detach)\n`);
5068
+ process.stderr.write(`[h2a] exec-attaching into Pod tmux for ${sessionId} (Ctrl-b d to detach)\n`);
5060
5069
  process.exitCode = attachPodTmux(tunnel, sessionId);
5061
5070
  return;
5062
5071
  }
5063
- process.stderr.write(`[remote] attaching to ${url}/sessions/${sessionId} (WS)\n`);
5072
+ process.stderr.write(`[h2a] attaching to ${url}/sessions/${sessionId} (WS)\n`);
5064
5073
  const session = await attach({ baseUrl: url, sessionId });
5065
5074
  await session.finished;
5066
5075
  });
@@ -5068,7 +5077,7 @@ export async function main(argv) {
5068
5077
  .command("refresh [urlOrSessionId] [sessionId]")
5069
5078
  .description("Re-bundle the local CLI credentials and push them to an existing remote session's Secret. The session's Pod is restarted by the control-plane so the new tokens take effect. Profile is auto-detected from the session.")
5070
5079
  .option("--profile <profile>", "override the auto-detected profile (rarely needed)")
5071
- .option("--name <name>", "set the session's display name (shown as PROJECT in `remote ls`); applied on the hard refresh (Pod recreate), ignored with --soft")
5080
+ .option("--name <name>", "set the session's display name (shown as PROJECT in `h2a ls`); applied on the hard refresh (Pod recreate), ignored with --soft")
5072
5081
  .option("--no-auth", "skip bundling local credentials")
5073
5082
  .option("--no-auth-refresh", "skip local auth status preflight before bundling credentials")
5074
5083
  .option("--soft", "push fresh creds INTO the running Pod + relaunch the CLI in place (no Pod recreate; keeps HOME + conversation; fixes the ~8h token logout)")
@@ -5077,12 +5086,12 @@ export async function main(argv) {
5077
5086
  .addHelpText("after", [
5078
5087
  "",
5079
5088
  "Examples:",
5080
- " $ remote refresh sess-abc --soft refresh one session's creds in place",
5081
- " $ remote refresh --soft --all refresh every live session once",
5082
- " $ remote refresh --all --watch 30 foreground loop: every 30 min, all sessions;",
5089
+ " $ h2a refresh sess-abc --soft refresh one session's creds in place",
5090
+ " $ h2a refresh --soft --all refresh every live session once",
5091
+ " $ h2a refresh --all --watch 30 foreground loop: every 30 min, all sessions;",
5083
5092
  " unchanged creds = no-op (no CLI respawn)",
5084
5093
  " Run the watch in a dedicated tmux window, e.g.:",
5085
- " tmux new-window -n creds 'remote refresh --all --watch 30'",
5094
+ " tmux new-window -n creds 'h2a refresh --all --watch 30'",
5086
5095
  "",
5087
5096
  ].join("\n"))
5088
5097
  .action(async (first, second, opts) => {
@@ -5116,7 +5125,7 @@ export async function main(argv) {
5116
5125
  return;
5117
5126
  }
5118
5127
  if (first === undefined) {
5119
- throw new Error("Missing session id. Usage: remote refresh [url] <sessionId> [--soft] (or --soft --all).");
5128
+ throw new Error("Missing session id. Usage: h2a refresh [url] <sessionId> [--soft] (or --soft --all).");
5120
5129
  }
5121
5130
  const { url, sessionId } = resolveUrlAndSessionId(first, second);
5122
5131
  if (watchMinutes !== undefined) {
@@ -5137,7 +5146,7 @@ export async function main(argv) {
5137
5146
  if (opts.authRefresh !== false) {
5138
5147
  const fresh = await ensureProfileAuthFresh(resolved);
5139
5148
  if (fresh.checked)
5140
- process.stderr.write(`[remote] auth status ok: ${fresh.command}\n`);
5149
+ process.stderr.write(`[h2a] auth status ok: ${fresh.command}\n`);
5141
5150
  }
5142
5151
  await softRefreshSession(sessionId, resolved);
5143
5152
  return;
@@ -5164,13 +5173,13 @@ export async function main(argv) {
5164
5173
  }
5165
5174
  if (opts.local) {
5166
5175
  if (local.length === 0)
5167
- process.stderr.write("[remote] no local sessions\n");
5176
+ process.stderr.write("[h2a] no local sessions\n");
5168
5177
  return;
5169
5178
  }
5170
5179
  const remote = url ?? getConfiguredRemoteOptional();
5171
5180
  if (!remote) {
5172
5181
  if (local.length === 0) {
5173
- process.stderr.write("[remote] no local sessions and no remote configured\n");
5182
+ process.stderr.write("[h2a] no local sessions and no remote configured\n");
5174
5183
  }
5175
5184
  return;
5176
5185
  }
@@ -5178,7 +5187,7 @@ export async function main(argv) {
5178
5187
  const sessions = await listRemoteSessions(remote);
5179
5188
  if (sessions.length === 0) {
5180
5189
  if (local.length === 0)
5181
- process.stderr.write("[remote] no sessions\n");
5190
+ process.stderr.write("[h2a] no sessions\n");
5182
5191
  return;
5183
5192
  }
5184
5193
  if (local.length > 0)
@@ -5208,7 +5217,7 @@ export async function main(argv) {
5208
5217
  const local = findLocalSession(first);
5209
5218
  if (local) {
5210
5219
  const ok = killLocalSession(local.name);
5211
- process.stderr.write(`[remote] local session ${local.slug} ${ok ? "killed" : "could not be killed"}\n`);
5220
+ process.stderr.write(`[h2a] local session ${local.slug} ${ok ? "killed" : "could not be killed"}\n`);
5212
5221
  if (!ok)
5213
5222
  process.exitCode = 1;
5214
5223
  return;
@@ -5216,7 +5225,7 @@ export async function main(argv) {
5216
5225
  }
5217
5226
  const { url, sessionId } = resolveUrlAndSessionId(first, second);
5218
5227
  const result = await stopRemoteSession(url, sessionId, opts.reason);
5219
- process.stderr.write(`[remote] stop ${result.accepted ? "accepted" : "rejected"} for ${sessionId}\n`);
5228
+ process.stderr.write(`[h2a] stop ${result.accepted ? "accepted" : "rejected"} for ${sessionId}\n`);
5220
5229
  });
5221
5230
  program
5222
5231
  .command("rename <slugOrId> <newName>")
@@ -5229,7 +5238,7 @@ export async function main(argv) {
5229
5238
  const local = findLocalSession(slugOrId);
5230
5239
  if (local) {
5231
5240
  setLocalSessionDisplayName(local.name, newName);
5232
- process.stderr.write(`[remote] local session display name set to "${newName}"\n`);
5241
+ process.stderr.write(`[h2a] local session display name set to "${newName}"\n`);
5233
5242
  }
5234
5243
  // Rename on the control-plane.
5235
5244
  const url = getConfiguredRemote(opts.remote);
@@ -5251,7 +5260,7 @@ export async function main(argv) {
5251
5260
  }
5252
5261
  }
5253
5262
  const result = await renameRemoteSession(url, sessionId, newName);
5254
- process.stderr.write(`[remote] session ${result.sessionId} renamed to "${result.displayName}" (accepted: ${String(result.accepted)})\n`);
5263
+ process.stderr.write(`[h2a] session ${result.sessionId} renamed to "${result.displayName}" (accepted: ${String(result.accepted)})\n`);
5255
5264
  });
5256
5265
  // ---------------------------------------------------------------------------
5257
5266
  // account — WP16 Layer-C: local LLM account pool (enroll / ls / rm / select)
@@ -5275,7 +5284,7 @@ export async function main(argv) {
5275
5284
  .action((opts) => {
5276
5285
  const provider = opts.provider;
5277
5286
  if (provider !== "claude-code" && provider !== "codex") {
5278
- process.stderr.write(`[remote] account enroll: unknown provider "${opts.provider}" (known: claude-code, codex)\n`);
5287
+ process.stderr.write(`[h2a] account enroll: unknown provider "${opts.provider}" (known: claude-code, codex)\n`);
5279
5288
  process.exitCode = 1;
5280
5289
  return;
5281
5290
  }
@@ -5285,18 +5294,18 @@ export async function main(argv) {
5285
5294
  ? readClaudeCredential(opts.configDir)
5286
5295
  : readCodexCredential(opts.configDir);
5287
5296
  if (!result.ok) {
5288
- process.stderr.write(`[remote] account enroll: ${result.error}\n`);
5297
+ process.stderr.write(`[h2a] account enroll: ${result.error}\n`);
5289
5298
  process.exitCode = 1;
5290
5299
  return;
5291
5300
  }
5292
5301
  accessToken = result.accessToken;
5293
- process.stderr.write(`[remote] account enroll: read token from ${provider === "claude-code" ? "~/.claude/.credentials.json" : "~/.codex/auth.json"}\n`);
5302
+ process.stderr.write(`[h2a] account enroll: read token from ${provider === "claude-code" ? "~/.claude/.credentials.json" : "~/.codex/auth.json"}\n`);
5294
5303
  }
5295
5304
  else {
5296
5305
  accessToken = process.env.REMOTE_ACCOUNT_TOKEN ?? "";
5297
5306
  if (!accessToken.trim()) {
5298
- process.stderr.write("[remote] account enroll: REMOTE_ACCOUNT_TOKEN env var is not set or empty\n" +
5299
- "[remote] (tip: use --from-credentials to read from the CLI's local config)\n");
5307
+ process.stderr.write("[h2a] account enroll: REMOTE_ACCOUNT_TOKEN env var is not set or empty\n" +
5308
+ "[h2a] (tip: use --from-credentials to read from the CLI's local config)\n");
5300
5309
  process.exitCode = 1;
5301
5310
  return;
5302
5311
  }
@@ -5312,11 +5321,11 @@ export async function main(argv) {
5312
5321
  : {}),
5313
5322
  });
5314
5323
  if (!result.ok) {
5315
- process.stderr.write(`[remote] account enroll: ${result.error}\n`);
5324
+ process.stderr.write(`[h2a] account enroll: ${result.error}\n`);
5316
5325
  process.exitCode = 1;
5317
5326
  return;
5318
5327
  }
5319
- process.stderr.write(`[remote] account enrolled: ${result.descriptor.id} (${result.descriptor.provider}) "${result.descriptor.label}"\n`);
5328
+ process.stderr.write(`[h2a] account enrolled: ${result.descriptor.id} (${result.descriptor.provider}) "${result.descriptor.label}"\n`);
5320
5329
  });
5321
5330
  accountCommand
5322
5331
  .command("ls")
@@ -5329,7 +5338,7 @@ export async function main(argv) {
5329
5338
  return;
5330
5339
  }
5331
5340
  if (accounts.length === 0) {
5332
- process.stderr.write("[remote] no accounts enrolled\n");
5341
+ process.stderr.write("[h2a] no accounts enrolled\n");
5333
5342
  return;
5334
5343
  }
5335
5344
  const hasConfigDir = accounts.some((a) => a.configDir !== undefined);
@@ -5359,11 +5368,11 @@ export async function main(argv) {
5359
5368
  .action((id) => {
5360
5369
  const result = removeAccount(id);
5361
5370
  if (!result.ok) {
5362
- process.stderr.write(`[remote] account rm: ${result.error}\n`);
5371
+ process.stderr.write(`[h2a] account rm: ${result.error}\n`);
5363
5372
  process.exitCode = 1;
5364
5373
  return;
5365
5374
  }
5366
- process.stderr.write(`[remote] account removed: ${result.id}\n`);
5375
+ process.stderr.write(`[h2a] account removed: ${result.id}\n`);
5367
5376
  });
5368
5377
  accountCommand
5369
5378
  .command("exhausted <id>")
@@ -5385,7 +5394,7 @@ export async function main(argv) {
5385
5394
  })();
5386
5395
  const rec = markExhausted(id, windowMs, opts.reason);
5387
5396
  const resetsAt = new Date(new Date(rec.exhaustedAt).getTime() + windowMs).toISOString();
5388
- process.stderr.write(`[remote] account ${id} marked exhausted — resets at ${resetsAt}` +
5397
+ process.stderr.write(`[h2a] account ${id} marked exhausted — resets at ${resetsAt}` +
5389
5398
  (opts.reason ? ` (reason: ${opts.reason})` : "") +
5390
5399
  "\n");
5391
5400
  });
@@ -5395,7 +5404,7 @@ export async function main(argv) {
5395
5404
  "becomes immediately available for selection again).")
5396
5405
  .action((id) => {
5397
5406
  clearExhaustion(id);
5398
- process.stderr.write(`[remote] account ${id} quota cleared — available for selection\n`);
5407
+ process.stderr.write(`[h2a] account ${id} quota cleared — available for selection\n`);
5399
5408
  });
5400
5409
  accountCommand
5401
5410
  .command("select")
@@ -5409,14 +5418,14 @@ export async function main(argv) {
5409
5418
  if (opts.fallback) {
5410
5419
  if (!opts.provider ||
5411
5420
  (opts.provider !== "claude-code" && opts.provider !== "codex")) {
5412
- process.stderr.write("[remote] account select --fallback requires --provider claude-code|codex\n");
5421
+ process.stderr.write("[h2a] account select --fallback requires --provider claude-code|codex\n");
5413
5422
  process.exitCode = 1;
5414
5423
  return;
5415
5424
  }
5416
5425
  const provider = opts.provider;
5417
5426
  const sel = selectAccountWithFallback(provider, opts.affinityKey);
5418
5427
  if (!sel.candidate) {
5419
- process.stderr.write("[remote] account select: all accounts exhausted — no usable account available\n");
5428
+ process.stderr.write("[h2a] account select: all accounts exhausted — no usable account available\n");
5420
5429
  process.exitCode = 1;
5421
5430
  return;
5422
5431
  }
@@ -5434,7 +5443,7 @@ export async function main(argv) {
5434
5443
  const candidates = loadCandidates(provider).map(({ accessToken: _t, ...d }) => d);
5435
5444
  const pick = selectAccount(candidates, opts.lastUsed);
5436
5445
  if (!pick) {
5437
- process.stderr.write("[remote] account select: no candidates available\n");
5446
+ process.stderr.write("[h2a] account select: no candidates available\n");
5438
5447
  return;
5439
5448
  }
5440
5449
  process.stdout.write(JSON.stringify({ selected: pick, totalCandidates: candidates.length }, null, 2) + "\n");
@@ -5454,15 +5463,15 @@ export async function main(argv) {
5454
5463
  ? opts.exportS3
5455
5464
  : process.env.REMOTE_ACCOUNT_LOG_S3;
5456
5465
  if (!uri) {
5457
- process.stderr.write("[remote] account log --export-s3: no S3 URI provided and REMOTE_ACCOUNT_LOG_S3 is not set\n");
5466
+ process.stderr.write("[h2a] account log --export-s3: no S3 URI provided and REMOTE_ACCOUNT_LOG_S3 is not set\n");
5458
5467
  process.exit(1);
5459
5468
  }
5460
5469
  try {
5461
5470
  await exportSessionLogToS3(uri);
5462
- process.stdout.write(`[remote] session log exported to ${uri}\n`);
5471
+ process.stdout.write(`[h2a] session log exported to ${uri}\n`);
5463
5472
  }
5464
5473
  catch (err) {
5465
- process.stderr.write(`[remote] account log --export-s3: ${err instanceof Error ? err.message : String(err)}\n`);
5474
+ process.stderr.write(`[h2a] account log --export-s3: ${err instanceof Error ? err.message : String(err)}\n`);
5466
5475
  process.exit(1);
5467
5476
  }
5468
5477
  return;
@@ -5473,7 +5482,7 @@ export async function main(argv) {
5473
5482
  raw = readFileSync(logFile, "utf8");
5474
5483
  }
5475
5484
  catch {
5476
- process.stderr.write("[remote] account log: no session log yet (no jobs launched with accounts enrolled)\n");
5485
+ process.stderr.write("[h2a] account log: no session log yet (no jobs launched with accounts enrolled)\n");
5477
5486
  return;
5478
5487
  }
5479
5488
  const lines = raw.split("\n").filter(Boolean);
@@ -5484,7 +5493,7 @@ export async function main(argv) {
5484
5493
  return;
5485
5494
  }
5486
5495
  if (tail.length === 0) {
5487
- process.stderr.write("[remote] account log: empty\n");
5496
+ process.stderr.write("[h2a] account log: empty\n");
5488
5497
  return;
5489
5498
  }
5490
5499
  process.stdout.write([
@@ -5522,7 +5531,7 @@ export async function main(argv) {
5522
5531
  .action((affinityKey) => {
5523
5532
  const existing = lookupBinding(affinityKey);
5524
5533
  if (!existing) {
5525
- process.stderr.write(`[remote] no binding for "${affinityKey}"\n`);
5534
+ process.stderr.write(`[h2a] no binding for "${affinityKey}"\n`);
5526
5535
  process.exitCode = 1;
5527
5536
  return;
5528
5537
  }
@@ -5530,7 +5539,7 @@ export async function main(argv) {
5530
5539
  const accounts = listAccounts();
5531
5540
  const label = accounts.find((a) => a.id === existing.accountId)?.label ??
5532
5541
  existing.accountId.slice(0, 8);
5533
- process.stderr.write(`[remote] binding removed: ${affinityKey} → ${label} (${existing.provider})\n`);
5542
+ process.stderr.write(`[h2a] binding removed: ${affinityKey} → ${label} (${existing.provider})\n`);
5534
5543
  });
5535
5544
  accountCommand
5536
5545
  .command("bindings")
@@ -5544,7 +5553,7 @@ export async function main(argv) {
5544
5553
  return;
5545
5554
  }
5546
5555
  if (bindings.length === 0) {
5547
- process.stderr.write("[remote] no active bindings (no jobs launched with accounts enrolled)\n");
5556
+ process.stderr.write("[h2a] no active bindings (no jobs launched with accounts enrolled)\n");
5548
5557
  return;
5549
5558
  }
5550
5559
  const accounts = listAccounts();
@@ -5581,7 +5590,7 @@ export async function main(argv) {
5581
5590
  .action(async (opts) => {
5582
5591
  const accounts = listAccountsWithTokens();
5583
5592
  if (accounts.length === 0) {
5584
- process.stderr.write("[remote] no enrolled accounts with tokens found — run `remote account enroll` first\n");
5593
+ process.stderr.write("[h2a] no enrolled accounts with tokens found — run `h2a account enroll` first\n");
5585
5594
  process.exit(1);
5586
5595
  }
5587
5596
  // Build the gateway-format JSON (token field, not accessToken)
@@ -5616,10 +5625,10 @@ export async function main(argv) {
5616
5625
  encoding: "utf-8",
5617
5626
  });
5618
5627
  if (result.status !== 0) {
5619
- process.stderr.write(`[remote] kubectl apply failed (exit ${result.status ?? "?"}) — try --dry-run to inspect the YAML\n`);
5628
+ process.stderr.write(`[h2a] kubectl apply failed (exit ${result.status ?? "?"}) — try --dry-run to inspect the YAML\n`);
5620
5629
  process.exit(result.status ?? 1);
5621
5630
  }
5622
- process.stdout.write(`[remote] pushed ${accounts.length} account(s) to ${opts.namespace}/${opts.secretName}\n`);
5631
+ process.stdout.write(`[h2a] pushed ${accounts.length} account(s) to ${opts.namespace}/${opts.secretName}\n`);
5623
5632
  });
5624
5633
  void accountPushClusterCommand; // suppress unused-var lint
5625
5634
  // ---------------------------------------------------------------------------
@@ -5642,10 +5651,10 @@ export async function main(argv) {
5642
5651
  .action(async (provider, opts) => {
5643
5652
  let account;
5644
5653
  if (provider === "claude") {
5645
- process.stderr.write(`[remote] llm-mesh: Claude Code OAuth is not a supported upstream transport yet.\n` +
5654
+ process.stderr.write(`[h2a] llm-mesh: Claude Code OAuth is not a supported upstream transport yet.\n` +
5646
5655
  ` Do not import ~/.claude/.credentials.json as an Anthropic API credential.\n` +
5647
5656
  ` Use the existing gateway token with codex/openai, or enroll a real Anthropic API key via:\n` +
5648
- ` remote llm-mesh enroll anthropic --token sk-ant-api...\n`);
5657
+ ` h2a llm-mesh enroll anthropic --token sk-ant-api...\n`);
5649
5658
  process.exitCode = 1;
5650
5659
  return;
5651
5660
  }
@@ -5658,8 +5667,8 @@ export async function main(argv) {
5658
5667
  }
5659
5668
  else if (provider === "openai") {
5660
5669
  if (!opts.token) {
5661
- process.stderr.write(`[remote] llm-mesh: --token <sk-...> is required for provider "openai"\n` +
5662
- ` Example: remote llm-mesh enroll openai --token sk-proj-...\n`);
5670
+ process.stderr.write(`[h2a] llm-mesh: --token <sk-...> is required for provider "openai"\n` +
5671
+ ` Example: h2a llm-mesh enroll openai --token sk-proj-...\n`);
5663
5672
  process.exitCode = 1;
5664
5673
  return;
5665
5674
  }
@@ -5672,12 +5681,12 @@ export async function main(argv) {
5672
5681
  }
5673
5682
  else if (provider === "anthropic") {
5674
5683
  if (!opts.token) {
5675
- process.stderr.write(`[remote] llm-mesh: --token <sk-ant-api...> is required for provider "anthropic"\n`);
5684
+ process.stderr.write(`[h2a] llm-mesh: --token <sk-ant-api...> is required for provider "anthropic"\n`);
5676
5685
  process.exitCode = 1;
5677
5686
  return;
5678
5687
  }
5679
5688
  if (opts.token.startsWith("sk-ant-oat")) {
5680
- process.stderr.write(`[remote] llm-mesh: refused Claude Code OAuth token for provider "anthropic".\n` +
5689
+ process.stderr.write(`[h2a] llm-mesh: refused Claude Code OAuth token for provider "anthropic".\n` +
5681
5690
  ` Claude Code OAuth uses a separate Claude Code transport, not Anthropic /v1/messages.\n`);
5682
5691
  process.exitCode = 1;
5683
5692
  return;
@@ -5690,7 +5699,7 @@ export async function main(argv) {
5690
5699
  };
5691
5700
  }
5692
5701
  else {
5693
- process.stderr.write(`[remote] llm-mesh: unsupported provider "${provider}".\n` +
5702
+ process.stderr.write(`[h2a] llm-mesh: unsupported provider "${provider}".\n` +
5694
5703
  ` Supported: codex (OAuth), openai (API key), anthropic (API key)\n`);
5695
5704
  process.exitCode = 1;
5696
5705
  return;
@@ -5699,18 +5708,18 @@ export async function main(argv) {
5699
5708
  const accounts = existing.accounts.filter((a) => a.id !== account.id);
5700
5709
  accounts.push(account);
5701
5710
  writeLlmMeshConfig({ ...existing, accounts });
5702
- process.stdout.write(`[remote] llm-mesh: enrolled ${account.label} (id: ${account.id}, provider: ${account.provider})\n`);
5711
+ process.stdout.write(`[h2a] llm-mesh: enrolled ${account.label} (id: ${account.id}, provider: ${account.provider})\n`);
5703
5712
  if ("expiresAt" in account && account.expiresAt) {
5704
5713
  const exp = new Date(account.expiresAt);
5705
5714
  const minsLeft = Math.round((exp.getTime() - Date.now()) / 60_000);
5706
- process.stdout.write(`[remote] llm-mesh: token expires ${account.expiresAt} (${minsLeft > 0 ? `in ${minsLeft}min` : "EXPIRED"})\n`);
5715
+ process.stdout.write(`[h2a] llm-mesh: token expires ${account.expiresAt} (${minsLeft > 0 ? `in ${minsLeft}min` : "EXPIRED"})\n`);
5707
5716
  }
5708
5717
  if (provider === "codex") {
5709
- process.stdout.write(`[remote] NOTE: Codex OAuth token works locally via codex app-server,\n` +
5718
+ process.stdout.write(`[h2a] NOTE: Codex OAuth token works locally via codex app-server,\n` +
5710
5719
  ` but NOT with api.openai.com (missing model.request scope).\n` +
5711
- ` For a fully working gateway, use: remote llm-mesh enroll openai --token sk-...\n`);
5720
+ ` For a fully working gateway, use: h2a llm-mesh enroll openai --token sk-...\n`);
5712
5721
  }
5713
- process.stdout.write(`[remote] Run \`remote llm-mesh start\` to launch the gateway.\n`);
5722
+ process.stdout.write(`[h2a] Run \`h2a llm-mesh start\` to launch the gateway.\n`);
5714
5723
  });
5715
5724
  llmMeshCommand
5716
5725
  .command("start")
@@ -5719,23 +5728,23 @@ export async function main(argv) {
5719
5728
  .action(async (opts) => {
5720
5729
  const config = readLlmMeshConfig();
5721
5730
  if (!config || config.accounts.length === 0) {
5722
- process.stderr.write(`[remote] llm-mesh: no accounts configured. Run \`remote llm-mesh enroll codex\` first.\n`);
5731
+ process.stderr.write(`[h2a] llm-mesh: no accounts configured. Run \`h2a llm-mesh enroll codex\` first.\n`);
5723
5732
  process.exitCode = 1;
5724
5733
  return;
5725
5734
  }
5726
5735
  const existing = readGatewayPid();
5727
5736
  if (existing) {
5728
5737
  setLlmMeshRuntimeConfig({ enabled: true });
5729
- process.stdout.write(`[remote] llm-mesh: already running (pid ${existing})\n`);
5730
- process.stdout.write(`[remote] llm-mesh: restore config enabled\n`);
5738
+ process.stdout.write(`[h2a] llm-mesh: already running (pid ${existing})\n`);
5739
+ process.stdout.write(`[h2a] llm-mesh: restore config enabled\n`);
5731
5740
  return;
5732
5741
  }
5733
- process.stdout.write(`[remote] llm-mesh: starting gateway…\n`);
5742
+ process.stdout.write(`[h2a] llm-mesh: starting gateway…\n`);
5734
5743
  const result = await startGateway(config, { verbose: opts.verbose });
5735
5744
  setLlmMeshRuntimeConfig({ enabled: true });
5736
5745
  const port = result.port;
5737
- process.stdout.write(`[remote] llm-mesh: gateway started (pid ${result.pid}, port ${port})\n`);
5738
- process.stdout.write(`[remote] llm-mesh: restore config enabled\n`);
5746
+ process.stdout.write(`[h2a] llm-mesh: gateway started (pid ${result.pid}, port ${port})\n`);
5747
+ process.stdout.write(`[h2a] llm-mesh: restore config enabled\n`);
5739
5748
  process.stdout.write(`\nTo use with Claude Code:\n`);
5740
5749
  process.stdout.write(` export ANTHROPIC_BASE_URL=http://localhost:${port}\n`);
5741
5750
  process.stdout.write(` export ANTHROPIC_AUTH_TOKEN=${result.gatewayToken}\n`);
@@ -5745,14 +5754,14 @@ export async function main(argv) {
5745
5754
  .description("Enable llm-mesh auto-reactivation for local sessions and remote restore")
5746
5755
  .action(() => {
5747
5756
  setLlmMeshRuntimeConfig({ enabled: true });
5748
- process.stdout.write(`[remote] llm-mesh: restore/local auto-reactivation enabled\n`);
5757
+ process.stdout.write(`[h2a] llm-mesh: restore/local auto-reactivation enabled\n`);
5749
5758
  });
5750
5759
  llmMeshCommand
5751
5760
  .command("disable")
5752
5761
  .description("Disable llm-mesh auto-reactivation for local sessions and remote restore")
5753
5762
  .action(() => {
5754
5763
  setLlmMeshRuntimeConfig({ enabled: false });
5755
- process.stdout.write(`[remote] llm-mesh: restore/local auto-reactivation disabled\n`);
5764
+ process.stdout.write(`[h2a] llm-mesh: restore/local auto-reactivation disabled\n`);
5756
5765
  });
5757
5766
  llmMeshCommand
5758
5767
  .command("stop")
@@ -5760,10 +5769,10 @@ export async function main(argv) {
5760
5769
  .action(() => {
5761
5770
  const res = stopGateway();
5762
5771
  if (res.stopped) {
5763
- process.stdout.write(`[remote] llm-mesh: stopped (pid ${res.pid})\n`);
5772
+ process.stdout.write(`[h2a] llm-mesh: stopped (pid ${res.pid})\n`);
5764
5773
  }
5765
5774
  else {
5766
- process.stdout.write(`[remote] llm-mesh: not running\n`);
5775
+ process.stdout.write(`[h2a] llm-mesh: not running\n`);
5767
5776
  }
5768
5777
  });
5769
5778
  llmMeshCommand
@@ -5773,23 +5782,23 @@ export async function main(argv) {
5773
5782
  .action(async (opts) => {
5774
5783
  const config = readLlmMeshConfig();
5775
5784
  if (!config || config.accounts.length === 0) {
5776
- process.stderr.write(`[remote] llm-mesh: no accounts configured. Run \`remote llm-mesh enroll codex\` first.\n`);
5785
+ process.stderr.write(`[h2a] llm-mesh: no accounts configured. Run \`h2a llm-mesh enroll codex\` first.\n`);
5777
5786
  process.exitCode = 1;
5778
5787
  return;
5779
5788
  }
5780
5789
  const stopped = stopGateway();
5781
5790
  if (stopped.stopped) {
5782
- process.stdout.write(`[remote] llm-mesh: stopped gateway (pid ${stopped.pid})\n`);
5791
+ process.stdout.write(`[h2a] llm-mesh: stopped gateway (pid ${stopped.pid})\n`);
5783
5792
  }
5784
5793
  else {
5785
- process.stdout.write(`[remote] llm-mesh: gateway was not running\n`);
5794
+ process.stdout.write(`[h2a] llm-mesh: gateway was not running\n`);
5786
5795
  }
5787
- process.stdout.write(`[remote] llm-mesh: starting gateway…\n`);
5796
+ process.stdout.write(`[h2a] llm-mesh: starting gateway…\n`);
5788
5797
  const result = await startGateway(config, { verbose: opts.verbose });
5789
5798
  setLlmMeshRuntimeConfig({ enabled: true });
5790
- process.stdout.write(`[remote] llm-mesh: gateway restarted (pid ${result.pid}, port ${result.port})\n`);
5791
- process.stdout.write(`[remote] llm-mesh: restore config enabled\n`);
5792
- process.stdout.write(`[remote] llm-mesh: no tmux/Claude session was restarted; active sessions may keep their old gateway token until explicitly relaunched.\n`);
5799
+ process.stdout.write(`[h2a] llm-mesh: gateway restarted (pid ${result.pid}, port ${result.port})\n`);
5800
+ process.stdout.write(`[h2a] llm-mesh: restore config enabled\n`);
5801
+ process.stdout.write(`[h2a] llm-mesh: no tmux/Claude session was restarted; active sessions may keep their old gateway token until explicitly relaunched.\n`);
5793
5802
  process.stdout.write(`\nTo use with new Claude Code processes:\n`);
5794
5803
  process.stdout.write(` export ANTHROPIC_BASE_URL=http://localhost:${result.port}\n`);
5795
5804
  process.stdout.write(` export ANTHROPIC_AUTH_TOKEN=${result.gatewayToken}\n`);
@@ -5804,10 +5813,10 @@ export async function main(argv) {
5804
5813
  const pid = readGatewayPid();
5805
5814
  const port = config?.port ?? 3002;
5806
5815
  if (pid) {
5807
- process.stdout.write(`[remote] llm-mesh: running (pid ${pid}, port ${port})\n`);
5816
+ process.stdout.write(`[h2a] llm-mesh: running (pid ${pid}, port ${port})\n`);
5808
5817
  }
5809
5818
  else {
5810
- process.stdout.write(`[remote] llm-mesh: stopped\n`);
5819
+ process.stdout.write(`[h2a] llm-mesh: stopped\n`);
5811
5820
  }
5812
5821
  process.stdout.write(` restore/local auto-reactivation: ${runtime.enabled ? "enabled" : "disabled"}\n`);
5813
5822
  if (config) {
@@ -5825,7 +5834,7 @@ export async function main(argv) {
5825
5834
  }
5826
5835
  }
5827
5836
  else {
5828
- process.stdout.write(` no config. Run \`remote llm-mesh enroll codex\` first.\n`);
5837
+ process.stdout.write(` no config. Run \`h2a llm-mesh enroll codex\` first.\n`);
5829
5838
  }
5830
5839
  });
5831
5840
  llmMeshCommand
@@ -5839,7 +5848,7 @@ export async function main(argv) {
5839
5848
  stdio: "inherit",
5840
5849
  });
5841
5850
  if (tail.error) {
5842
- process.stderr.write(`[remote] llm-mesh: log tail failed: ${String(tail.error)}\n`);
5851
+ process.stderr.write(`[h2a] llm-mesh: log tail failed: ${String(tail.error)}\n`);
5843
5852
  process.exitCode = 1;
5844
5853
  }
5845
5854
  });
@@ -5855,7 +5864,7 @@ export async function main(argv) {
5855
5864
  .description("Suspend the local incarnation for a lineage: writes a sentinel file that prevents the agent from starting a new turn. Does NOT kill the process.")
5856
5865
  .action((id) => {
5857
5866
  suspendLocalIncarnation(id);
5858
- process.stderr.write(`[remote] lineage ${id} suspended\n`);
5867
+ process.stderr.write(`[h2a] lineage ${id} suspended\n`);
5859
5868
  });
5860
5869
  lineageCommand
5861
5870
  .command("resume <id>")
@@ -5864,8 +5873,8 @@ export async function main(argv) {
5864
5873
  const wasSuspended = isIncarnationSuspended(id);
5865
5874
  resumeLocalIncarnation(id);
5866
5875
  process.stderr.write(wasSuspended
5867
- ? `[remote] lineage ${id} resumed\n`
5868
- : `[remote] lineage ${id} was not suspended (no-op)\n`);
5876
+ ? `[h2a] lineage ${id} resumed\n`
5877
+ : `[h2a] lineage ${id} was not suspended (no-op)\n`);
5869
5878
  });
5870
5879
  await program.parseAsync([...argv]);
5871
5880
  const code = process.exitCode;
@@ -5889,7 +5898,7 @@ if (isEntryPoint()) {
5889
5898
  console.error(error.message);
5890
5899
  }
5891
5900
  else {
5892
- console.error("[remote] fatal:", error);
5901
+ console.error("[h2a] fatal:", error);
5893
5902
  }
5894
5903
  process.exitCode = 1;
5895
5904
  });