@sentropic/h2a-runtime 0.98.7-rc.1 → 0.98.7-rc.10
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/agent-launch-args.d.ts.map +1 -1
- package/dist/agent-launch-args.js +5 -5
- package/dist/agent-launch-args.js.map +1 -1
- package/dist/agy-claude.d.ts +42 -0
- package/dist/agy-claude.d.ts.map +1 -0
- package/dist/agy-claude.js +148 -0
- package/dist/agy-claude.js.map +1 -0
- package/dist/agy-headless.d.ts +11 -0
- package/dist/agy-headless.d.ts.map +1 -0
- package/dist/agy-headless.js +104 -0
- package/dist/agy-headless.js.map +1 -0
- 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/central-mcp.d.ts +32 -0
- package/dist/central-mcp.d.ts.map +1 -1
- package/dist/central-mcp.js +186 -69
- package/dist/central-mcp.js.map +1 -1
- package/dist/claude-code-cli-isolation.d.ts +53 -0
- package/dist/claude-code-cli-isolation.d.ts.map +1 -0
- package/dist/claude-code-cli-isolation.js +277 -0
- package/dist/claude-code-cli-isolation.js.map +1 -0
- package/dist/claude-code-cli-lifecycle.d.ts +80 -0
- package/dist/claude-code-cli-lifecycle.d.ts.map +1 -0
- package/dist/claude-code-cli-lifecycle.js +402 -0
- package/dist/claude-code-cli-lifecycle.js.map +1 -0
- package/dist/claude-code-cli-runner.d.ts +89 -0
- package/dist/claude-code-cli-runner.d.ts.map +1 -0
- package/dist/claude-code-cli-runner.js +659 -0
- package/dist/claude-code-cli-runner.js.map +1 -0
- package/dist/claude-seat-input.d.ts +47 -0
- package/dist/claude-seat-input.d.ts.map +1 -0
- package/dist/claude-seat-input.js +272 -0
- package/dist/claude-seat-input.js.map +1 -0
- package/dist/claude-seat-mesh-contract.d.ts +88 -0
- package/dist/claude-seat-mesh-contract.d.ts.map +1 -0
- package/dist/claude-seat-mesh-contract.js +13 -0
- package/dist/claude-seat-mesh-contract.js.map +1 -0
- package/dist/cli-seat-codecs.d.ts +86 -0
- package/dist/cli-seat-codecs.d.ts.map +1 -0
- package/dist/cli-seat-codecs.js +255 -0
- package/dist/cli-seat-codecs.js.map +1 -0
- package/dist/cli-seat-fixtures.d.ts +86 -0
- package/dist/cli-seat-fixtures.d.ts.map +1 -0
- package/dist/cli-seat-fixtures.js +114 -0
- package/dist/cli-seat-fixtures.js.map +1 -0
- package/dist/cli-seat-materialization.d.ts +245 -0
- package/dist/cli-seat-materialization.d.ts.map +1 -0
- package/dist/cli-seat-materialization.js +646 -0
- package/dist/cli-seat-materialization.js.map +1 -0
- package/dist/conv-guard.d.ts.map +1 -1
- package/dist/conv-guard.js +6 -2
- package/dist/conv-guard.js.map +1 -1
- package/dist/delegate.d.ts +10 -0
- package/dist/delegate.d.ts.map +1 -1
- package/dist/delegate.js +29 -0
- package/dist/delegate.js.map +1 -1
- package/dist/file-lock.d.ts +4 -2
- package/dist/file-lock.d.ts.map +1 -1
- package/dist/file-lock.js +24 -10
- package/dist/file-lock.js.map +1 -1
- package/dist/gateway-host/claude-seat-client.d.ts +32 -0
- package/dist/gateway-host/claude-seat-client.d.ts.map +1 -0
- package/dist/gateway-host/claude-seat-client.js +38 -0
- package/dist/gateway-host/claude-seat-client.js.map +1 -0
- package/dist/gateway-host/config-file.d.ts +2 -0
- package/dist/gateway-host/config-file.d.ts.map +1 -1
- package/dist/gateway-host/config-file.js +5 -1
- package/dist/gateway-host/config-file.js.map +1 -1
- package/dist/gateway-host/daemon.d.ts.map +1 -1
- package/dist/gateway-host/daemon.js +1 -2
- package/dist/gateway-host/daemon.js.map +1 -1
- package/dist/gateway-host/host.d.ts +6 -0
- package/dist/gateway-host/host.d.ts.map +1 -1
- package/dist/gateway-host/host.js +24 -3
- package/dist/gateway-host/host.js.map +1 -1
- package/dist/gateway-host/live-auth.d.ts +62 -0
- package/dist/gateway-host/live-auth.d.ts.map +1 -0
- package/dist/gateway-host/live-auth.js +87 -0
- package/dist/gateway-host/live-auth.js.map +1 -0
- package/dist/gateway-host/live-declarations.d.ts.map +1 -1
- package/dist/gateway-host/live-declarations.js +13 -7
- package/dist/gateway-host/live-declarations.js.map +1 -1
- package/dist/gateway-host/live-disclosure.d.ts +124 -0
- package/dist/gateway-host/live-disclosure.d.ts.map +1 -0
- package/dist/gateway-host/live-disclosure.js +347 -0
- package/dist/gateway-host/live-disclosure.js.map +1 -0
- package/dist/gateway-host/live-github.d.ts +11 -0
- package/dist/gateway-host/live-github.d.ts.map +1 -0
- package/dist/gateway-host/live-github.js +374 -0
- package/dist/gateway-host/live-github.js.map +1 -0
- package/dist/gateway-host/live-host.d.ts +20 -0
- package/dist/gateway-host/live-host.d.ts.map +1 -0
- package/dist/gateway-host/live-host.js +14 -0
- package/dist/gateway-host/live-host.js.map +1 -0
- package/dist/gateway-host/live-object-store.d.ts +35 -0
- package/dist/gateway-host/live-object-store.d.ts.map +1 -0
- package/dist/gateway-host/live-object-store.js +447 -0
- package/dist/gateway-host/live-object-store.js.map +1 -0
- package/dist/gateway-model-catalog/model-catalog.d.ts +38 -0
- package/dist/gateway-model-catalog/model-catalog.d.ts.map +1 -0
- package/dist/gateway-model-catalog/model-catalog.js +167 -0
- package/dist/gateway-model-catalog/model-catalog.js.map +1 -0
- package/dist/identity-cull/cull.js +1 -1
- package/dist/identity-cull/cull.js.map +1 -1
- package/dist/identity-cull/identity-fence.d.ts +48 -0
- package/dist/identity-cull/identity-fence.d.ts.map +1 -0
- package/dist/identity-cull/identity-fence.js +221 -0
- package/dist/identity-cull/identity-fence.js.map +1 -0
- package/dist/identity-operator-break.d.ts +43 -0
- package/dist/identity-operator-break.d.ts.map +1 -0
- package/dist/identity-operator-break.js +85 -0
- package/dist/identity-operator-break.js.map +1 -0
- package/dist/index.d.ts +8 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +446 -131
- 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/llm-mesh-accounts.d.ts +128 -6
- package/dist/llm-mesh-accounts.d.ts.map +1 -1
- package/dist/llm-mesh-accounts.js +370 -14
- package/dist/llm-mesh-accounts.js.map +1 -1
- package/dist/native-host.d.ts +25 -1
- package/dist/native-host.d.ts.map +1 -1
- package/dist/native-host.js +184 -23
- 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/native-terminal/fleet.d.ts +11 -0
- package/dist/native-terminal/fleet.d.ts.map +1 -1
- package/dist/native-terminal/fleet.js +58 -1
- package/dist/native-terminal/fleet.js.map +1 -1
- package/dist/native-terminal/host-scope.d.ts +18 -0
- package/dist/native-terminal/host-scope.d.ts.map +1 -0
- package/dist/native-terminal/host-scope.js +65 -0
- package/dist/native-terminal/host-scope.js.map +1 -0
- package/dist/native-terminal/host.d.ts +7 -1
- package/dist/native-terminal/host.d.ts.map +1 -1
- package/dist/native-terminal/host.js +10 -26
- package/dist/native-terminal/host.js.map +1 -1
- package/dist/native-terminal/op.d.ts +36 -1
- package/dist/native-terminal/op.d.ts.map +1 -1
- package/dist/native-terminal/op.js +190 -33
- package/dist/native-terminal/op.js.map +1 -1
- package/dist/native-terminal/process.d.ts.map +1 -1
- package/dist/native-terminal/process.js +10 -1
- package/dist/native-terminal/process.js.map +1 -1
- package/dist/native-terminal/screen.d.ts.map +1 -1
- package/dist/native-terminal/screen.js +1 -1
- package/dist/native-terminal/screen.js.map +1 -1
- package/dist/native-terminal/server.d.ts +12 -2
- package/dist/native-terminal/server.d.ts.map +1 -1
- package/dist/native-terminal/server.js +143 -42
- package/dist/native-terminal/server.js.map +1 -1
- package/dist/native-terminal/session-scope.d.ts +2 -0
- package/dist/native-terminal/session-scope.d.ts.map +1 -0
- package/dist/native-terminal/session-scope.js +32 -0
- package/dist/native-terminal/session-scope.js.map +1 -0
- package/dist/proc-identity.d.ts +31 -0
- package/dist/proc-identity.d.ts.map +1 -1
- package/dist/proc-identity.js +54 -0
- package/dist/proc-identity.js.map +1 -1
- package/dist/prompt-delivery.d.ts +3 -0
- package/dist/prompt-delivery.d.ts.map +1 -1
- package/dist/prompt-delivery.js +8 -3
- package/dist/prompt-delivery.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 +16 -12
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +39 -5
- 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 +9 -37
- 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 +9 -4
- 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 +31 -5
- package/dist/tmux.d.ts.map +1 -1
- package/dist/tmux.js +96 -20
- package/dist/tmux.js.map +1 -1
- package/dist/vendor/engram-live/contracts.d.ts +619 -2
- package/dist/vendor/engram-live/contracts.js +1201 -25
- package/dist/vendor/engram-live/integration.d.ts +44 -4
- package/dist/vendor/engram-live/integration.js +65 -5
- package/dist/vendor/engram-live/pr-sync.d.ts +144 -0
- package/dist/vendor/engram-live/pr-sync.js +411 -0
- package/dist/vendor/engram-live/registration.d.ts +2 -2
- package/dist/vendor/engram-live/registration.js +2 -4
- package/dist/vendor/engram-live/retention.d.ts +56 -0
- package/dist/vendor/engram-live/retention.js +179 -0
- package/package.json +5 -5
package/dist/index.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
3
|
import { spawnSync } from "node:child_process";
|
|
4
|
-
import { existsSync, mkdirSync, readdirSync, readFileSync, realpathSync, statSync, writeFileSync, } from "node:fs";
|
|
4
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, realpathSync, rmSync, statSync, writeFileSync, } from "node:fs";
|
|
5
5
|
import { homedir, tmpdir } from "node:os";
|
|
6
6
|
import { basename, dirname, isAbsolute, join, resolve } from "node:path";
|
|
7
7
|
import { pathToFileURL } from "node:url";
|
|
8
8
|
import { Command, Help } from "commander";
|
|
9
|
+
import { resolveSeatEnrollmentProvider } from "./cli-seat-codecs.js";
|
|
10
|
+
import { agyClaudeQuotaError, assertAgyClaudeBuildAllowed, detectAgyClaudeQuotaExhaustion, isAgyClaudeModel, normalizeAgyEffort, readAgyClaudeQuotaStatus, resolveAgyModel, writeAgyClaudeQuotaStatus } from "./agy-claude.js";
|
|
9
11
|
import { applyRuntimeHelpGroups, groupRuntimeHelpItems, } from "./cli-help-groups.js";
|
|
10
12
|
import { attach, createRemoteSession, getRemoteSession, listRemoteSessions, refreshRemoteSession, renameRemoteSession, sessionTerminalHealth, stopRemoteSession, } from "./attach.js";
|
|
11
13
|
import { authHeaders, clearDefaultRemote, getDefaultRemote, getDefaultTarget, getDefaultTools, getH2aConfig, getJobMaxAgeHours, getMaxConcurrent, getLlmMeshRuntimeConfig, getTmuxProfileConfig, getTunnel, resolveConfigPath, setDefaultRemote, setDefaultTarget, migrateConfigHomeIfNeeded, setDefaultTools, setLlmMeshRuntimeConfig, setTmuxProfileConfig, setToken, setTunnel, } from "./config.js";
|
|
@@ -22,13 +24,14 @@ import { attachLocalSession, attachPodTmux, capturePane, capturePaneVisible, cle
|
|
|
22
24
|
import { deliverInitialPrompt, sleepSync, } from "./prompt-delivery.js";
|
|
23
25
|
import { startLaunchGuard } from "./launch-guard.js";
|
|
24
26
|
import { buildLaunchContext } from "./launch-context.js";
|
|
25
|
-
import { attachNativeSession, driveNativeInstruction, killNativeSessionIfIncarnation, killNativeSessionTree, nativeHostAvailable, nativePromptDeliveryDeps, nativeSessionLiveness, nativeSessionPid, nativeSessionState, nativeTreeCpuMs, nativeWorkerPid, preflightNativeLaunch, resolveSessionHostKind, startNativeH2aSidecar, startNativeHeadlessSession, startNativeSession, NativeHostCapabilityMismatchError, NativeLaunchAdmissionError, } from "./native-host.js";
|
|
27
|
+
import { attachNativeSession, driveNativeInstruction, killNativeSessionIfIncarnation, killNativeSessionTree, nativeHostAvailable, nativePromptDeliveryDeps, nativeSessionLiveness, nativeSessionPid, nativeSessionState, nativeSessionStateAsync, nativeTreeCpuMs, nativeWorkerPid, preflightNativeLaunch, resolveSessionHostKind, startNativeH2aSidecar, startNativeHeadlessSession, startNativeSession, startNativeSessionSingleShot, NativeHostCapabilityMismatchError, NativeLaunchAdmissionError, } from "./native-host.js";
|
|
26
28
|
import { executeNativeRestart, NativeRestartError, } from "./native-restart.js";
|
|
27
29
|
import { migrateTmuxNames } from "./tmux-name-migration.js";
|
|
28
30
|
import { projectStatusForH2a } from "./status-projection.js";
|
|
29
31
|
import { buildAgentLaunchArgs, buildAgentLaunchStdin, isAgentLaunchEffort, isAgentLaunchProfile, } from "./agent-launch-args.js";
|
|
32
|
+
import { resolveGatewayModelAlias } from "./gateway-model-catalog/model-catalog.js";
|
|
30
33
|
import { deriveSessionClass } from "./session-class.js";
|
|
31
|
-
import { isRelaunchKillable, planRelaunch, relaunchContinuationPrompt, tryAcquireResumeLaunchClaim, wakeRelaunchedSession, } from "./relaunch.js";
|
|
34
|
+
import { isRelaunchKillable, planRelaunch, relaunchContinuationPrompt, scrubbedRetypeCommand, prepareGatewayRetypeCommand, tryAcquireResumeLaunchClaim, wakeRelaunchedSession, } from "./relaunch.js";
|
|
32
35
|
import { isHumanFacingSession, legacySessionEvidence, readConversationCustomTitle, readLastLayout, reconcileRunConvIds, restore as restoreLayout, } from "./restore.js";
|
|
33
36
|
import { getLayoutConfig } from "./config.js";
|
|
34
37
|
import { resolvePinnedLaunchEntries } from "./resume-launch-options.js";
|
|
@@ -42,7 +45,11 @@ import { DEFAULT_FANOUT_MAX, mapWithConcurrency, planRemoteFanout, } from "./fan
|
|
|
42
45
|
import { authenticateJobEnvelopes, buildDecisionReply, dropEnvelope, emitJobDone, envelopeFileName, isAwaitingDecision, jobInstance, parentInstance, pendingDecisions, readInboxEnvelopes, renderPendingDecisions, repliedDecisionJobIds, } from "./h2a-jobs.js";
|
|
43
46
|
import { buildConductorTask, computeDurableWorkspaceId, normalizeRootCommits, detectAvailableHosts, freshestLaunchEnvelope, h2aReportsLiveConductor, markLaunchEnvelopeProcessed, readLastLaunchAt, readLaunchEnvelopes, recordLaunchAt, selectHost, shouldLaunch, } from "./conductor-launch.js";
|
|
44
47
|
import { defaultLocalH2aRoot } from "./h2a-bridge.js";
|
|
45
|
-
import {
|
|
48
|
+
import { operatorBreak as operatorBreakToCore } from "./identity-operator-break.js";
|
|
49
|
+
// identity-cull is imported lazily at its verb below: cull.js needs the
|
|
50
|
+
// core identity fence (`@sentropic/h2a`), which must never become a
|
|
51
|
+
// load-time edge of this barrel — isolated installs of this package have
|
|
52
|
+
// no core beside them.
|
|
46
53
|
import { guardConvWriters } from "./conv-guard.js";
|
|
47
54
|
import { handleClaudeHook, installClaudeHooks, manualEnroll, readStdin, } from "./enroll.js";
|
|
48
55
|
import { probePodCredHealth, softRefreshSession } from "./soft-refresh.js";
|
|
@@ -74,8 +81,7 @@ import { createInterface } from "node:readline";
|
|
|
74
81
|
const H2A_RUN_API_VERSION = "h2a.run/v1";
|
|
75
82
|
const STRUCTURED_LAUNCH_PHASE_PREFIX = "[h2a] h2a.run.phase/v1 ";
|
|
76
83
|
const H2A_RUNTIME_VERSION = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")).version;
|
|
77
|
-
import { CLI_PROFILES, gatewayModeForProfile, profileUsesLlmMeshGateway, } from "./protocol-local.js";
|
|
78
|
-
import { enrollViaFacade, formatLlmMeshAccountError, formatLlmMeshAccountList, listAccountsViaFacade, removeAccountViaFacade, } from "./llm-mesh-accounts.js";
|
|
84
|
+
import { CLI_PROFILES, gatewayModeForProfile, profileUsesLlmMeshGateway, resolveResumedGatewayMode, } from "./protocol-local.js";
|
|
79
85
|
import { readLlmMeshConfig, updateLlmMeshRoutingConfig, } from "./gateway-host/config-file.js";
|
|
80
86
|
import { startGateway, stopGateway, readGatewayPid, llmMeshLogPath, replaceAnthropicGatewayEnvironment, acquireLlmMeshSessionEnv, } from "./gateway-host/daemon.js";
|
|
81
87
|
import { describeLlmMeshRoutingConfig, parseLlmMeshRoutingConfig, preferredRoutingConfig, strategyRoutingConfig, } from "./routing-preferences.js";
|
|
@@ -135,6 +141,7 @@ async function startRemoteFanout(remote, profileName, members, spec) {
|
|
|
135
141
|
? { startupArgs: spec.startupArgs }
|
|
136
142
|
: {}),
|
|
137
143
|
...(spec.credentials ? { credentials: spec.credentials } : {}),
|
|
144
|
+
...(spec.llmGateway === true ? { llmGateway: true } : {}),
|
|
138
145
|
});
|
|
139
146
|
return { name: member.name, sessionId: session.id, workspaceId: ws.id };
|
|
140
147
|
});
|
|
@@ -200,6 +207,20 @@ async function runProfile(profileName, opts, commandArgs = []) {
|
|
|
200
207
|
if (opts.resume !== undefined && resumeArgs.length === 0) {
|
|
201
208
|
throw new Error(`profile "${profileName}" has no verified resume argv; start it without -r/--resume`);
|
|
202
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 } : {};
|
|
203
224
|
const startupArgs = [...resumeArgs, ...commandArgs];
|
|
204
225
|
// WP6 — REMOTE fan-out: --count N spawns N concurrent remote sessions, each
|
|
205
226
|
// on its OWN workspace subPath of the shared RWX volume. count<=1 falls
|
|
@@ -229,6 +250,7 @@ async function runProfile(profileName, opts, commandArgs = []) {
|
|
|
229
250
|
target: opts.target,
|
|
230
251
|
startupArgs,
|
|
231
252
|
...(credentials ? { credentials } : {}),
|
|
253
|
+
...remoteGatewayChoice,
|
|
232
254
|
});
|
|
233
255
|
return;
|
|
234
256
|
}
|
|
@@ -247,6 +269,7 @@ async function runProfile(profileName, opts, commandArgs = []) {
|
|
|
247
269
|
...(opts.workspaceId ? { workspaceId: opts.workspaceId } : {}),
|
|
248
270
|
...(opts.browser ? { metadata: { browser: true } } : {}),
|
|
249
271
|
...(opts.agentImage ? { agentImage: opts.agentImage } : {}),
|
|
272
|
+
...remoteGatewayChoice,
|
|
250
273
|
});
|
|
251
274
|
if (archive) {
|
|
252
275
|
await uploadWorkspaceArchive(opts.remote, session.id, archive);
|
|
@@ -270,7 +293,21 @@ async function runProfile(profileName, opts, commandArgs = []) {
|
|
|
270
293
|
...(opts.port !== undefined ? { port: opts.port } : {}),
|
|
271
294
|
...(commandArgs.length > 0 ? { startupArgs: commandArgs } : {}),
|
|
272
295
|
};
|
|
273
|
-
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
|
+
}
|
|
274
311
|
process.stderr.write(`[h2a] session ${result.sessionId} attach at http://127.0.0.1:${result.port}\n`);
|
|
275
312
|
const { exitCode } = await result.exit;
|
|
276
313
|
process.exitCode = exitCode;
|
|
@@ -852,32 +889,12 @@ export async function startJob(job) {
|
|
|
852
889
|
const prevJobId = process.env[JOB_ID_ENV];
|
|
853
890
|
process.env[DEPTH_ENV] = childDepthEnvValue(job.depthBudget ?? clampDepth(undefined));
|
|
854
891
|
process.env[JOB_ID_ENV] = job.id;
|
|
855
|
-
//
|
|
856
|
-
//
|
|
857
|
-
//
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
const
|
|
861
|
-
? jobSessionResolution.session.name
|
|
862
|
-
: jobSessionResolution.kind === "missing"
|
|
863
|
-
? localSessionName(jobSessionSlug)
|
|
864
|
-
: undefined;
|
|
865
|
-
let meshEnv;
|
|
866
|
-
try {
|
|
867
|
-
meshEnv =
|
|
868
|
-
profileUsesLlmMeshGateway(job.tool) &&
|
|
869
|
-
getLlmMeshRuntimeConfig().enabled &&
|
|
870
|
-
gatewayClientSessionId
|
|
871
|
-
? await acquireLlmMeshSessionEnv(undefined, gatewayClientSessionId)
|
|
872
|
-
: null;
|
|
873
|
-
}
|
|
874
|
-
catch (error) {
|
|
875
|
-
return {
|
|
876
|
-
started: false,
|
|
877
|
-
error: error instanceof Error ? error.message : String(error),
|
|
878
|
-
};
|
|
879
|
-
}
|
|
880
|
-
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);
|
|
881
898
|
let tmuxSession;
|
|
882
899
|
let workerPid;
|
|
883
900
|
let agentPane;
|
|
@@ -1399,13 +1416,12 @@ export function bareChoiceFromOptions(opts) {
|
|
|
1399
1416
|
return false;
|
|
1400
1417
|
return undefined;
|
|
1401
1418
|
}
|
|
1402
|
-
async function injectLlmMeshGatewayEnv(mode = "auto", allowDirectFallback = true, clientSessionId) {
|
|
1403
|
-
if (mode
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
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.
|
|
1409
1425
|
delete process.env.ANTHROPIC_BASE_URL;
|
|
1410
1426
|
delete process.env.ANTHROPIC_AUTH_TOKEN;
|
|
1411
1427
|
return undefined;
|
|
@@ -1436,7 +1452,7 @@ async function injectLlmMeshGatewayEnv(mode = "auto", allowDirectFallback = true
|
|
|
1436
1452
|
delete process.env.ANTHROPIC_BASE_URL;
|
|
1437
1453
|
delete process.env.ANTHROPIC_AUTH_TOKEN;
|
|
1438
1454
|
if (mode === "gateway") {
|
|
1439
|
-
throw new Error("llm-mesh gateway is required but unavailable; no agent was started. Run `h2a llm-mesh start` or `h2a llm-mesh account enroll codex|cloud-code|muse` first");
|
|
1455
|
+
throw new Error("llm-mesh gateway is required but unavailable; no agent was started. Run `h2a llm-mesh start` or `h2a llm-mesh account enroll codex|cloud-code|muse|claude-code` first");
|
|
1440
1456
|
}
|
|
1441
1457
|
if (allowDirectFallback) {
|
|
1442
1458
|
process.stderr.write("[h2a] llm-mesh: gateway unavailable; continuing with native authentication.\n");
|
|
@@ -1518,7 +1534,7 @@ function renderNativeRestartResult(result, json) {
|
|
|
1518
1534
|
export async function prepareStructuredGateway(mode, inject = (selectedMode) => injectLlmMeshGatewayEnv(selectedMode, selectedMode !== "gateway")) {
|
|
1519
1535
|
const gateway = await inject(mode);
|
|
1520
1536
|
if (mode === "gateway" && !gateway) {
|
|
1521
|
-
throw new Error("llm-mesh gateway is required but unavailable; no agent was started. Run `h2a llm-mesh start` or `h2a llm-mesh account enroll codex|cloud-code|muse` first");
|
|
1537
|
+
throw new Error("llm-mesh gateway is required but unavailable; no agent was started. Run `h2a llm-mesh start` or `h2a llm-mesh account enroll codex|cloud-code|muse|claude-code` first");
|
|
1522
1538
|
}
|
|
1523
1539
|
return gateway;
|
|
1524
1540
|
}
|
|
@@ -1529,7 +1545,9 @@ export async function prepareLlmMeshForRestore(opts = {}, context = {
|
|
|
1529
1545
|
injectGateway: (mode) => injectLlmMeshGatewayEnv(mode, mode !== "gateway"),
|
|
1530
1546
|
}) {
|
|
1531
1547
|
const mode = opts.mode ?? "auto";
|
|
1532
|
-
|
|
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")
|
|
1533
1551
|
return;
|
|
1534
1552
|
const config = context.config ?? {};
|
|
1535
1553
|
const port = config.port ?? 3002;
|
|
@@ -1720,6 +1738,40 @@ function setAndReportDefaultRemote(url) {
|
|
|
1720
1738
|
ensureManagedTmuxProfile(getTmuxProfileConfig().profile);
|
|
1721
1739
|
process.stderr.write(`[h2a] default remote set to ${configured}\n`);
|
|
1722
1740
|
}
|
|
1741
|
+
// Root commander error boundary (DEC-119): commander never prints its own
|
|
1742
|
+
// error text. `outputError` is a no-op so the commander-built message (which
|
|
1743
|
+
// echoes argv, possibly multi-line) is dropped; `exitOverride` writes a
|
|
1744
|
+
// static message chosen ONLY from `err.code` — never `err.message`, never
|
|
1745
|
+
// user-typed text. Registered on the ROOT program before any `.command()`
|
|
1746
|
+
// so the whole tree inherits it via `copyInheritedSettings`.
|
|
1747
|
+
function staticCommanderErrorForCode(code) {
|
|
1748
|
+
switch (code) {
|
|
1749
|
+
case "commander.unknownOption":
|
|
1750
|
+
return "error: unknown option\n";
|
|
1751
|
+
case "commander.unknownCommand":
|
|
1752
|
+
return "error: unknown command\n";
|
|
1753
|
+
case "commander.excessArguments":
|
|
1754
|
+
return "error: too many arguments\n";
|
|
1755
|
+
case "commander.missingArgument":
|
|
1756
|
+
return "error: missing required argument\n";
|
|
1757
|
+
case "commander.optionMissingArgument":
|
|
1758
|
+
return "error: option argument missing\n";
|
|
1759
|
+
case "commander.missingMandatoryOptionValue":
|
|
1760
|
+
return "error: required option not specified\n";
|
|
1761
|
+
case "commander.conflictingOption":
|
|
1762
|
+
return "error: conflicting option\n";
|
|
1763
|
+
case "commander.invalidArgument":
|
|
1764
|
+
return "error: invalid argument\n";
|
|
1765
|
+
default:
|
|
1766
|
+
return "error: command error\n";
|
|
1767
|
+
}
|
|
1768
|
+
}
|
|
1769
|
+
function isCommanderNonErrorCode(code) {
|
|
1770
|
+
return (code === "commander.help" ||
|
|
1771
|
+
code === "commander.helpDisplayed" ||
|
|
1772
|
+
code === "commander.version" ||
|
|
1773
|
+
code === "commander.executeSubCommandAsync");
|
|
1774
|
+
}
|
|
1723
1775
|
export async function main(argv) {
|
|
1724
1776
|
// Passive fatal-path witness on the real entry path (see
|
|
1725
1777
|
// installCliWitnessOnce): observes via uncaughtExceptionMonitor, so the
|
|
@@ -1773,22 +1825,34 @@ export async function main(argv) {
|
|
|
1773
1825
|
"the separate remote and track CLIs. The heavy session runtime loads on " +
|
|
1774
1826
|
"demand. h2a runs and coordinates agents; it is not itself an agent.")
|
|
1775
1827
|
.version("0.0.0");
|
|
1828
|
+
// Root error boundary (DEC-119): commander never prints its own error
|
|
1829
|
+
// text. `outputError` is a no-op (commander message dropped);
|
|
1830
|
+
// `exitOverride` writes the static per-code message. Registered here —
|
|
1831
|
+
// before any `.command()` — so the whole tree inherits it through
|
|
1832
|
+
// `copyInheritedSettings`.
|
|
1833
|
+
program.configureOutput({
|
|
1834
|
+
writeErr: (str) => {
|
|
1835
|
+
process.stderr.write(str);
|
|
1836
|
+
},
|
|
1837
|
+
outputError: () => { },
|
|
1838
|
+
});
|
|
1839
|
+
program.showHelpAfterError(false);
|
|
1840
|
+
program.showSuggestionAfterError(false);
|
|
1841
|
+
program.exitOverride((err) => {
|
|
1842
|
+
if (isCommanderNonErrorCode(err.code))
|
|
1843
|
+
throw err;
|
|
1844
|
+
process.stderr.write(staticCommanderErrorForCode(err.code));
|
|
1845
|
+
throw err;
|
|
1846
|
+
});
|
|
1776
1847
|
const identityCommand = program
|
|
1777
1848
|
.command("identity")
|
|
1778
1849
|
.description("Inspect identity bindings or unlock a token-fenced identity lock");
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
if (!result.broken) {
|
|
1786
|
-
process.stderr.write(`[h2a] lock break refused: ${result.reason}${result.pid !== undefined ? ` (pid ${result.pid})` : ""}\n`);
|
|
1787
|
-
if (result.reason === "assert-dead-required") {
|
|
1788
|
-
process.stderr.write("[h2a] verify the holder has stopped, then explicitly pass --assert-dead; no PID is killed\n");
|
|
1789
|
-
}
|
|
1790
|
-
process.exitCode = 1;
|
|
1791
|
-
}
|
|
1850
|
+
// Operator break delegates to the core CLI's hidden `__operator-break`
|
|
1851
|
+
// plumbing (same `h2a`-binary spawn pattern as central-mcp): this package
|
|
1852
|
+
// must not import `@sentropic/h2a`, and the relay keeps stdio/exit codes
|
|
1853
|
+
// byte-for-byte identical where core is installed.
|
|
1854
|
+
const operatorBreak = (lockPath, token, assertDead) => {
|
|
1855
|
+
operatorBreakToCore(lockPath, token, assertDead, (code) => { process.exitCode = code; });
|
|
1792
1856
|
};
|
|
1793
1857
|
identityCommand
|
|
1794
1858
|
.command("unlock")
|
|
@@ -1818,9 +1882,18 @@ export async function main(argv) {
|
|
|
1818
1882
|
.option("--execute", "refuse destructive execution; no bypass exists")
|
|
1819
1883
|
.option("--root <path>", "DEF h2a root (default: H2A_ROOT or ~/h2a-workspace/.h2a)")
|
|
1820
1884
|
.option("--output <path>", "new external directory for the proof packet")
|
|
1821
|
-
.action((opts) => {
|
|
1885
|
+
.action(async (opts) => {
|
|
1886
|
+
let cull;
|
|
1887
|
+
try {
|
|
1888
|
+
cull = await import("./identity-cull/cull.js");
|
|
1889
|
+
}
|
|
1890
|
+
catch (error) {
|
|
1891
|
+
process.stderr.write(`[h2a] identity cull unavailable: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
1892
|
+
process.exitCode = 1;
|
|
1893
|
+
return;
|
|
1894
|
+
}
|
|
1822
1895
|
if (opts.execute) {
|
|
1823
|
-
const refusal = refuseCullExecution();
|
|
1896
|
+
const refusal = cull.refuseCullExecution();
|
|
1824
1897
|
process.stderr.write(`[h2a] identity cull refused: ${refusal.refusalCodes.join(", ")}\n`);
|
|
1825
1898
|
process.exitCode = 2;
|
|
1826
1899
|
return;
|
|
@@ -1828,7 +1901,7 @@ export async function main(argv) {
|
|
|
1828
1901
|
const defRoot = opts.root ?? process.env.H2A_ROOT ?? defaultLocalH2aRoot();
|
|
1829
1902
|
const outputDir = opts.output ?? join(tmpdir(), `h2a-identity-cull-${randomUUID()}`);
|
|
1830
1903
|
try {
|
|
1831
|
-
const result = runIdentityCullDryRun({
|
|
1904
|
+
const result = cull.runIdentityCullDryRun({
|
|
1832
1905
|
defRoot,
|
|
1833
1906
|
pinRoot: join(homedir(), "src", "a2a-cli"),
|
|
1834
1907
|
outputDir,
|
|
@@ -1955,6 +2028,10 @@ export async function main(argv) {
|
|
|
1955
2028
|
.option("--name <label>", "base label for the --count fan-out fleet names (default: cwd basename)")
|
|
1956
2029
|
.option("--browser", "add the headful-browser noVNC sidecar (WP7) — enables 2FA / authenticated-site flows from inside the pod")
|
|
1957
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")
|
|
1958
2035
|
.action(async (commandArgs, opts) => {
|
|
1959
2036
|
const { remote: remoteOverride, local, workspace, ...rest } = opts;
|
|
1960
2037
|
if (local) {
|
|
@@ -3640,6 +3717,8 @@ export async function main(argv) {
|
|
|
3640
3717
|
process.exitCode = 1;
|
|
3641
3718
|
return;
|
|
3642
3719
|
}
|
|
3720
|
+
const sameResumeIdentity = resolvedConvId !== undefined && registryEntry?.convId === resolvedConvId &&
|
|
3721
|
+
registryEntry?.cwd === process.cwd() && registryEntry?.tool === explicitProfile;
|
|
3643
3722
|
const entry = explicitProfile
|
|
3644
3723
|
? {
|
|
3645
3724
|
id: displaySlug,
|
|
@@ -3654,8 +3733,9 @@ export async function main(argv) {
|
|
|
3654
3733
|
enrolledAt: new Date().toISOString(),
|
|
3655
3734
|
lastSeenAt: new Date().toISOString(),
|
|
3656
3735
|
source: "run",
|
|
3657
|
-
bare:
|
|
3658
|
-
gatewayMode:
|
|
3736
|
+
bare: sameResumeIdentity ? registryEntry?.bare : undefined,
|
|
3737
|
+
gatewayMode: sameResumeIdentity ? registryEntry?.gatewayMode : undefined,
|
|
3738
|
+
gatewayProvenance: sameResumeIdentity ? registryEntry?.gatewayProvenance : undefined,
|
|
3659
3739
|
}
|
|
3660
3740
|
: registryEntry;
|
|
3661
3741
|
// WHICH host serves this resume — resolved BEFORE ANY act (F1): every
|
|
@@ -3753,7 +3833,7 @@ export async function main(argv) {
|
|
|
3753
3833
|
process.exitCode = 2;
|
|
3754
3834
|
return;
|
|
3755
3835
|
}
|
|
3756
|
-
const effectiveGatewayMode =
|
|
3836
|
+
const effectiveGatewayMode = resolveResumedGatewayMode(profile, gatewayMode, entry.gatewayMode, entry.gatewayProvenance);
|
|
3757
3837
|
let gateway;
|
|
3758
3838
|
// Resolve against the selected mode, not the parent's environment:
|
|
3759
3839
|
// required gateway injection below must succeed before either launch.
|
|
@@ -3966,7 +4046,11 @@ export async function main(argv) {
|
|
|
3966
4046
|
cwd: entry.cwd,
|
|
3967
4047
|
sessionClass,
|
|
3968
4048
|
...(entry.convId ? { convId: entry.convId } : {}),
|
|
3969
|
-
|
|
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
|
+
: {}),
|
|
3970
4054
|
bare: useBare,
|
|
3971
4055
|
});
|
|
3972
4056
|
process.stderr.write(`[h2a] resumed local session ${resumeSlug} (${profile} resume${entry.convId ? ` ${entry.convId}` : ""} in ${entry.cwd})\n`);
|
|
@@ -3997,8 +4081,11 @@ export async function main(argv) {
|
|
|
3997
4081
|
.option("--prompt-stdin", "read the initial Claude/Codex/AGY prompt from stdin (keeps it out of process argv)")
|
|
3998
4082
|
.option("--agent <agent>", "AGY agent override (for example: stp)")
|
|
3999
4083
|
.option("--model <model>", "Claude/Codex/AGY model override")
|
|
4000
|
-
.option("--
|
|
4001
|
-
.option("--
|
|
4084
|
+
.option("--role <role>", "launch purpose: review|challenge|build; AGY Claude builds require explicit opt-in")
|
|
4085
|
+
.option("--allow-claude-build", "explicitly spend the reserved AGY Claude pool on a build")
|
|
4086
|
+
.option("--output-file <path>", "expected AGY Claude headless artifact; report missing or unchanged output at exit")
|
|
4087
|
+
.option("--effort <level>", "reasoning effort: low|medium|high|xhigh (AGY: low|medium|high, max maps to high)")
|
|
4088
|
+
.option("--headless", "run once and exit (Claude -p / Codex exec / AGY stream-json), recording output under .h2a/runs/<name>")
|
|
4002
4089
|
.option("--background", "classify this detached session as a background worker excluded from human restore")
|
|
4003
4090
|
.option("--json", "emit one machine-readable h2a.run.result object")
|
|
4004
4091
|
.option("--llm-gateway", "launch the CLI through the local llm-mesh gateway")
|
|
@@ -4020,13 +4107,30 @@ export async function main(argv) {
|
|
|
4020
4107
|
process.exitCode = 2;
|
|
4021
4108
|
return;
|
|
4022
4109
|
}
|
|
4023
|
-
|
|
4024
|
-
|
|
4110
|
+
try {
|
|
4111
|
+
if (profile === "agy") {
|
|
4112
|
+
const effort = normalizeAgyEffort(opts.effort);
|
|
4113
|
+
if (effort !== undefined)
|
|
4114
|
+
opts.effort = effort;
|
|
4115
|
+
const model = resolveAgyModel(opts.model, effort);
|
|
4116
|
+
if (model !== undefined)
|
|
4117
|
+
opts.model = model;
|
|
4118
|
+
}
|
|
4119
|
+
assertAgyClaudeBuildAllowed({ profile, model: opts.model, role: opts.role, allowClaudeBuild: opts.allowClaudeBuild });
|
|
4120
|
+
if (opts.outputFile && !(profile === "agy" && isAgyClaudeModel(opts.model) && opts.headless)) {
|
|
4121
|
+
throw new Error("--output-file requires an AGY Claude headless run");
|
|
4122
|
+
}
|
|
4123
|
+
}
|
|
4124
|
+
catch (error) {
|
|
4125
|
+
process.stderr.write(`[h2a] ${error.message}\n`);
|
|
4126
|
+
if (opts.json)
|
|
4127
|
+
process.stdout.write(`${JSON.stringify({ kind: "h2a.run.failure", version: 1, state: "not-started",
|
|
4128
|
+
code: error.code ?? "invalid-agy-launch", error: error.message, retrySafe: false })}\n`);
|
|
4025
4129
|
process.exitCode = 2;
|
|
4026
4130
|
return;
|
|
4027
4131
|
}
|
|
4028
|
-
if (
|
|
4029
|
-
process.stderr.write(
|
|
4132
|
+
if (opts.effort !== undefined && !isAgentLaunchEffort(opts.effort)) {
|
|
4133
|
+
process.stderr.write(`[h2a] --effort must be low|medium|high|xhigh\n`);
|
|
4030
4134
|
process.exitCode = 2;
|
|
4031
4135
|
return;
|
|
4032
4136
|
}
|
|
@@ -4172,13 +4276,11 @@ export async function main(argv) {
|
|
|
4172
4276
|
}
|
|
4173
4277
|
};
|
|
4174
4278
|
try {
|
|
4175
|
-
|
|
4176
|
-
|
|
4177
|
-
|
|
4178
|
-
|
|
4179
|
-
const reservedTmuxSlugs =
|
|
4180
|
-
? existingLocalSessionSlugs(labels, cwd)
|
|
4181
|
-
: [];
|
|
4279
|
+
// No separate preflight fork: the in-process name probe below prepares
|
|
4280
|
+
// the host for a native run. The single-shot launch still performs its
|
|
4281
|
+
// fenced admission with a fresh inventory before creating any session.
|
|
4282
|
+
void preflightNativeLaunch;
|
|
4283
|
+
const reservedTmuxSlugs = existingLocalSessionSlugs(labels, cwd);
|
|
4182
4284
|
// The inventory and this decision are separate reads: a structured
|
|
4183
4285
|
// launch can leave a name visible just before its last tmux process
|
|
4184
4286
|
// exits. Re-probe every canonical/legacy spelling and reclaim the slug
|
|
@@ -4213,14 +4315,16 @@ export async function main(argv) {
|
|
|
4213
4315
|
// twin over a live-but-unprovable native writer (sol-F3): the same
|
|
4214
4316
|
// fail-closed rule as resolveManagedHost's rule 4 applies — refuse
|
|
4215
4317
|
// to launch on an unprovable host state.
|
|
4216
|
-
const nativeExistence = labels
|
|
4318
|
+
const nativeExistence = await Promise.all(labels
|
|
4217
4319
|
.map((label) => slugify(label ?? cwd))
|
|
4218
|
-
.map((slug) => {
|
|
4219
|
-
const detail =
|
|
4320
|
+
.map(async (slug) => {
|
|
4321
|
+
const detail = sessionHost === "native"
|
|
4322
|
+
? await nativeSessionStateAsync(localSessionName(slug), true)
|
|
4323
|
+
: nativeSessionState(localSessionName(slug));
|
|
4220
4324
|
const probe = detail.state === "unknown" ? "unknown"
|
|
4221
4325
|
: detail.state === "found" && detail.session.status === "running" ? "live" : "dead";
|
|
4222
4326
|
return { slug, probe, reason: detail.state === "unknown" ? detail.reason : undefined };
|
|
4223
|
-
});
|
|
4327
|
+
}));
|
|
4224
4328
|
const unknownNativeSlugs = nativeExistence
|
|
4225
4329
|
.filter((probed) => probed.probe === "unknown");
|
|
4226
4330
|
if (unknownNativeSlugs.length > 0) {
|
|
@@ -4265,9 +4369,15 @@ export async function main(argv) {
|
|
|
4265
4369
|
// Single-writer guard: refuse to resume a conversation another live
|
|
4266
4370
|
// session (local registry / remote pod) is already writing.
|
|
4267
4371
|
if (opts.resume) {
|
|
4372
|
+
// Reuse this operation's certified name verdict for that SAME
|
|
4373
|
+
// session. Other conversation owners still require their own probe.
|
|
4374
|
+
// Creation rechecks the complete inventory and name admission at
|
|
4375
|
+
// the host, so this snapshot never authorizes a colliding create.
|
|
4376
|
+
const nativeLivenessByName = new Map(nativeExistence.map(({ slug, probe }) => [localSessionName(slug), probe === "dead" ? false : probe === "live" ? true : "unknown"]));
|
|
4268
4377
|
const ok = await guardConvWriters({
|
|
4269
4378
|
convId: opts.resume,
|
|
4270
4379
|
cwd,
|
|
4380
|
+
nativeSessionLiveness: name => nativeLivenessByName.get(name) ?? nativeSessionLiveness(name),
|
|
4271
4381
|
...(opts.force ? { force: true } : {}),
|
|
4272
4382
|
fetchRemoteSessions: async () => {
|
|
4273
4383
|
const url = getConfiguredRemoteOptional();
|
|
@@ -4296,8 +4406,11 @@ export async function main(argv) {
|
|
|
4296
4406
|
for (const prunedId of resumeResolution.prunedIds) {
|
|
4297
4407
|
process.stderr.write(`[h2a] pruned dead registry row ${prunedId} that pinned conflicting launch options for this resume\n`);
|
|
4298
4408
|
}
|
|
4299
|
-
|
|
4300
|
-
|
|
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);
|
|
4301
4414
|
const command = localCliCommand(profile);
|
|
4302
4415
|
// A detached/background or run-once launch is a worker, even when its
|
|
4303
4416
|
// agent later emits a Claude SessionStart hook. Stamp this through tmux
|
|
@@ -4341,6 +4454,17 @@ export async function main(argv) {
|
|
|
4341
4454
|
return;
|
|
4342
4455
|
}
|
|
4343
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;
|
|
4462
|
+
// H0 (#662): normalize readable gateway aliases (argon-high, wonder,
|
|
4463
|
+
// wonder-full, spark) to their exact canonical model id. Unknown
|
|
4464
|
+
// names pass through unchanged for exact-model validation downstream.
|
|
4465
|
+
const runModel = opts.model !== undefined
|
|
4466
|
+
? (resolveGatewayModelAlias(opts.model) ?? opts.model)
|
|
4467
|
+
: undefined;
|
|
4344
4468
|
let args;
|
|
4345
4469
|
try {
|
|
4346
4470
|
args =
|
|
@@ -4351,7 +4475,7 @@ export async function main(argv) {
|
|
|
4351
4475
|
? { prompt: initialPrompt }
|
|
4352
4476
|
: {}),
|
|
4353
4477
|
...(opts.agent !== undefined ? { agent: opts.agent } : {}),
|
|
4354
|
-
...(
|
|
4478
|
+
...(runModel !== undefined ? { model: runModel } : {}),
|
|
4355
4479
|
...(opts.effort !== undefined
|
|
4356
4480
|
? { effort: opts.effort }
|
|
4357
4481
|
: {}),
|
|
@@ -4377,6 +4501,10 @@ export async function main(argv) {
|
|
|
4377
4501
|
}
|
|
4378
4502
|
let name;
|
|
4379
4503
|
let slug;
|
|
4504
|
+
// R3 P1: pid/identity returned by the single-shot launch; the
|
|
4505
|
+
// re-probes below prefer these and skip their forks.
|
|
4506
|
+
let launchedPid;
|
|
4507
|
+
let launchedWorkerPid;
|
|
4380
4508
|
let outputLog;
|
|
4381
4509
|
let resultJson;
|
|
4382
4510
|
let agentPane;
|
|
@@ -4389,6 +4517,10 @@ export async function main(argv) {
|
|
|
4389
4517
|
mkdirSync(runDir, { recursive: true });
|
|
4390
4518
|
outputLog = join(runDir, "output.log");
|
|
4391
4519
|
resultJson = join(runDir, "result.json");
|
|
4520
|
+
// Name admission above has completed. Retire the previous result
|
|
4521
|
+
// before returning a path or starting the new run-once supervisor.
|
|
4522
|
+
if (profile === "agy" && isAgyClaudeModel(opts.model))
|
|
4523
|
+
rmSync(resultJson, { force: true });
|
|
4392
4524
|
const headlessPromptInput = structuredLaunch && isAgentLaunchProfile(profile)
|
|
4393
4525
|
? buildAgentLaunchStdin({
|
|
4394
4526
|
profile,
|
|
@@ -4402,14 +4534,16 @@ export async function main(argv) {
|
|
|
4402
4534
|
({ name, slug, promptFile } = startNativeHeadlessSession(profile, command, cwd, args, resultJson, outputLog, label, headlessPromptInput, structuredLaunch, sessionClass, onCreateAttempt, structuredLaunch ? (owned) => {
|
|
4403
4535
|
launchOwnership = { host: "native", sessions: [owned] };
|
|
4404
4536
|
launchGuard = startLaunchGuard(runDir, launchOwnership);
|
|
4405
|
-
} : undefined));
|
|
4537
|
+
} : undefined, opts.outputFile ? resolve(cwd, opts.outputFile) : undefined));
|
|
4406
4538
|
}
|
|
4407
4539
|
else {
|
|
4408
|
-
({ name, slug, agentPane, promptFile } = startHeadlessSession(profile, command, cwd, args, resultJson, outputLog, label, getTmuxProfileConfig().profile, headlessPromptInput, structuredLaunch, sessionClass, onCreateAttempt));
|
|
4540
|
+
({ name, slug, agentPane, promptFile } = startHeadlessSession(profile, command, cwd, args, resultJson, outputLog, label, getTmuxProfileConfig().profile, headlessPromptInput, structuredLaunch, sessionClass, onCreateAttempt, opts.outputFile ? resolve(cwd, opts.outputFile) : undefined));
|
|
4409
4541
|
}
|
|
4410
4542
|
}
|
|
4411
4543
|
else if (sessionHost === "native") {
|
|
4412
|
-
|
|
4544
|
+
// One in-process async operation, with a fresh admission inventory;
|
|
4545
|
+
// returns pid/identity so the re-probes below are skipped.
|
|
4546
|
+
({ name, slug, pid: launchedPid, workerPid: launchedWorkerPid } = await startNativeSessionSingleShot(profile, command, cwd, args, label, {
|
|
4413
4547
|
onCreateAttempt,
|
|
4414
4548
|
...(opts.resume !== undefined ? { resumeId: opts.resume } : {}),
|
|
4415
4549
|
...(initialPrompt !== undefined
|
|
@@ -4538,6 +4672,18 @@ export async function main(argv) {
|
|
|
4538
4672
|
now: () => Date.now(),
|
|
4539
4673
|
}, { profile });
|
|
4540
4674
|
if (promptDelivery.state !== "working") {
|
|
4675
|
+
const quotaDiagnostic = promptDelivery.state === "submitted-idle" ? "" : `${promptDelivery.reason}\n${promptDelivery.capture ?? ""}`;
|
|
4676
|
+
const quotaVerdict = profile === "agy" && isAgyClaudeModel(opts.model)
|
|
4677
|
+
? detectAgyClaudeQuotaExhaustion(quotaDiagnostic) : { exhausted: false };
|
|
4678
|
+
const quotaFailure = profile === "agy" && isAgyClaudeModel(opts.model) &&
|
|
4679
|
+
promptDelivery.state === "provider-blocked" && promptDelivery.providerError
|
|
4680
|
+
? promptDelivery.providerError : quotaVerdict.exhausted
|
|
4681
|
+
? agyClaudeQuotaError(quotaVerdict.signature, quotaDiagnostic) : undefined;
|
|
4682
|
+
if (quotaFailure)
|
|
4683
|
+
try {
|
|
4684
|
+
writeAgyClaudeQuotaStatus(quotaFailure.quota);
|
|
4685
|
+
}
|
|
4686
|
+
catch { /* Response still carries the observation. */ }
|
|
4541
4687
|
cleanupHeadlessPromptFile(promptFile);
|
|
4542
4688
|
// Only claim the session is gone when the kill actually reported
|
|
4543
4689
|
// success. "nothing is left running" over an unchecked call is the
|
|
@@ -4567,26 +4713,64 @@ export async function main(argv) {
|
|
|
4567
4713
|
? `[h2a] last screen:\n${promptDelivery.capture}\n`
|
|
4568
4714
|
: ""));
|
|
4569
4715
|
process.exitCode = 1;
|
|
4570
|
-
if (opts.json && promptDelivery.state === "provider-blocked") {
|
|
4716
|
+
if (opts.json && (promptDelivery.state === "provider-blocked" || quotaFailure)) {
|
|
4571
4717
|
process.stdout.write(`${JSON.stringify({ kind: "h2a.run.failure", version: 1,
|
|
4572
|
-
state: "provider-blocked", launchId: slug, error: promptDelivery.reason,
|
|
4573
|
-
stopped, retrySafe: false, prompt: { delivered:
|
|
4718
|
+
state: "provider-blocked", launchId: slug, error: quotaFailure?.message ?? (promptDelivery.state === "submitted-idle" ? "agent idle after submission" : promptDelivery.reason),
|
|
4719
|
+
stopped, retrySafe: false, prompt: { delivered: promptDelivery.state === "provider-blocked", ...("waitedMs" in promptDelivery ? { waitedMs: promptDelivery.waitedMs } : {}) },
|
|
4720
|
+
...(quotaFailure ? { code: quotaFailure.code, error: quotaFailure.message, quota: quotaFailure.quota,
|
|
4721
|
+
resetsAt: quotaFailure.resetsAt, retryable: false, model: opts.model } : {}),
|
|
4574
4722
|
})}\n`);
|
|
4575
4723
|
}
|
|
4576
4724
|
return;
|
|
4577
4725
|
}
|
|
4578
4726
|
}
|
|
4727
|
+
const reportAgyCompletion = () => {
|
|
4728
|
+
if (!(profile === "agy" && isAgyClaudeModel(opts.model) && opts.headless && resultJson))
|
|
4729
|
+
return false;
|
|
4730
|
+
try {
|
|
4731
|
+
const completed = JSON.parse(readFileSync(resultJson, "utf8"));
|
|
4732
|
+
// The previous result was removed before this supervisor started.
|
|
4733
|
+
// Filesystem and wall-clock timestamps cannot prove generation.
|
|
4734
|
+
if (!["h2a.run.failure", "h2a.run.completion"].includes(completed.kind))
|
|
4735
|
+
return false;
|
|
4736
|
+
launchGuard?.complete();
|
|
4737
|
+
if (opts.json)
|
|
4738
|
+
process.stdout.write(`${JSON.stringify({ ...completed, launchId: slug, result: { path: resultJson }, logs: { path: outputLog } })}\n`);
|
|
4739
|
+
else
|
|
4740
|
+
process.stderr.write(`[h2a] ${completed.error ?? "AGY run completed"}\n`);
|
|
4741
|
+
process.exitCode = completed.state === "done" ? 0 : 1;
|
|
4742
|
+
return true;
|
|
4743
|
+
}
|
|
4744
|
+
catch {
|
|
4745
|
+
return false;
|
|
4746
|
+
} // Still running; the durable result may appear later.
|
|
4747
|
+
};
|
|
4748
|
+
if (profile === "agy" && isAgyClaudeModel(opts.model) && opts.headless && resultJson) {
|
|
4749
|
+
// A fast provider refusal may finish before the host PID probe.
|
|
4750
|
+
// Return that typed completion rather than a misleading PID error.
|
|
4751
|
+
for (let attempt = 0; attempt < 5; attempt++) {
|
|
4752
|
+
if (reportAgyCompletion())
|
|
4753
|
+
return;
|
|
4754
|
+
sleepSync(50);
|
|
4755
|
+
}
|
|
4756
|
+
}
|
|
4757
|
+
// R3 P1: prefer the pid/identity returned by the single-shot
|
|
4758
|
+
// launch (no fork); fall back to probes only when absent (e.g.
|
|
4759
|
+
// tmux path or adopted pre-existing session without a fresh pid).
|
|
4579
4760
|
const pid = sessionHost === "native"
|
|
4580
|
-
? nativeSessionPid(name)
|
|
4761
|
+
? (launchedPid ?? nativeSessionPid(name))
|
|
4581
4762
|
: agentPane
|
|
4582
4763
|
? localSessionPanePid(agentPane)
|
|
4583
4764
|
: undefined;
|
|
4584
4765
|
const workerPid = sessionHost === "native"
|
|
4585
|
-
? nativeWorkerPid(name)
|
|
4766
|
+
? (launchedWorkerPid ?? nativeWorkerPid(name))
|
|
4586
4767
|
: agentPane !== undefined
|
|
4587
4768
|
? paneWorkerPid(agentPane)
|
|
4588
4769
|
: undefined;
|
|
4589
4770
|
if (structuredLaunch && pid === undefined) {
|
|
4771
|
+
// The supervisor can publish and exit DURING the PID probe.
|
|
4772
|
+
if (reportAgyCompletion())
|
|
4773
|
+
return;
|
|
4590
4774
|
cleanupHeadlessPromptFile(promptFile);
|
|
4591
4775
|
// OWN-CLEANUP, deliberately NOT resolver-derived (F1 sorting):
|
|
4592
4776
|
// `name`/`sessionHost` are the session and host THIS command
|
|
@@ -4602,8 +4786,8 @@ export async function main(argv) {
|
|
|
4602
4786
|
return;
|
|
4603
4787
|
}
|
|
4604
4788
|
// Auto-enroll in the live-session registry (feeds `h2a ls`/`restore`).
|
|
4605
|
-
//
|
|
4606
|
-
//
|
|
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.
|
|
4607
4791
|
enrollFromRun({
|
|
4608
4792
|
profile,
|
|
4609
4793
|
slug,
|
|
@@ -4615,8 +4799,10 @@ export async function main(argv) {
|
|
|
4615
4799
|
cwd,
|
|
4616
4800
|
sessionClass,
|
|
4617
4801
|
...(opts.resume !== undefined ? { convId: opts.resume } : {}),
|
|
4618
|
-
gatewayMode
|
|
4619
|
-
|
|
4802
|
+
...(gatewayMode !== "auto" || pinnedLaunch?.gatewayProvenance === "explicit"
|
|
4803
|
+
? { gatewayMode: launchGatewayMode, gatewayProvenance: "explicit" }
|
|
4804
|
+
: {}),
|
|
4805
|
+
...(enrollmentBare !== undefined ? { bare: enrollmentBare } : {}),
|
|
4620
4806
|
});
|
|
4621
4807
|
const nativeOwnerSocket = launchOwnership?.host === "native"
|
|
4622
4808
|
? launchOwnership.sessions.find(owned => owned.name === name)?.socketPath : undefined;
|
|
@@ -4656,6 +4842,8 @@ export async function main(argv) {
|
|
|
4656
4842
|
tmuxSession: only.name,
|
|
4657
4843
|
host: sessionHost === "native" ? "native" : "tmux",
|
|
4658
4844
|
profile,
|
|
4845
|
+
...(opts.model ? { model: opts.model, modelSource: "launch-argument", effectiveModel: "unknown" } : {}),
|
|
4846
|
+
...(profile === "agy" && isAgyClaudeModel(opts.model) ? { quota: readAgyClaudeQuotaStatus() } : {}),
|
|
4659
4847
|
workspace: cwd,
|
|
4660
4848
|
mode: opts.headless ? "headless" : "interactive",
|
|
4661
4849
|
background: opts.background === true,
|
|
@@ -6140,6 +6328,9 @@ export async function main(argv) {
|
|
|
6140
6328
|
...(entry.gatewayMode !== undefined
|
|
6141
6329
|
? { gatewayMode: entry.gatewayMode }
|
|
6142
6330
|
: {}),
|
|
6331
|
+
...(entry.gatewayProvenance !== undefined
|
|
6332
|
+
? { gatewayProvenance: entry.gatewayProvenance }
|
|
6333
|
+
: {}),
|
|
6143
6334
|
...(entry.bare !== undefined ? { bare: entry.bare } : {}),
|
|
6144
6335
|
}));
|
|
6145
6336
|
const result = await executeNativeRestart({
|
|
@@ -6194,7 +6385,9 @@ export async function main(argv) {
|
|
|
6194
6385
|
...(candidate.convId !== undefined
|
|
6195
6386
|
? { convId: candidate.convId }
|
|
6196
6387
|
: {}),
|
|
6197
|
-
...(mode !== "auto"
|
|
6388
|
+
...(mode !== "auto"
|
|
6389
|
+
? { gatewayMode: mode, gatewayProvenance: "explicit" }
|
|
6390
|
+
: {}),
|
|
6198
6391
|
...(candidate.bare !== undefined ? { bare: candidate.bare } : {}),
|
|
6199
6392
|
});
|
|
6200
6393
|
}
|
|
@@ -6307,7 +6500,7 @@ export async function main(argv) {
|
|
|
6307
6500
|
process.exitCode = 1;
|
|
6308
6501
|
return;
|
|
6309
6502
|
}
|
|
6310
|
-
const relaunchMode =
|
|
6503
|
+
const relaunchMode = resolveResumedGatewayMode(nativeEntry.tool, "auto", nativeEntry.gatewayMode, nativeEntry.gatewayProvenance);
|
|
6311
6504
|
await injectLlmMeshGatewayEnv(relaunchMode, relaunchMode !== "gateway", actName);
|
|
6312
6505
|
if (!killNativeSessionTree(actName)) {
|
|
6313
6506
|
process.stderr.write(`[h2a] native session ${slug} could not be killed; nothing was relaunched.\n`);
|
|
@@ -6334,7 +6527,15 @@ export async function main(argv) {
|
|
|
6334
6527
|
...(relaunched.pid !== undefined ? { pid: relaunched.pid } : {}),
|
|
6335
6528
|
cwd: nativeEntry.cwd,
|
|
6336
6529
|
sessionClass: nativeEntry.sessionClass ?? "human",
|
|
6337
|
-
|
|
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
|
+
: {}),
|
|
6338
6539
|
bare: useBare,
|
|
6339
6540
|
...(nativeEntry.convId ? { convId: nativeEntry.convId } : {}),
|
|
6340
6541
|
});
|
|
@@ -6464,14 +6665,55 @@ export async function main(argv) {
|
|
|
6464
6665
|
if (!forced) {
|
|
6465
6666
|
let ok = 0;
|
|
6466
6667
|
for (const action of plan.actions) {
|
|
6467
|
-
|
|
6468
|
-
|
|
6469
|
-
|
|
6470
|
-
|
|
6471
|
-
|
|
6472
|
-
|
|
6473
|
-
|
|
6474
|
-
|
|
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
|
+
}
|
|
6475
6717
|
if (relaunched) {
|
|
6476
6718
|
ok += 1;
|
|
6477
6719
|
process.stderr.write(`[h2a] relaunched ${action.slug}: ${action.cmd}\n`);
|
|
@@ -6547,6 +6789,11 @@ export async function main(argv) {
|
|
|
6547
6789
|
}
|
|
6548
6790
|
finalReady.push(item);
|
|
6549
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();
|
|
6550
6797
|
let ok = 0;
|
|
6551
6798
|
for (const { action, entry, sessionClass } of finalReady) {
|
|
6552
6799
|
const safety = sessionRelaunchSafety(action.name);
|
|
@@ -6573,8 +6820,9 @@ export async function main(argv) {
|
|
|
6573
6820
|
process.stderr.write(`[h2a] skipped ${action.slug}: tmux session ${action.name} changed or was no longer proven dead immediately before kill.\n`);
|
|
6574
6821
|
continue;
|
|
6575
6822
|
}
|
|
6823
|
+
const restoreLoopEnv = replaceAnthropicGatewayEnvironment(process.env, loopEntryGatewayEnv);
|
|
6576
6824
|
try {
|
|
6577
|
-
const gatewayMode =
|
|
6825
|
+
const gatewayMode = resolveResumedGatewayMode(action.profile, "auto", entry.gatewayMode, entry.gatewayProvenance);
|
|
6578
6826
|
await injectLlmMeshGatewayEnv(gatewayMode, true, localSessionName(action.slug));
|
|
6579
6827
|
const { name, agentPane } = startLocalSession(action.profile, action.command, entry.cwd, localResumeArgs(action.profile, action.convId, {
|
|
6580
6828
|
bare: resolveClaudeBare(action.profile, entry.bare),
|
|
@@ -6596,6 +6844,9 @@ export async function main(argv) {
|
|
|
6596
6844
|
...(entry.gatewayMode !== undefined
|
|
6597
6845
|
? { gatewayMode: entry.gatewayMode }
|
|
6598
6846
|
: {}),
|
|
6847
|
+
...(entry.gatewayProvenance !== undefined
|
|
6848
|
+
? { gatewayProvenance: entry.gatewayProvenance }
|
|
6849
|
+
: {}),
|
|
6599
6850
|
...(entry.bare !== undefined ? { bare: entry.bare } : {}),
|
|
6600
6851
|
});
|
|
6601
6852
|
const wake = wakeRelaunchedSession(relaunchContinuationPrompt(entry.task), {
|
|
@@ -6628,6 +6879,9 @@ export async function main(argv) {
|
|
|
6628
6879
|
catch (error) {
|
|
6629
6880
|
process.stderr.write(`[h2a] FAILED to force-restart ${action.slug} after killing ${action.name}: ${error.message}\n`);
|
|
6630
6881
|
}
|
|
6882
|
+
finally {
|
|
6883
|
+
restoreLoopEnv();
|
|
6884
|
+
}
|
|
6631
6885
|
}
|
|
6632
6886
|
process.stderr.write(`[h2a] force-restarted ${ok}/${plan.actions.length}${plan.skipped.length ? `, ${plan.skipped.length} skipped` : ""}\n`);
|
|
6633
6887
|
if (ok !== plan.actions.length)
|
|
@@ -6991,13 +7245,13 @@ export async function main(argv) {
|
|
|
6991
7245
|
}
|
|
6992
7246
|
if (opts.reattach)
|
|
6993
7247
|
restoreOpts.reattach = true;
|
|
6994
|
-
//
|
|
6995
|
-
//
|
|
6996
|
-
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") {
|
|
6997
7251
|
try {
|
|
6998
7252
|
await prepareLlmMeshForRestore({
|
|
6999
7253
|
dryRun: Boolean(opts.dryRun),
|
|
7000
|
-
mode: forceGateway
|
|
7254
|
+
mode: forceGateway,
|
|
7001
7255
|
});
|
|
7002
7256
|
}
|
|
7003
7257
|
catch (error) {
|
|
@@ -7531,13 +7785,29 @@ export async function main(argv) {
|
|
|
7531
7785
|
llmMeshAccountCommand
|
|
7532
7786
|
.command("enroll <provider>")
|
|
7533
7787
|
.description("Enroll through the sentropic-owned OAuth state machine " +
|
|
7534
|
-
"(cloud-code, codex, muse OAuth device flow,
|
|
7535
|
-
"--cli
|
|
7788
|
+
"(cloud-code, codex, muse OAuth device flow, mistral-vibe browser sign-in — alias: vibe, or claude-code). " +
|
|
7789
|
+
"--from oauth|cli|token selects the source (default oauth): cli reads a private L1 parser profile selected by --seat-profile; " +
|
|
7790
|
+
"token reads a consumer SeatBundle via bounded stdin or masked input for all five CLIs. " +
|
|
7791
|
+
"--cli / --token are the same modes as booleans. Seat aliases: agy/cloud-code, claude/claude-code, vibe/mistral-vibe")
|
|
7536
7792
|
.option("--config-ref <ref>", "sentropic configuration reference for OAuth")
|
|
7537
|
-
.option("--
|
|
7538
|
-
.
|
|
7539
|
-
|
|
7540
|
-
|
|
7793
|
+
.option("--from <mode>", "enrollment source: oauth (default), cli (private L1 seat profile) or token (consumer SeatBundle on stdin)")
|
|
7794
|
+
.option("--cli", "import a consumer seat from the explicitly selected --seat-profile. Takes no value")
|
|
7795
|
+
.option("--token", "import a consumer SeatBundle for any of the five CLIs via bounded stdin or masked input; never argv")
|
|
7796
|
+
.option("--seat-profile <path>", "private L1 CliSeatProfileInput file for --from cli; no ambient store fallback")
|
|
7797
|
+
.allowExcessArguments(true)
|
|
7798
|
+
.action(async (provider, opts, command) => {
|
|
7799
|
+
// Static excess-argument refusal (m1): commander would otherwise echo
|
|
7800
|
+
// every positional value (including a secret) on stderr before the
|
|
7801
|
+
// action runs. Excess positionals are refused here without echoing
|
|
7802
|
+
// any value. Token material travels on stdin only, never argv.
|
|
7803
|
+
const positionalCount = Array.isArray(command?.args) ? command.args.length : 1;
|
|
7804
|
+
if (positionalCount > 1) {
|
|
7805
|
+
process.stderr.write("[h2a] llm-mesh account: too many arguments (expected provider only; never pass secrets in argv)\n");
|
|
7806
|
+
process.exitCode = 1;
|
|
7807
|
+
return;
|
|
7808
|
+
}
|
|
7809
|
+
// Seat names and enrollment providers share the five-CLI codec map.
|
|
7810
|
+
const normalized = ["agy", "claude", "vibe"].includes(provider) ? resolveSeatEnrollmentProvider(provider) : provider;
|
|
7541
7811
|
if (normalized === "muse-code") {
|
|
7542
7812
|
// Owner decision 2026-10-10: muse-code is no longer a user entry —
|
|
7543
7813
|
// muse enrolls via the OAuth device flow by default, --cli selects
|
|
@@ -7547,20 +7817,37 @@ export async function main(argv) {
|
|
|
7547
7817
|
process.exitCode = 1;
|
|
7548
7818
|
return;
|
|
7549
7819
|
}
|
|
7820
|
+
if (opts.from !== undefined && opts.from !== "oauth" && opts.from !== "cli" && opts.from !== "token") {
|
|
7821
|
+
process.stderr.write("[h2a] llm-mesh account: --from must be one of oauth, cli, token\n");
|
|
7822
|
+
process.exitCode = 1;
|
|
7823
|
+
return;
|
|
7824
|
+
}
|
|
7825
|
+
if (opts.from !== undefined && (opts.cli || opts.token)) {
|
|
7826
|
+
process.stderr.write("[h2a] llm-mesh account: --from cannot be combined with --cli or --token\n");
|
|
7827
|
+
process.exitCode = 1;
|
|
7828
|
+
return;
|
|
7829
|
+
}
|
|
7830
|
+
if (opts.cli && opts.token) {
|
|
7831
|
+
process.stderr.write("[h2a] llm-mesh account: --cli and --token cannot be combined\n");
|
|
7832
|
+
process.exitCode = 1;
|
|
7833
|
+
return;
|
|
7834
|
+
}
|
|
7550
7835
|
if (normalized !== "cloud-code" &&
|
|
7551
7836
|
normalized !== "codex" &&
|
|
7552
7837
|
normalized !== "muse" &&
|
|
7553
|
-
normalized !== "mistral-vibe"
|
|
7554
|
-
|
|
7555
|
-
|
|
7838
|
+
normalized !== "mistral-vibe" &&
|
|
7839
|
+
normalized !== "claude-code") {
|
|
7840
|
+
process.stderr.write("[h2a] llm-mesh account: unsupported provider. " +
|
|
7841
|
+
"Supported: cloud-code (agy), codex, muse, mistral-vibe (vibe), claude-code (claude); oauth is the default, cli/token import consumer seats\n");
|
|
7556
7842
|
process.exitCode = 1;
|
|
7557
7843
|
return;
|
|
7558
7844
|
}
|
|
7559
7845
|
try {
|
|
7560
|
-
const
|
|
7561
|
-
|
|
7562
|
-
|
|
7563
|
-
|
|
7846
|
+
const source = opts.from ?? (opts.cli ? "cli" : opts.token ? "token" : "oauth");
|
|
7847
|
+
const { enrollViaFacade } = await import("./llm-mesh-accounts.js");
|
|
7848
|
+
const account = source === "oauth"
|
|
7849
|
+
? await enrollViaFacade(normalized, { ...(opts.configRef ? { configRef: opts.configRef } : {}), from: "oauth" })
|
|
7850
|
+
: await enrollSeatConsumerViaFacade(normalized, { from: source, ...(opts.seatProfile ? { seatProfile: opts.seatProfile } : {}) });
|
|
7564
7851
|
process.stdout.write(`[h2a] llm-mesh account: enrolled ${account.provider}; ` +
|
|
7565
7852
|
"account inventory remains in Sentropic\n");
|
|
7566
7853
|
// The gateway loads the account inventory at boot only: an account
|
|
@@ -7582,6 +7869,7 @@ export async function main(argv) {
|
|
|
7582
7869
|
.description("List public accounts owned by this local llm-mesh scope")
|
|
7583
7870
|
.option("--json", "emit public account metadata as JSON")
|
|
7584
7871
|
.action(async (opts) => {
|
|
7872
|
+
const { listAccountsViaFacade, formatLlmMeshAccountList, formatLlmMeshAccountError } = await import("./llm-mesh-accounts.js");
|
|
7585
7873
|
try {
|
|
7586
7874
|
const accounts = await listAccountsViaFacade();
|
|
7587
7875
|
process.stdout.write(`${formatLlmMeshAccountList(accounts, opts.json === true)}\n`);
|
|
@@ -7596,6 +7884,7 @@ export async function main(argv) {
|
|
|
7596
7884
|
.aliases(["rm", "unenroll"])
|
|
7597
7885
|
.description("Remove one account owned by this local llm-mesh scope")
|
|
7598
7886
|
.action(async (accountId) => {
|
|
7887
|
+
const { removeAccountViaFacade, formatLlmMeshAccountError } = await import("./llm-mesh-accounts.js");
|
|
7599
7888
|
try {
|
|
7600
7889
|
const result = await removeAccountViaFacade(accountId);
|
|
7601
7890
|
process.stdout.write(`[h2a] llm-mesh account: removed ${result.accountId}\n`);
|
|
@@ -7661,17 +7950,15 @@ export async function main(argv) {
|
|
|
7661
7950
|
const config = readLlmMeshConfig() ?? {};
|
|
7662
7951
|
const existing = readGatewayPid();
|
|
7663
7952
|
if (existing) {
|
|
7664
|
-
|
|
7953
|
+
// Starting the service is not routing consent: launches stay direct
|
|
7954
|
+
// unless --gw is passed, so the durable flag is left untouched.
|
|
7665
7955
|
process.stdout.write(`[h2a] llm-mesh: already running (pid ${existing})\n`);
|
|
7666
|
-
process.stdout.write(`[h2a] llm-mesh: restore config enabled\n`);
|
|
7667
7956
|
return;
|
|
7668
7957
|
}
|
|
7669
7958
|
process.stdout.write(`[h2a] llm-mesh: starting gateway…\n`);
|
|
7670
7959
|
const result = await startGateway(config, { verbose: opts.verbose });
|
|
7671
|
-
setLlmMeshRuntimeConfig({ enabled: true });
|
|
7672
7960
|
const port = result.port;
|
|
7673
7961
|
process.stdout.write(`[h2a] llm-mesh: gateway started (pid ${result.pid}, port ${port})\n`);
|
|
7674
|
-
process.stdout.write(`[h2a] llm-mesh: restore config enabled\n`);
|
|
7675
7962
|
process.stdout.write("[h2a] llm-mesh: use `h2a run claude --gw`; the opaque bearer is not printed\n");
|
|
7676
7963
|
});
|
|
7677
7964
|
llmMeshCommand
|
|
@@ -7699,6 +7986,9 @@ export async function main(argv) {
|
|
|
7699
7986
|
else {
|
|
7700
7987
|
process.stdout.write(`[h2a] llm-mesh: not running\n`);
|
|
7701
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 });
|
|
7702
7992
|
});
|
|
7703
7993
|
llmMeshCommand
|
|
7704
7994
|
.command("restart")
|
|
@@ -7715,9 +8005,9 @@ export async function main(argv) {
|
|
|
7715
8005
|
}
|
|
7716
8006
|
process.stdout.write(`[h2a] llm-mesh: starting gateway…\n`);
|
|
7717
8007
|
const result = await startGateway(config, { verbose: opts.verbose });
|
|
7718
|
-
|
|
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.
|
|
7719
8010
|
process.stdout.write(`[h2a] llm-mesh: gateway restarted (pid ${result.pid}, port ${result.port})\n`);
|
|
7720
|
-
process.stdout.write(`[h2a] llm-mesh: restore config enabled\n`);
|
|
7721
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`);
|
|
7722
8012
|
process.stdout.write("[h2a] llm-mesh: use `h2a run claude --gw`; the opaque bearer is not printed\n");
|
|
7723
8013
|
});
|
|
@@ -7897,7 +8187,25 @@ export async function main(argv) {
|
|
|
7897
8187
|
applyRuntimeHelpGroups(program.createHelp().visibleCommands(program));
|
|
7898
8188
|
program.configureHelp({ groupItems: groupRuntimeHelpItems });
|
|
7899
8189
|
program.addHelpText("after", "\nGrouped map of every h2a command (core + runtime): h2a explain\n");
|
|
7900
|
-
|
|
8190
|
+
try {
|
|
8191
|
+
await program.parseAsync([...argv]);
|
|
8192
|
+
}
|
|
8193
|
+
catch (err) {
|
|
8194
|
+
// Commander errors already wrote their static per-code message in
|
|
8195
|
+
// `exitOverride`; only set the exit code here, never `err.message`.
|
|
8196
|
+
if (err !== null &&
|
|
8197
|
+
typeof err === "object" &&
|
|
8198
|
+
"code" in err &&
|
|
8199
|
+
typeof err.code === "string") {
|
|
8200
|
+
const code = err.code;
|
|
8201
|
+
if (code.startsWith("commander.")) {
|
|
8202
|
+
const exitCode = err.exitCode;
|
|
8203
|
+
process.exitCode = typeof exitCode === "number" ? exitCode : 1;
|
|
8204
|
+
return process.exitCode;
|
|
8205
|
+
}
|
|
8206
|
+
}
|
|
8207
|
+
throw err;
|
|
8208
|
+
}
|
|
7901
8209
|
const code = process.exitCode;
|
|
7902
8210
|
return typeof code === "number" ? code : 0;
|
|
7903
8211
|
}
|
|
@@ -7931,6 +8239,13 @@ export { main as dispatchH2a };
|
|
|
7931
8239
|
/** @deprecated Compatibility for older h2a cores; new cores require dispatchH2a. */
|
|
7932
8240
|
export { main as dispatch };
|
|
7933
8241
|
export { ensureCentralMcp } from "./central-mcp.js";
|
|
8242
|
+
/** Offline five-CLI seat contract and run-scoped materialization. */
|
|
8243
|
+
export * from "./cli-seat-fixtures.js";
|
|
8244
|
+
export * from "./cli-seat-codecs.js";
|
|
8245
|
+
export * from "./cli-seat-materialization.js";
|
|
8246
|
+
// Keep the public async consumer contract without loading account providers in
|
|
8247
|
+
// every local crash-recovery launcher before it can elect its native host.
|
|
8248
|
+
export const enrollSeatConsumerViaFacade = async (...args) => (await import("./llm-mesh-accounts.js")).enrollSeatConsumerViaFacade(...args);
|
|
7934
8249
|
/**
|
|
7935
8250
|
* h2a-facing surface: the read-only remote-agents projection consumed by the
|
|
7936
8251
|
* objective-loop tick's *agents adapter* in `@sentropic/h2a` (via lazy
|