@sentropic/h2a-runtime 0.98.7-rc.7 → 0.98.7-rc.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/attach.d.ts +3 -0
- package/dist/attach.d.ts.map +1 -1
- package/dist/attach.js +2 -0
- package/dist/attach.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +169 -69
- package/dist/index.js.map +1 -1
- package/dist/launch-layout.d.ts.map +1 -1
- package/dist/launch-layout.js +10 -2
- package/dist/launch-layout.js.map +1 -1
- package/dist/native-host.d.ts.map +1 -1
- package/dist/native-host.js +9 -1
- package/dist/native-host.js.map +1 -1
- package/dist/native-restart.d.ts +2 -0
- package/dist/native-restart.d.ts.map +1 -1
- package/dist/native-restart.js +7 -0
- package/dist/native-restart.js.map +1 -1
- package/dist/protocol-local.d.ts +16 -0
- package/dist/protocol-local.d.ts.map +1 -1
- package/dist/protocol-local.js +14 -0
- package/dist/protocol-local.js.map +1 -1
- package/dist/registry.d.ts +11 -0
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +19 -2
- package/dist/registry.js.map +1 -1
- package/dist/relaunch.d.ts +16 -0
- package/dist/relaunch.d.ts.map +1 -1
- package/dist/relaunch.js +42 -1
- package/dist/relaunch.js.map +1 -1
- package/dist/restore.d.ts +4 -0
- package/dist/restore.d.ts.map +1 -1
- package/dist/restore.js +4 -0
- package/dist/restore.js.map +1 -1
- package/dist/resume-launch-options.d.ts +3 -3
- package/dist/resume-launch-options.d.ts.map +1 -1
- package/dist/resume-launch-options.js +7 -4
- package/dist/resume-launch-options.js.map +1 -1
- package/dist/run.d.ts +2 -0
- package/dist/run.d.ts.map +1 -1
- package/dist/run.js +6 -1
- package/dist/run.js.map +1 -1
- package/dist/soft-refresh.d.ts.map +1 -1
- package/dist/soft-refresh.js +6 -0
- package/dist/soft-refresh.js.map +1 -1
- package/dist/tmux.d.ts +27 -3
- package/dist/tmux.d.ts.map +1 -1
- package/dist/tmux.js +49 -9
- package/dist/tmux.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -31,7 +31,7 @@ import { projectStatusForH2a } from "./status-projection.js";
|
|
|
31
31
|
import { buildAgentLaunchArgs, buildAgentLaunchStdin, isAgentLaunchEffort, isAgentLaunchProfile, } from "./agent-launch-args.js";
|
|
32
32
|
import { resolveGatewayModelAlias } from "./gateway-model-catalog/model-catalog.js";
|
|
33
33
|
import { deriveSessionClass } from "./session-class.js";
|
|
34
|
-
import { isRelaunchKillable, planRelaunch, relaunchContinuationPrompt, tryAcquireResumeLaunchClaim, wakeRelaunchedSession, } from "./relaunch.js";
|
|
34
|
+
import { isRelaunchKillable, planRelaunch, relaunchContinuationPrompt, scrubbedRetypeCommand, prepareGatewayRetypeCommand, tryAcquireResumeLaunchClaim, wakeRelaunchedSession, } from "./relaunch.js";
|
|
35
35
|
import { isHumanFacingSession, legacySessionEvidence, readConversationCustomTitle, readLastLayout, reconcileRunConvIds, restore as restoreLayout, } from "./restore.js";
|
|
36
36
|
import { getLayoutConfig } from "./config.js";
|
|
37
37
|
import { resolvePinnedLaunchEntries } from "./resume-launch-options.js";
|
|
@@ -81,7 +81,7 @@ import { createInterface } from "node:readline";
|
|
|
81
81
|
const H2A_RUN_API_VERSION = "h2a.run/v1";
|
|
82
82
|
const STRUCTURED_LAUNCH_PHASE_PREFIX = "[h2a] h2a.run.phase/v1 ";
|
|
83
83
|
const H2A_RUNTIME_VERSION = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")).version;
|
|
84
|
-
import { CLI_PROFILES, gatewayModeForProfile, profileUsesLlmMeshGateway, } from "./protocol-local.js";
|
|
84
|
+
import { CLI_PROFILES, gatewayModeForProfile, profileUsesLlmMeshGateway, resolveResumedGatewayMode, } from "./protocol-local.js";
|
|
85
85
|
import { readLlmMeshConfig, updateLlmMeshRoutingConfig, } from "./gateway-host/config-file.js";
|
|
86
86
|
import { startGateway, stopGateway, readGatewayPid, llmMeshLogPath, replaceAnthropicGatewayEnvironment, acquireLlmMeshSessionEnv, } from "./gateway-host/daemon.js";
|
|
87
87
|
import { describeLlmMeshRoutingConfig, parseLlmMeshRoutingConfig, preferredRoutingConfig, strategyRoutingConfig, } from "./routing-preferences.js";
|
|
@@ -141,6 +141,7 @@ async function startRemoteFanout(remote, profileName, members, spec) {
|
|
|
141
141
|
? { startupArgs: spec.startupArgs }
|
|
142
142
|
: {}),
|
|
143
143
|
...(spec.credentials ? { credentials: spec.credentials } : {}),
|
|
144
|
+
...(spec.llmGateway === true ? { llmGateway: true } : {}),
|
|
144
145
|
});
|
|
145
146
|
return { name: member.name, sessionId: session.id, workspaceId: ws.id };
|
|
146
147
|
});
|
|
@@ -206,6 +207,20 @@ async function runProfile(profileName, opts, commandArgs = []) {
|
|
|
206
207
|
if (opts.resume !== undefined && resumeArgs.length === 0) {
|
|
207
208
|
throw new Error(`profile "${profileName}" has no verified resume argv; start it without -r/--resume`);
|
|
208
209
|
}
|
|
210
|
+
// Remote sessions are direct unless the gateway is explicitly asked for.
|
|
211
|
+
// Only an explicit --gw/--llm-gateway transmits the opt-in; the server
|
|
212
|
+
// ignores a globally configured gateway URL without it.
|
|
213
|
+
let remoteGatewayMode;
|
|
214
|
+
try {
|
|
215
|
+
remoteGatewayMode = gatewayModeFromOptions(opts);
|
|
216
|
+
}
|
|
217
|
+
catch (error) {
|
|
218
|
+
throw new Error(`[h2a] ${error.message}.`);
|
|
219
|
+
}
|
|
220
|
+
if (remoteGatewayMode === "gateway" && !profileUsesLlmMeshGateway(profileName)) {
|
|
221
|
+
throw new Error(`[h2a] --llm-gateway/--gw is unsupported for ${profileName}; this gateway is Anthropic-compatible and only Claude profiles consume it.`);
|
|
222
|
+
}
|
|
223
|
+
const remoteGatewayChoice = remoteGatewayMode === "gateway" ? { llmGateway: true } : {};
|
|
209
224
|
const startupArgs = [...resumeArgs, ...commandArgs];
|
|
210
225
|
// WP6 — REMOTE fan-out: --count N spawns N concurrent remote sessions, each
|
|
211
226
|
// on its OWN workspace subPath of the shared RWX volume. count<=1 falls
|
|
@@ -235,6 +250,7 @@ async function runProfile(profileName, opts, commandArgs = []) {
|
|
|
235
250
|
target: opts.target,
|
|
236
251
|
startupArgs,
|
|
237
252
|
...(credentials ? { credentials } : {}),
|
|
253
|
+
...remoteGatewayChoice,
|
|
238
254
|
});
|
|
239
255
|
return;
|
|
240
256
|
}
|
|
@@ -253,6 +269,7 @@ async function runProfile(profileName, opts, commandArgs = []) {
|
|
|
253
269
|
...(opts.workspaceId ? { workspaceId: opts.workspaceId } : {}),
|
|
254
270
|
...(opts.browser ? { metadata: { browser: true } } : {}),
|
|
255
271
|
...(opts.agentImage ? { agentImage: opts.agentImage } : {}),
|
|
272
|
+
...remoteGatewayChoice,
|
|
256
273
|
});
|
|
257
274
|
if (archive) {
|
|
258
275
|
await uploadWorkspaceArchive(opts.remote, session.id, archive);
|
|
@@ -276,7 +293,21 @@ async function runProfile(profileName, opts, commandArgs = []) {
|
|
|
276
293
|
...(opts.port !== undefined ? { port: opts.port } : {}),
|
|
277
294
|
...(commandArgs.length > 0 ? { startupArgs: commandArgs } : {}),
|
|
278
295
|
};
|
|
279
|
-
const
|
|
296
|
+
const gatewayMode = gatewayModeFromOptions(opts);
|
|
297
|
+
if (gatewayMode === "gateway" && !profileUsesLlmMeshGateway(profileName)) {
|
|
298
|
+
throw new Error(`--llm-gateway/--gw is unsupported for ${profileName}`);
|
|
299
|
+
}
|
|
300
|
+
// Resolve local PTY routing before spawning, just as for managed sessions.
|
|
301
|
+
// Restore the caller's native auth even if acquisition or spawning fails.
|
|
302
|
+
const restoreEnv = replaceAnthropicGatewayEnvironment(process.env, captureAnthropicGatewayEnvironment());
|
|
303
|
+
let result;
|
|
304
|
+
try {
|
|
305
|
+
await injectLlmMeshGatewayEnv(gatewayMode, false);
|
|
306
|
+
result = await run({ ...runOptions, gatewayMode: gatewayMode === "gateway" ? "gateway" : "direct" });
|
|
307
|
+
}
|
|
308
|
+
finally {
|
|
309
|
+
restoreEnv();
|
|
310
|
+
}
|
|
280
311
|
process.stderr.write(`[h2a] session ${result.sessionId} attach at http://127.0.0.1:${result.port}\n`);
|
|
281
312
|
const { exitCode } = await result.exit;
|
|
282
313
|
process.exitCode = exitCode;
|
|
@@ -858,32 +889,12 @@ export async function startJob(job) {
|
|
|
858
889
|
const prevJobId = process.env[JOB_ID_ENV];
|
|
859
890
|
process.env[DEPTH_ENV] = childDepthEnvValue(job.depthBudget ?? clampDepth(undefined));
|
|
860
891
|
process.env[JOB_ID_ENV] = job.id;
|
|
861
|
-
//
|
|
862
|
-
//
|
|
863
|
-
//
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
const
|
|
867
|
-
? jobSessionResolution.session.name
|
|
868
|
-
: jobSessionResolution.kind === "missing"
|
|
869
|
-
? localSessionName(jobSessionSlug)
|
|
870
|
-
: undefined;
|
|
871
|
-
let meshEnv;
|
|
872
|
-
try {
|
|
873
|
-
meshEnv =
|
|
874
|
-
profileUsesLlmMeshGateway(job.tool) &&
|
|
875
|
-
getLlmMeshRuntimeConfig().enabled &&
|
|
876
|
-
gatewayClientSessionId
|
|
877
|
-
? await acquireLlmMeshSessionEnv(undefined, gatewayClientSessionId)
|
|
878
|
-
: null;
|
|
879
|
-
}
|
|
880
|
-
catch (error) {
|
|
881
|
-
return {
|
|
882
|
-
started: false,
|
|
883
|
-
error: error instanceof Error ? error.message : String(error),
|
|
884
|
-
};
|
|
885
|
-
}
|
|
886
|
-
const restoreGatewayEnvironment = replaceAnthropicGatewayEnvironment(process.env, meshEnv ?? undefined);
|
|
892
|
+
// Gateway off unless explicitly asked: jobs never acquire the gateway.
|
|
893
|
+
// A legacy llmMesh.enabled=true, a running service, enrolled accounts or a
|
|
894
|
+
// parent env never select gateway routing for immediate or deferred jobs.
|
|
895
|
+
// Strip any stale h2a routing vars so the child starts direct; a user-owned
|
|
896
|
+
// ANTHROPIC_API_KEY survives for native authentication.
|
|
897
|
+
const restoreGatewayEnvironment = replaceAnthropicGatewayEnvironment(process.env, undefined);
|
|
887
898
|
let tmuxSession;
|
|
888
899
|
let workerPid;
|
|
889
900
|
let agentPane;
|
|
@@ -1405,13 +1416,12 @@ export function bareChoiceFromOptions(opts) {
|
|
|
1405
1416
|
return false;
|
|
1406
1417
|
return undefined;
|
|
1407
1418
|
}
|
|
1408
|
-
async function injectLlmMeshGatewayEnv(mode = "auto", allowDirectFallback = true, clientSessionId) {
|
|
1409
|
-
if (mode
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
if (mode !== "gateway" && !getLlmMeshRuntimeConfig().enabled) {
|
|
1419
|
+
export async function injectLlmMeshGatewayEnv(mode = "auto", allowDirectFallback = true, clientSessionId) {
|
|
1420
|
+
if (mode !== "gateway") {
|
|
1421
|
+
// Gateway off unless explicitly asked: auto resolves to direct BEFORE any
|
|
1422
|
+
// acquire/start. A legacy llmMesh.enabled=true, a running service, enrolled
|
|
1423
|
+
// accounts or a parent env never select gateway routing. A user-owned
|
|
1424
|
+
// ANTHROPIC_API_KEY is preserved for native authentication.
|
|
1415
1425
|
delete process.env.ANTHROPIC_BASE_URL;
|
|
1416
1426
|
delete process.env.ANTHROPIC_AUTH_TOKEN;
|
|
1417
1427
|
return undefined;
|
|
@@ -1535,7 +1545,9 @@ export async function prepareLlmMeshForRestore(opts = {}, context = {
|
|
|
1535
1545
|
injectGateway: (mode) => injectLlmMeshGatewayEnv(mode, mode !== "gateway"),
|
|
1536
1546
|
}) {
|
|
1537
1547
|
const mode = opts.mode ?? "auto";
|
|
1538
|
-
|
|
1548
|
+
// Global enablement is not restore consent. Pinned sessions request --gw
|
|
1549
|
+
// individually through their launch command; only --gw prewarms globally.
|
|
1550
|
+
if (mode !== "gateway")
|
|
1539
1551
|
return;
|
|
1540
1552
|
const config = context.config ?? {};
|
|
1541
1553
|
const port = config.port ?? 3002;
|
|
@@ -2016,6 +2028,10 @@ export async function main(argv) {
|
|
|
2016
2028
|
.option("--name <label>", "base label for the --count fan-out fleet names (default: cwd basename)")
|
|
2017
2029
|
.option("--browser", "add the headful-browser noVNC sidecar (WP7) — enables 2FA / authenticated-site flows from inside the pod")
|
|
2018
2030
|
.option("--image <ref>", "override the session-agent container image (fully-qualified registry ref, e.g. rg.fr-par.scw.cloud/sentropic-geo/geo-acquisition:0.1.0)")
|
|
2031
|
+
.option("--llm-gateway", "route this remote session through the llm-mesh gateway (explicit opt-in; default is direct)")
|
|
2032
|
+
.option("--gw", "alias for --llm-gateway")
|
|
2033
|
+
.option("--no-llm-gateway", "run this remote session without llm-mesh gateway env")
|
|
2034
|
+
.option("--no-gw", "alias for --no-llm-gateway")
|
|
2019
2035
|
.action(async (commandArgs, opts) => {
|
|
2020
2036
|
const { remote: remoteOverride, local, workspace, ...rest } = opts;
|
|
2021
2037
|
if (local) {
|
|
@@ -3701,6 +3717,8 @@ export async function main(argv) {
|
|
|
3701
3717
|
process.exitCode = 1;
|
|
3702
3718
|
return;
|
|
3703
3719
|
}
|
|
3720
|
+
const sameResumeIdentity = resolvedConvId !== undefined && registryEntry?.convId === resolvedConvId &&
|
|
3721
|
+
registryEntry?.cwd === process.cwd() && registryEntry?.tool === explicitProfile;
|
|
3704
3722
|
const entry = explicitProfile
|
|
3705
3723
|
? {
|
|
3706
3724
|
id: displaySlug,
|
|
@@ -3715,8 +3733,9 @@ export async function main(argv) {
|
|
|
3715
3733
|
enrolledAt: new Date().toISOString(),
|
|
3716
3734
|
lastSeenAt: new Date().toISOString(),
|
|
3717
3735
|
source: "run",
|
|
3718
|
-
bare:
|
|
3719
|
-
gatewayMode:
|
|
3736
|
+
bare: sameResumeIdentity ? registryEntry?.bare : undefined,
|
|
3737
|
+
gatewayMode: sameResumeIdentity ? registryEntry?.gatewayMode : undefined,
|
|
3738
|
+
gatewayProvenance: sameResumeIdentity ? registryEntry?.gatewayProvenance : undefined,
|
|
3720
3739
|
}
|
|
3721
3740
|
: registryEntry;
|
|
3722
3741
|
// WHICH host serves this resume — resolved BEFORE ANY act (F1): every
|
|
@@ -3814,7 +3833,7 @@ export async function main(argv) {
|
|
|
3814
3833
|
process.exitCode = 2;
|
|
3815
3834
|
return;
|
|
3816
3835
|
}
|
|
3817
|
-
const effectiveGatewayMode =
|
|
3836
|
+
const effectiveGatewayMode = resolveResumedGatewayMode(profile, gatewayMode, entry.gatewayMode, entry.gatewayProvenance);
|
|
3818
3837
|
let gateway;
|
|
3819
3838
|
// Resolve against the selected mode, not the parent's environment:
|
|
3820
3839
|
// required gateway injection below must succeed before either launch.
|
|
@@ -4027,7 +4046,11 @@ export async function main(argv) {
|
|
|
4027
4046
|
cwd: entry.cwd,
|
|
4028
4047
|
sessionClass,
|
|
4029
4048
|
...(entry.convId ? { convId: entry.convId } : {}),
|
|
4030
|
-
|
|
4049
|
+
// Only an explicit CLI choice becomes a new proven pin; otherwise
|
|
4050
|
+
// the previous pin (if any) is preserved untouched by the merge.
|
|
4051
|
+
...(gatewayMode !== "auto"
|
|
4052
|
+
? { gatewayMode, gatewayProvenance: "explicit" }
|
|
4053
|
+
: {}),
|
|
4031
4054
|
bare: useBare,
|
|
4032
4055
|
});
|
|
4033
4056
|
process.stderr.write(`[h2a] resumed local session ${resumeSlug} (${profile} resume${entry.convId ? ` ${entry.convId}` : ""} in ${entry.cwd})\n`);
|
|
@@ -4383,8 +4406,11 @@ export async function main(argv) {
|
|
|
4383
4406
|
for (const prunedId of resumeResolution.prunedIds) {
|
|
4384
4407
|
process.stderr.write(`[h2a] pruned dead registry row ${prunedId} that pinned conflicting launch options for this resume\n`);
|
|
4385
4408
|
}
|
|
4386
|
-
|
|
4387
|
-
|
|
4409
|
+
// Conflicting pins have already failed closed above. For agreeing
|
|
4410
|
+
// pins, prefer the proven choice over an equivalent legacy row.
|
|
4411
|
+
const pinnedLaunch = resumeResolution.entries.find(entry => entry.gatewayProvenance === "explicit")
|
|
4412
|
+
?? resumeResolution.entries[0];
|
|
4413
|
+
const launchGatewayMode = resolveResumedGatewayMode(profile, gatewayMode, pinnedLaunch?.gatewayMode, pinnedLaunch?.gatewayProvenance);
|
|
4388
4414
|
const command = localCliCommand(profile);
|
|
4389
4415
|
// A detached/background or run-once launch is a worker, even when its
|
|
4390
4416
|
// agent later emits a Claude SessionStart hook. Stamp this through tmux
|
|
@@ -4428,6 +4454,11 @@ export async function main(argv) {
|
|
|
4428
4454
|
return;
|
|
4429
4455
|
}
|
|
4430
4456
|
const useBare = resolveClaudeBare(profile, bareChoiceFromOptions(opts) ?? pinnedLaunch?.bare);
|
|
4457
|
+
// Preserve the conversation's bare pin, including absence on a
|
|
4458
|
+
// legacy row: materializing it only on the new row creates a conflict.
|
|
4459
|
+
const enrollmentBare = pinnedLaunch && bareChoiceFromOptions(opts) === undefined
|
|
4460
|
+
? pinnedLaunch.bare
|
|
4461
|
+
: useBare;
|
|
4431
4462
|
// H0 (#662): normalize readable gateway aliases (argon-high, wonder,
|
|
4432
4463
|
// wonder-full, spark) to their exact canonical model id. Unknown
|
|
4433
4464
|
// names pass through unchanged for exact-model validation downstream.
|
|
@@ -4755,8 +4786,8 @@ export async function main(argv) {
|
|
|
4755
4786
|
return;
|
|
4756
4787
|
}
|
|
4757
4788
|
// Auto-enroll in the live-session registry (feeds `h2a ls`/`restore`).
|
|
4758
|
-
//
|
|
4759
|
-
//
|
|
4789
|
+
// A CLI choice or a proven pin for this exact convId/cwd/tool
|
|
4790
|
+
// survives enrollment under a new name. Legacy/auto stays unpinned.
|
|
4760
4791
|
enrollFromRun({
|
|
4761
4792
|
profile,
|
|
4762
4793
|
slug,
|
|
@@ -4768,8 +4799,10 @@ export async function main(argv) {
|
|
|
4768
4799
|
cwd,
|
|
4769
4800
|
sessionClass,
|
|
4770
4801
|
...(opts.resume !== undefined ? { convId: opts.resume } : {}),
|
|
4771
|
-
gatewayMode
|
|
4772
|
-
|
|
4802
|
+
...(gatewayMode !== "auto" || pinnedLaunch?.gatewayProvenance === "explicit"
|
|
4803
|
+
? { gatewayMode: launchGatewayMode, gatewayProvenance: "explicit" }
|
|
4804
|
+
: {}),
|
|
4805
|
+
...(enrollmentBare !== undefined ? { bare: enrollmentBare } : {}),
|
|
4773
4806
|
});
|
|
4774
4807
|
const nativeOwnerSocket = launchOwnership?.host === "native"
|
|
4775
4808
|
? launchOwnership.sessions.find(owned => owned.name === name)?.socketPath : undefined;
|
|
@@ -6295,6 +6328,9 @@ export async function main(argv) {
|
|
|
6295
6328
|
...(entry.gatewayMode !== undefined
|
|
6296
6329
|
? { gatewayMode: entry.gatewayMode }
|
|
6297
6330
|
: {}),
|
|
6331
|
+
...(entry.gatewayProvenance !== undefined
|
|
6332
|
+
? { gatewayProvenance: entry.gatewayProvenance }
|
|
6333
|
+
: {}),
|
|
6298
6334
|
...(entry.bare !== undefined ? { bare: entry.bare } : {}),
|
|
6299
6335
|
}));
|
|
6300
6336
|
const result = await executeNativeRestart({
|
|
@@ -6349,7 +6385,9 @@ export async function main(argv) {
|
|
|
6349
6385
|
...(candidate.convId !== undefined
|
|
6350
6386
|
? { convId: candidate.convId }
|
|
6351
6387
|
: {}),
|
|
6352
|
-
...(mode !== "auto"
|
|
6388
|
+
...(mode !== "auto"
|
|
6389
|
+
? { gatewayMode: mode, gatewayProvenance: "explicit" }
|
|
6390
|
+
: {}),
|
|
6353
6391
|
...(candidate.bare !== undefined ? { bare: candidate.bare } : {}),
|
|
6354
6392
|
});
|
|
6355
6393
|
}
|
|
@@ -6462,7 +6500,7 @@ export async function main(argv) {
|
|
|
6462
6500
|
process.exitCode = 1;
|
|
6463
6501
|
return;
|
|
6464
6502
|
}
|
|
6465
|
-
const relaunchMode =
|
|
6503
|
+
const relaunchMode = resolveResumedGatewayMode(nativeEntry.tool, "auto", nativeEntry.gatewayMode, nativeEntry.gatewayProvenance);
|
|
6466
6504
|
await injectLlmMeshGatewayEnv(relaunchMode, relaunchMode !== "gateway", actName);
|
|
6467
6505
|
if (!killNativeSessionTree(actName)) {
|
|
6468
6506
|
process.stderr.write(`[h2a] native session ${slug} could not be killed; nothing was relaunched.\n`);
|
|
@@ -6489,7 +6527,15 @@ export async function main(argv) {
|
|
|
6489
6527
|
...(relaunched.pid !== undefined ? { pid: relaunched.pid } : {}),
|
|
6490
6528
|
cwd: nativeEntry.cwd,
|
|
6491
6529
|
sessionClass: nativeEntry.sessionClass ?? "human",
|
|
6492
|
-
|
|
6530
|
+
// No new CLI choice here: preserve the recorded pin verbatim so a
|
|
6531
|
+
// proven choice survives the relaunch and a legacy row keeps its
|
|
6532
|
+
// (unproven) data without gaining authority.
|
|
6533
|
+
...(nativeEntry.gatewayMode !== undefined
|
|
6534
|
+
? { gatewayMode: nativeEntry.gatewayMode }
|
|
6535
|
+
: {}),
|
|
6536
|
+
...(nativeEntry.gatewayProvenance !== undefined
|
|
6537
|
+
? { gatewayProvenance: nativeEntry.gatewayProvenance }
|
|
6538
|
+
: {}),
|
|
6493
6539
|
bare: useBare,
|
|
6494
6540
|
...(nativeEntry.convId ? { convId: nativeEntry.convId } : {}),
|
|
6495
6541
|
});
|
|
@@ -6619,14 +6665,55 @@ export async function main(argv) {
|
|
|
6619
6665
|
if (!forced) {
|
|
6620
6666
|
let ok = 0;
|
|
6621
6667
|
for (const action of plan.actions) {
|
|
6622
|
-
|
|
6623
|
-
|
|
6624
|
-
|
|
6625
|
-
|
|
6626
|
-
|
|
6627
|
-
|
|
6628
|
-
|
|
6629
|
-
|
|
6668
|
+
// The pane is NOT recreated, so apply the routing policy to the
|
|
6669
|
+
// retyped command itself: resolve the session's consent, scrub the
|
|
6670
|
+
// routing vars for direct, or pass the freshly acquired gateway
|
|
6671
|
+
// route: the idle shell may carry an expired bearer or no route.
|
|
6672
|
+
const retypeEntry = entryByTmuxSession.get(action.name);
|
|
6673
|
+
const retypeMode = resolveResumedGatewayMode(action.profile, "auto", retypeEntry?.gatewayMode, retypeEntry?.gatewayProvenance);
|
|
6674
|
+
let retypeCmd = action.cmd;
|
|
6675
|
+
let cleanupRetypeEnv = () => { };
|
|
6676
|
+
if (retypeMode === "gateway") {
|
|
6677
|
+
// Validate the explicit choice before retyping: fail closed when
|
|
6678
|
+
// the gateway is unavailable instead of typing a doomed command.
|
|
6679
|
+
// Hand off privately before restoring this process's environment.
|
|
6680
|
+
const restoreRetypeEnv = replaceAnthropicGatewayEnvironment(process.env, captureAnthropicGatewayEnvironment());
|
|
6681
|
+
try {
|
|
6682
|
+
await injectLlmMeshGatewayEnv("gateway", false, localSessionName(action.slug));
|
|
6683
|
+
const prepared = prepareGatewayRetypeCommand(action.cmd, {
|
|
6684
|
+
ANTHROPIC_BASE_URL: process.env.ANTHROPIC_BASE_URL,
|
|
6685
|
+
ANTHROPIC_AUTH_TOKEN: process.env.ANTHROPIC_AUTH_TOKEN,
|
|
6686
|
+
});
|
|
6687
|
+
retypeCmd = prepared.command;
|
|
6688
|
+
cleanupRetypeEnv = prepared.cleanup;
|
|
6689
|
+
}
|
|
6690
|
+
catch (error) {
|
|
6691
|
+
process.stderr.write(`[h2a] FAILED to relaunch ${action.slug}: ${error.message}\n`);
|
|
6692
|
+
continue;
|
|
6693
|
+
}
|
|
6694
|
+
finally {
|
|
6695
|
+
restoreRetypeEnv();
|
|
6696
|
+
}
|
|
6697
|
+
}
|
|
6698
|
+
else {
|
|
6699
|
+
retypeCmd = scrubbedRetypeCommand(action.cmd);
|
|
6700
|
+
}
|
|
6701
|
+
const sessionId = retypeEntry?.id;
|
|
6702
|
+
let relaunched = false;
|
|
6703
|
+
try {
|
|
6704
|
+
relaunched = sessionId !== undefined
|
|
6705
|
+
? relaunchSessionWithLease({
|
|
6706
|
+
sessionId,
|
|
6707
|
+
tmuxSession: action.name,
|
|
6708
|
+
command: retypeCmd,
|
|
6709
|
+
})
|
|
6710
|
+
: relaunchInSession(action.name, retypeCmd);
|
|
6711
|
+
}
|
|
6712
|
+
finally {
|
|
6713
|
+
// A successful retype transfers cleanup to the private reader.
|
|
6714
|
+
if (!relaunched)
|
|
6715
|
+
cleanupRetypeEnv();
|
|
6716
|
+
}
|
|
6630
6717
|
if (relaunched) {
|
|
6631
6718
|
ok += 1;
|
|
6632
6719
|
process.stderr.write(`[h2a] relaunched ${action.slug}: ${action.cmd}\n`);
|
|
@@ -6702,6 +6789,11 @@ export async function main(argv) {
|
|
|
6702
6789
|
}
|
|
6703
6790
|
finalReady.push(item);
|
|
6704
6791
|
}
|
|
6792
|
+
// Isolate the routing env per session: a gateway relaunch deletes the
|
|
6793
|
+
// native ANTHROPIC_API_KEY from the shared process env, so without a
|
|
6794
|
+
// per-iteration restore the next (direct) session would lose native
|
|
6795
|
+
// auth. Every iteration starts from the loop-entry environment.
|
|
6796
|
+
const loopEntryGatewayEnv = captureAnthropicGatewayEnvironment();
|
|
6705
6797
|
let ok = 0;
|
|
6706
6798
|
for (const { action, entry, sessionClass } of finalReady) {
|
|
6707
6799
|
const safety = sessionRelaunchSafety(action.name);
|
|
@@ -6728,8 +6820,9 @@ export async function main(argv) {
|
|
|
6728
6820
|
process.stderr.write(`[h2a] skipped ${action.slug}: tmux session ${action.name} changed or was no longer proven dead immediately before kill.\n`);
|
|
6729
6821
|
continue;
|
|
6730
6822
|
}
|
|
6823
|
+
const restoreLoopEnv = replaceAnthropicGatewayEnvironment(process.env, loopEntryGatewayEnv);
|
|
6731
6824
|
try {
|
|
6732
|
-
const gatewayMode =
|
|
6825
|
+
const gatewayMode = resolveResumedGatewayMode(action.profile, "auto", entry.gatewayMode, entry.gatewayProvenance);
|
|
6733
6826
|
await injectLlmMeshGatewayEnv(gatewayMode, true, localSessionName(action.slug));
|
|
6734
6827
|
const { name, agentPane } = startLocalSession(action.profile, action.command, entry.cwd, localResumeArgs(action.profile, action.convId, {
|
|
6735
6828
|
bare: resolveClaudeBare(action.profile, entry.bare),
|
|
@@ -6751,6 +6844,9 @@ export async function main(argv) {
|
|
|
6751
6844
|
...(entry.gatewayMode !== undefined
|
|
6752
6845
|
? { gatewayMode: entry.gatewayMode }
|
|
6753
6846
|
: {}),
|
|
6847
|
+
...(entry.gatewayProvenance !== undefined
|
|
6848
|
+
? { gatewayProvenance: entry.gatewayProvenance }
|
|
6849
|
+
: {}),
|
|
6754
6850
|
...(entry.bare !== undefined ? { bare: entry.bare } : {}),
|
|
6755
6851
|
});
|
|
6756
6852
|
const wake = wakeRelaunchedSession(relaunchContinuationPrompt(entry.task), {
|
|
@@ -6783,6 +6879,9 @@ export async function main(argv) {
|
|
|
6783
6879
|
catch (error) {
|
|
6784
6880
|
process.stderr.write(`[h2a] FAILED to force-restart ${action.slug} after killing ${action.name}: ${error.message}\n`);
|
|
6785
6881
|
}
|
|
6882
|
+
finally {
|
|
6883
|
+
restoreLoopEnv();
|
|
6884
|
+
}
|
|
6786
6885
|
}
|
|
6787
6886
|
process.stderr.write(`[h2a] force-restarted ${ok}/${plan.actions.length}${plan.skipped.length ? `, ${plan.skipped.length} skipped` : ""}\n`);
|
|
6788
6887
|
if (ok !== plan.actions.length)
|
|
@@ -7146,13 +7245,13 @@ export async function main(argv) {
|
|
|
7146
7245
|
}
|
|
7147
7246
|
if (opts.reattach)
|
|
7148
7247
|
restoreOpts.reattach = true;
|
|
7149
|
-
//
|
|
7150
|
-
//
|
|
7151
|
-
if (forceGateway
|
|
7248
|
+
// Only explicit --gw prewarms globally. In auto mode, any pinned
|
|
7249
|
+
// gateway session acquires its gateway through its own launch.
|
|
7250
|
+
if (forceGateway === "gateway") {
|
|
7152
7251
|
try {
|
|
7153
7252
|
await prepareLlmMeshForRestore({
|
|
7154
7253
|
dryRun: Boolean(opts.dryRun),
|
|
7155
|
-
mode: forceGateway
|
|
7254
|
+
mode: forceGateway,
|
|
7156
7255
|
});
|
|
7157
7256
|
}
|
|
7158
7257
|
catch (error) {
|
|
@@ -7851,17 +7950,15 @@ export async function main(argv) {
|
|
|
7851
7950
|
const config = readLlmMeshConfig() ?? {};
|
|
7852
7951
|
const existing = readGatewayPid();
|
|
7853
7952
|
if (existing) {
|
|
7854
|
-
|
|
7953
|
+
// Starting the service is not routing consent: launches stay direct
|
|
7954
|
+
// unless --gw is passed, so the durable flag is left untouched.
|
|
7855
7955
|
process.stdout.write(`[h2a] llm-mesh: already running (pid ${existing})\n`);
|
|
7856
|
-
process.stdout.write(`[h2a] llm-mesh: restore config enabled\n`);
|
|
7857
7956
|
return;
|
|
7858
7957
|
}
|
|
7859
7958
|
process.stdout.write(`[h2a] llm-mesh: starting gateway…\n`);
|
|
7860
7959
|
const result = await startGateway(config, { verbose: opts.verbose });
|
|
7861
|
-
setLlmMeshRuntimeConfig({ enabled: true });
|
|
7862
7960
|
const port = result.port;
|
|
7863
7961
|
process.stdout.write(`[h2a] llm-mesh: gateway started (pid ${result.pid}, port ${port})\n`);
|
|
7864
|
-
process.stdout.write(`[h2a] llm-mesh: restore config enabled\n`);
|
|
7865
7962
|
process.stdout.write("[h2a] llm-mesh: use `h2a run claude --gw`; the opaque bearer is not printed\n");
|
|
7866
7963
|
});
|
|
7867
7964
|
llmMeshCommand
|
|
@@ -7889,6 +7986,9 @@ export async function main(argv) {
|
|
|
7889
7986
|
else {
|
|
7890
7987
|
process.stdout.write(`[h2a] llm-mesh: not running\n`);
|
|
7891
7988
|
}
|
|
7989
|
+
// A previous start must not keep authorizing reactivation: stopping the
|
|
7990
|
+
// service clears the durable flag so a later restore stays direct.
|
|
7991
|
+
setLlmMeshRuntimeConfig({ enabled: false });
|
|
7892
7992
|
});
|
|
7893
7993
|
llmMeshCommand
|
|
7894
7994
|
.command("restart")
|
|
@@ -7905,9 +8005,9 @@ export async function main(argv) {
|
|
|
7905
8005
|
}
|
|
7906
8006
|
process.stdout.write(`[h2a] llm-mesh: starting gateway…\n`);
|
|
7907
8007
|
const result = await startGateway(config, { verbose: opts.verbose });
|
|
7908
|
-
|
|
8008
|
+
// Restarting the service is not routing consent: the durable flag is
|
|
8009
|
+
// left untouched so a later restore stays direct unless --gw is passed.
|
|
7909
8010
|
process.stdout.write(`[h2a] llm-mesh: gateway restarted (pid ${result.pid}, port ${result.port})\n`);
|
|
7910
|
-
process.stdout.write(`[h2a] llm-mesh: restore config enabled\n`);
|
|
7911
8011
|
process.stdout.write(`[h2a] llm-mesh: no tmux/Claude session was restarted; active sessions may keep their old gateway token until explicitly relaunched.\n`);
|
|
7912
8012
|
process.stdout.write("[h2a] llm-mesh: use `h2a run claude --gw`; the opaque bearer is not printed\n");
|
|
7913
8013
|
});
|