@sentropic/h2a-runtime 0.98.6 → 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 +5 -0
- package/dist/attach.d.ts.map +1 -1
- package/dist/attach.js +75 -6
- package/dist/attach.js.map +1 -1
- package/dist/auth-login.d.ts +24 -1
- package/dist/auth-login.d.ts.map +1 -1
- package/dist/auth-login.js +132 -4
- package/dist/auth-login.js.map +1 -1
- package/dist/central-mcp.d.ts +34 -0
- package/dist/central-mcp.d.ts.map +1 -1
- package/dist/central-mcp.js +275 -71
- 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-help-groups.js +1 -1
- package/dist/cli-help-groups.js.map +1 -1
- 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/crash-log.d.ts +112 -0
- package/dist/crash-log.d.ts.map +1 -0
- package/dist/crash-log.js +560 -0
- package/dist/crash-log.js.map +1 -0
- 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 +58 -0
- package/dist/gateway-host/live-declarations.d.ts.map +1 -0
- package/dist/gateway-host/live-declarations.js +429 -0
- package/dist/gateway-host/live-declarations.js.map +1 -0
- 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-host/live-registration.d.ts +162 -0
- package/dist/gateway-host/live-registration.d.ts.map +1 -0
- package/dist/gateway-host/live-registration.js +455 -0
- package/dist/gateway-host/live-registration.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 +525 -132
- 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 +211 -28
- 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/journal.d.ts +5 -0
- package/dist/native-terminal/journal.d.ts.map +1 -1
- package/dist/native-terminal/journal.js +28 -16
- package/dist/native-terminal/journal.js.map +1 -1
- package/dist/native-terminal/op.d.ts +49 -1
- package/dist/native-terminal/op.d.ts.map +1 -1
- package/dist/native-terminal/op.js +254 -38
- 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 +88 -20
- 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/terminal-reset.d.ts +104 -0
- package/dist/terminal-reset.d.ts.map +1 -0
- package/dist/terminal-reset.js +229 -0
- package/dist/terminal-reset.js.map +1 -0
- package/dist/tmux.d.ts +54 -8
- package/dist/tmux.d.ts.map +1 -1
- package/dist/tmux.js +143 -36
- package/dist/tmux.js.map +1 -1
- package/dist/vendor/engram-live/contracts.d.ts +825 -0
- package/dist/vendor/engram-live/contracts.js +1734 -0
- package/dist/vendor/engram-live/integration.d.ts +113 -0
- package/dist/vendor/engram-live/integration.js +254 -0
- 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 +138 -0
- package/dist/vendor/engram-live/registration.js +358 -0
- package/dist/vendor/engram-live/retention.d.ts +56 -0
- package/dist/vendor/engram-live/retention.js +179 -0
- package/package.json +6 -6
package/dist/index.js
CHANGED
|
@@ -1,15 +1,19 @@
|
|
|
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";
|
|
12
|
-
import { prepareCentralMcpForLaunch, prepareCentralMcpForRestore, } from "./central-mcp.js";
|
|
14
|
+
import { prepareCentralMcpForLaunch, prepareCentralMcpForRestore, resolveCentralMcpLogPath, } from "./central-mcp.js";
|
|
15
|
+
import { CRASH_LOG_COMPONENTS, formatCrashLogHumanRow, installCliWitnessOnce, installCrashHandlers, readFileTailLines, redactCrashText, readRecentCrashLogs, resolveCrashLogDir, } from "./crash-log.js";
|
|
16
|
+
import { readHostJournal, resolveHostJournalPath } from "./native-terminal/journal.js";
|
|
13
17
|
import { ensureConnected, stopTunnel } from "./tunnel.js";
|
|
14
18
|
import { detectToolAuth, KNOWN_TOOLS, partitionTools } from "./auth-tools.js";
|
|
15
19
|
import { transmittedSecrets, secretsSummary } from "./secrets.js";
|
|
@@ -20,13 +24,14 @@ import { attachLocalSession, attachPodTmux, capturePane, capturePaneVisible, cle
|
|
|
20
24
|
import { deliverInitialPrompt, sleepSync, } from "./prompt-delivery.js";
|
|
21
25
|
import { startLaunchGuard } from "./launch-guard.js";
|
|
22
26
|
import { buildLaunchContext } from "./launch-context.js";
|
|
23
|
-
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";
|
|
24
28
|
import { executeNativeRestart, NativeRestartError, } from "./native-restart.js";
|
|
25
29
|
import { migrateTmuxNames } from "./tmux-name-migration.js";
|
|
26
30
|
import { projectStatusForH2a } from "./status-projection.js";
|
|
27
31
|
import { buildAgentLaunchArgs, buildAgentLaunchStdin, isAgentLaunchEffort, isAgentLaunchProfile, } from "./agent-launch-args.js";
|
|
32
|
+
import { resolveGatewayModelAlias } from "./gateway-model-catalog/model-catalog.js";
|
|
28
33
|
import { deriveSessionClass } from "./session-class.js";
|
|
29
|
-
import { isRelaunchKillable, planRelaunch, relaunchContinuationPrompt, tryAcquireResumeLaunchClaim, wakeRelaunchedSession, } from "./relaunch.js";
|
|
34
|
+
import { isRelaunchKillable, planRelaunch, relaunchContinuationPrompt, scrubbedRetypeCommand, prepareGatewayRetypeCommand, tryAcquireResumeLaunchClaim, wakeRelaunchedSession, } from "./relaunch.js";
|
|
30
35
|
import { isHumanFacingSession, legacySessionEvidence, readConversationCustomTitle, readLastLayout, reconcileRunConvIds, restore as restoreLayout, } from "./restore.js";
|
|
31
36
|
import { getLayoutConfig } from "./config.js";
|
|
32
37
|
import { resolvePinnedLaunchEntries } from "./resume-launch-options.js";
|
|
@@ -40,7 +45,11 @@ import { DEFAULT_FANOUT_MAX, mapWithConcurrency, planRemoteFanout, } from "./fan
|
|
|
40
45
|
import { authenticateJobEnvelopes, buildDecisionReply, dropEnvelope, emitJobDone, envelopeFileName, isAwaitingDecision, jobInstance, parentInstance, pendingDecisions, readInboxEnvelopes, renderPendingDecisions, repliedDecisionJobIds, } from "./h2a-jobs.js";
|
|
41
46
|
import { buildConductorTask, computeDurableWorkspaceId, normalizeRootCommits, detectAvailableHosts, freshestLaunchEnvelope, h2aReportsLiveConductor, markLaunchEnvelopeProcessed, readLastLaunchAt, readLaunchEnvelopes, recordLaunchAt, selectHost, shouldLaunch, } from "./conductor-launch.js";
|
|
42
47
|
import { defaultLocalH2aRoot } from "./h2a-bridge.js";
|
|
43
|
-
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.
|
|
44
53
|
import { guardConvWriters } from "./conv-guard.js";
|
|
45
54
|
import { handleClaudeHook, installClaudeHooks, manualEnroll, readStdin, } from "./enroll.js";
|
|
46
55
|
import { probePodCredHealth, softRefreshSession } from "./soft-refresh.js";
|
|
@@ -72,8 +81,7 @@ import { createInterface } from "node:readline";
|
|
|
72
81
|
const H2A_RUN_API_VERSION = "h2a.run/v1";
|
|
73
82
|
const STRUCTURED_LAUNCH_PHASE_PREFIX = "[h2a] h2a.run.phase/v1 ";
|
|
74
83
|
const H2A_RUNTIME_VERSION = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")).version;
|
|
75
|
-
import { CLI_PROFILES, gatewayModeForProfile, profileUsesLlmMeshGateway, } from "./protocol-local.js";
|
|
76
|
-
import { enrollViaFacade, formatLlmMeshAccountError, formatLlmMeshAccountList, listAccountsViaFacade, removeAccountViaFacade, } from "./llm-mesh-accounts.js";
|
|
84
|
+
import { CLI_PROFILES, gatewayModeForProfile, profileUsesLlmMeshGateway, resolveResumedGatewayMode, } from "./protocol-local.js";
|
|
77
85
|
import { readLlmMeshConfig, updateLlmMeshRoutingConfig, } from "./gateway-host/config-file.js";
|
|
78
86
|
import { startGateway, stopGateway, readGatewayPid, llmMeshLogPath, replaceAnthropicGatewayEnvironment, acquireLlmMeshSessionEnv, } from "./gateway-host/daemon.js";
|
|
79
87
|
import { describeLlmMeshRoutingConfig, parseLlmMeshRoutingConfig, preferredRoutingConfig, strategyRoutingConfig, } from "./routing-preferences.js";
|
|
@@ -133,6 +141,7 @@ async function startRemoteFanout(remote, profileName, members, spec) {
|
|
|
133
141
|
? { startupArgs: spec.startupArgs }
|
|
134
142
|
: {}),
|
|
135
143
|
...(spec.credentials ? { credentials: spec.credentials } : {}),
|
|
144
|
+
...(spec.llmGateway === true ? { llmGateway: true } : {}),
|
|
136
145
|
});
|
|
137
146
|
return { name: member.name, sessionId: session.id, workspaceId: ws.id };
|
|
138
147
|
});
|
|
@@ -198,6 +207,20 @@ async function runProfile(profileName, opts, commandArgs = []) {
|
|
|
198
207
|
if (opts.resume !== undefined && resumeArgs.length === 0) {
|
|
199
208
|
throw new Error(`profile "${profileName}" has no verified resume argv; start it without -r/--resume`);
|
|
200
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 } : {};
|
|
201
224
|
const startupArgs = [...resumeArgs, ...commandArgs];
|
|
202
225
|
// WP6 — REMOTE fan-out: --count N spawns N concurrent remote sessions, each
|
|
203
226
|
// on its OWN workspace subPath of the shared RWX volume. count<=1 falls
|
|
@@ -227,6 +250,7 @@ async function runProfile(profileName, opts, commandArgs = []) {
|
|
|
227
250
|
target: opts.target,
|
|
228
251
|
startupArgs,
|
|
229
252
|
...(credentials ? { credentials } : {}),
|
|
253
|
+
...remoteGatewayChoice,
|
|
230
254
|
});
|
|
231
255
|
return;
|
|
232
256
|
}
|
|
@@ -245,6 +269,7 @@ async function runProfile(profileName, opts, commandArgs = []) {
|
|
|
245
269
|
...(opts.workspaceId ? { workspaceId: opts.workspaceId } : {}),
|
|
246
270
|
...(opts.browser ? { metadata: { browser: true } } : {}),
|
|
247
271
|
...(opts.agentImage ? { agentImage: opts.agentImage } : {}),
|
|
272
|
+
...remoteGatewayChoice,
|
|
248
273
|
});
|
|
249
274
|
if (archive) {
|
|
250
275
|
await uploadWorkspaceArchive(opts.remote, session.id, archive);
|
|
@@ -268,7 +293,21 @@ async function runProfile(profileName, opts, commandArgs = []) {
|
|
|
268
293
|
...(opts.port !== undefined ? { port: opts.port } : {}),
|
|
269
294
|
...(commandArgs.length > 0 ? { startupArgs: commandArgs } : {}),
|
|
270
295
|
};
|
|
271
|
-
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
|
+
}
|
|
272
311
|
process.stderr.write(`[h2a] session ${result.sessionId} attach at http://127.0.0.1:${result.port}\n`);
|
|
273
312
|
const { exitCode } = await result.exit;
|
|
274
313
|
process.exitCode = exitCode;
|
|
@@ -850,32 +889,12 @@ export async function startJob(job) {
|
|
|
850
889
|
const prevJobId = process.env[JOB_ID_ENV];
|
|
851
890
|
process.env[DEPTH_ENV] = childDepthEnvValue(job.depthBudget ?? clampDepth(undefined));
|
|
852
891
|
process.env[JOB_ID_ENV] = job.id;
|
|
853
|
-
//
|
|
854
|
-
//
|
|
855
|
-
//
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
const
|
|
859
|
-
? jobSessionResolution.session.name
|
|
860
|
-
: jobSessionResolution.kind === "missing"
|
|
861
|
-
? localSessionName(jobSessionSlug)
|
|
862
|
-
: undefined;
|
|
863
|
-
let meshEnv;
|
|
864
|
-
try {
|
|
865
|
-
meshEnv =
|
|
866
|
-
profileUsesLlmMeshGateway(job.tool) &&
|
|
867
|
-
getLlmMeshRuntimeConfig().enabled &&
|
|
868
|
-
gatewayClientSessionId
|
|
869
|
-
? await acquireLlmMeshSessionEnv(undefined, gatewayClientSessionId)
|
|
870
|
-
: null;
|
|
871
|
-
}
|
|
872
|
-
catch (error) {
|
|
873
|
-
return {
|
|
874
|
-
started: false,
|
|
875
|
-
error: error instanceof Error ? error.message : String(error),
|
|
876
|
-
};
|
|
877
|
-
}
|
|
878
|
-
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);
|
|
879
898
|
let tmuxSession;
|
|
880
899
|
let workerPid;
|
|
881
900
|
let agentPane;
|
|
@@ -1397,13 +1416,12 @@ export function bareChoiceFromOptions(opts) {
|
|
|
1397
1416
|
return false;
|
|
1398
1417
|
return undefined;
|
|
1399
1418
|
}
|
|
1400
|
-
async function injectLlmMeshGatewayEnv(mode = "auto", allowDirectFallback = true, clientSessionId) {
|
|
1401
|
-
if (mode
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
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.
|
|
1407
1425
|
delete process.env.ANTHROPIC_BASE_URL;
|
|
1408
1426
|
delete process.env.ANTHROPIC_AUTH_TOKEN;
|
|
1409
1427
|
return undefined;
|
|
@@ -1434,7 +1452,7 @@ async function injectLlmMeshGatewayEnv(mode = "auto", allowDirectFallback = true
|
|
|
1434
1452
|
delete process.env.ANTHROPIC_BASE_URL;
|
|
1435
1453
|
delete process.env.ANTHROPIC_AUTH_TOKEN;
|
|
1436
1454
|
if (mode === "gateway") {
|
|
1437
|
-
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");
|
|
1438
1456
|
}
|
|
1439
1457
|
if (allowDirectFallback) {
|
|
1440
1458
|
process.stderr.write("[h2a] llm-mesh: gateway unavailable; continuing with native authentication.\n");
|
|
@@ -1516,7 +1534,7 @@ function renderNativeRestartResult(result, json) {
|
|
|
1516
1534
|
export async function prepareStructuredGateway(mode, inject = (selectedMode) => injectLlmMeshGatewayEnv(selectedMode, selectedMode !== "gateway")) {
|
|
1517
1535
|
const gateway = await inject(mode);
|
|
1518
1536
|
if (mode === "gateway" && !gateway) {
|
|
1519
|
-
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");
|
|
1520
1538
|
}
|
|
1521
1539
|
return gateway;
|
|
1522
1540
|
}
|
|
@@ -1527,7 +1545,9 @@ export async function prepareLlmMeshForRestore(opts = {}, context = {
|
|
|
1527
1545
|
injectGateway: (mode) => injectLlmMeshGatewayEnv(mode, mode !== "gateway"),
|
|
1528
1546
|
}) {
|
|
1529
1547
|
const mode = opts.mode ?? "auto";
|
|
1530
|
-
|
|
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")
|
|
1531
1551
|
return;
|
|
1532
1552
|
const config = context.config ?? {};
|
|
1533
1553
|
const port = config.port ?? 3002;
|
|
@@ -1718,7 +1738,45 @@ function setAndReportDefaultRemote(url) {
|
|
|
1718
1738
|
ensureManagedTmuxProfile(getTmuxProfileConfig().profile);
|
|
1719
1739
|
process.stderr.write(`[h2a] default remote set to ${configured}\n`);
|
|
1720
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
|
+
}
|
|
1721
1775
|
export async function main(argv) {
|
|
1776
|
+
// Passive fatal-path witness on the real entry path (see
|
|
1777
|
+
// installCliWitnessOnce): observes via uncaughtExceptionMonitor, so the
|
|
1778
|
+
// default crash/exit behavior is unchanged.
|
|
1779
|
+
installCliWitnessOnce();
|
|
1722
1780
|
// ②b — auto-migrate the config home remote-cli/ -> h2a/ on first run (backup +
|
|
1723
1781
|
// compat symlink). Best-effort: never let it break a command. Runs once
|
|
1724
1782
|
// (idempotent) before any config/registry read.
|
|
@@ -1767,22 +1825,34 @@ export async function main(argv) {
|
|
|
1767
1825
|
"the separate remote and track CLIs. The heavy session runtime loads on " +
|
|
1768
1826
|
"demand. h2a runs and coordinates agents; it is not itself an agent.")
|
|
1769
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
|
+
});
|
|
1770
1847
|
const identityCommand = program
|
|
1771
1848
|
.command("identity")
|
|
1772
1849
|
.description("Inspect identity bindings or unlock a token-fenced identity lock");
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
if (!result.broken) {
|
|
1780
|
-
process.stderr.write(`[h2a] lock break refused: ${result.reason}${result.pid !== undefined ? ` (pid ${result.pid})` : ""}\n`);
|
|
1781
|
-
if (result.reason === "assert-dead-required") {
|
|
1782
|
-
process.stderr.write("[h2a] verify the holder has stopped, then explicitly pass --assert-dead; no PID is killed\n");
|
|
1783
|
-
}
|
|
1784
|
-
process.exitCode = 1;
|
|
1785
|
-
}
|
|
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; });
|
|
1786
1856
|
};
|
|
1787
1857
|
identityCommand
|
|
1788
1858
|
.command("unlock")
|
|
@@ -1812,9 +1882,18 @@ export async function main(argv) {
|
|
|
1812
1882
|
.option("--execute", "refuse destructive execution; no bypass exists")
|
|
1813
1883
|
.option("--root <path>", "DEF h2a root (default: H2A_ROOT or ~/h2a-workspace/.h2a)")
|
|
1814
1884
|
.option("--output <path>", "new external directory for the proof packet")
|
|
1815
|
-
.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
|
+
}
|
|
1816
1895
|
if (opts.execute) {
|
|
1817
|
-
const refusal = refuseCullExecution();
|
|
1896
|
+
const refusal = cull.refuseCullExecution();
|
|
1818
1897
|
process.stderr.write(`[h2a] identity cull refused: ${refusal.refusalCodes.join(", ")}\n`);
|
|
1819
1898
|
process.exitCode = 2;
|
|
1820
1899
|
return;
|
|
@@ -1822,7 +1901,7 @@ export async function main(argv) {
|
|
|
1822
1901
|
const defRoot = opts.root ?? process.env.H2A_ROOT ?? defaultLocalH2aRoot();
|
|
1823
1902
|
const outputDir = opts.output ?? join(tmpdir(), `h2a-identity-cull-${randomUUID()}`);
|
|
1824
1903
|
try {
|
|
1825
|
-
const result = runIdentityCullDryRun({
|
|
1904
|
+
const result = cull.runIdentityCullDryRun({
|
|
1826
1905
|
defRoot,
|
|
1827
1906
|
pinRoot: join(homedir(), "src", "a2a-cli"),
|
|
1828
1907
|
outputDir,
|
|
@@ -1949,6 +2028,10 @@ export async function main(argv) {
|
|
|
1949
2028
|
.option("--name <label>", "base label for the --count fan-out fleet names (default: cwd basename)")
|
|
1950
2029
|
.option("--browser", "add the headful-browser noVNC sidecar (WP7) — enables 2FA / authenticated-site flows from inside the pod")
|
|
1951
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")
|
|
1952
2035
|
.action(async (commandArgs, opts) => {
|
|
1953
2036
|
const { remote: remoteOverride, local, workspace, ...rest } = opts;
|
|
1954
2037
|
if (local) {
|
|
@@ -3634,6 +3717,8 @@ export async function main(argv) {
|
|
|
3634
3717
|
process.exitCode = 1;
|
|
3635
3718
|
return;
|
|
3636
3719
|
}
|
|
3720
|
+
const sameResumeIdentity = resolvedConvId !== undefined && registryEntry?.convId === resolvedConvId &&
|
|
3721
|
+
registryEntry?.cwd === process.cwd() && registryEntry?.tool === explicitProfile;
|
|
3637
3722
|
const entry = explicitProfile
|
|
3638
3723
|
? {
|
|
3639
3724
|
id: displaySlug,
|
|
@@ -3648,8 +3733,9 @@ export async function main(argv) {
|
|
|
3648
3733
|
enrolledAt: new Date().toISOString(),
|
|
3649
3734
|
lastSeenAt: new Date().toISOString(),
|
|
3650
3735
|
source: "run",
|
|
3651
|
-
bare:
|
|
3652
|
-
gatewayMode:
|
|
3736
|
+
bare: sameResumeIdentity ? registryEntry?.bare : undefined,
|
|
3737
|
+
gatewayMode: sameResumeIdentity ? registryEntry?.gatewayMode : undefined,
|
|
3738
|
+
gatewayProvenance: sameResumeIdentity ? registryEntry?.gatewayProvenance : undefined,
|
|
3653
3739
|
}
|
|
3654
3740
|
: registryEntry;
|
|
3655
3741
|
// WHICH host serves this resume — resolved BEFORE ANY act (F1): every
|
|
@@ -3747,7 +3833,7 @@ export async function main(argv) {
|
|
|
3747
3833
|
process.exitCode = 2;
|
|
3748
3834
|
return;
|
|
3749
3835
|
}
|
|
3750
|
-
const effectiveGatewayMode =
|
|
3836
|
+
const effectiveGatewayMode = resolveResumedGatewayMode(profile, gatewayMode, entry.gatewayMode, entry.gatewayProvenance);
|
|
3751
3837
|
let gateway;
|
|
3752
3838
|
// Resolve against the selected mode, not the parent's environment:
|
|
3753
3839
|
// required gateway injection below must succeed before either launch.
|
|
@@ -3960,7 +4046,11 @@ export async function main(argv) {
|
|
|
3960
4046
|
cwd: entry.cwd,
|
|
3961
4047
|
sessionClass,
|
|
3962
4048
|
...(entry.convId ? { convId: entry.convId } : {}),
|
|
3963
|
-
|
|
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
|
+
: {}),
|
|
3964
4054
|
bare: useBare,
|
|
3965
4055
|
});
|
|
3966
4056
|
process.stderr.write(`[h2a] resumed local session ${resumeSlug} (${profile} resume${entry.convId ? ` ${entry.convId}` : ""} in ${entry.cwd})\n`);
|
|
@@ -3991,8 +4081,11 @@ export async function main(argv) {
|
|
|
3991
4081
|
.option("--prompt-stdin", "read the initial Claude/Codex/AGY prompt from stdin (keeps it out of process argv)")
|
|
3992
4082
|
.option("--agent <agent>", "AGY agent override (for example: stp)")
|
|
3993
4083
|
.option("--model <model>", "Claude/Codex/AGY model override")
|
|
3994
|
-
.option("--
|
|
3995
|
-
.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>")
|
|
3996
4089
|
.option("--background", "classify this detached session as a background worker excluded from human restore")
|
|
3997
4090
|
.option("--json", "emit one machine-readable h2a.run.result object")
|
|
3998
4091
|
.option("--llm-gateway", "launch the CLI through the local llm-mesh gateway")
|
|
@@ -4014,13 +4107,30 @@ export async function main(argv) {
|
|
|
4014
4107
|
process.exitCode = 2;
|
|
4015
4108
|
return;
|
|
4016
4109
|
}
|
|
4017
|
-
|
|
4018
|
-
|
|
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`);
|
|
4019
4129
|
process.exitCode = 2;
|
|
4020
4130
|
return;
|
|
4021
4131
|
}
|
|
4022
|
-
if (
|
|
4023
|
-
process.stderr.write(
|
|
4132
|
+
if (opts.effort !== undefined && !isAgentLaunchEffort(opts.effort)) {
|
|
4133
|
+
process.stderr.write(`[h2a] --effort must be low|medium|high|xhigh\n`);
|
|
4024
4134
|
process.exitCode = 2;
|
|
4025
4135
|
return;
|
|
4026
4136
|
}
|
|
@@ -4166,13 +4276,11 @@ export async function main(argv) {
|
|
|
4166
4276
|
}
|
|
4167
4277
|
};
|
|
4168
4278
|
try {
|
|
4169
|
-
|
|
4170
|
-
|
|
4171
|
-
|
|
4172
|
-
|
|
4173
|
-
const reservedTmuxSlugs =
|
|
4174
|
-
? existingLocalSessionSlugs(labels, cwd)
|
|
4175
|
-
: [];
|
|
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);
|
|
4176
4284
|
// The inventory and this decision are separate reads: a structured
|
|
4177
4285
|
// launch can leave a name visible just before its last tmux process
|
|
4178
4286
|
// exits. Re-probe every canonical/legacy spelling and reclaim the slug
|
|
@@ -4207,14 +4315,16 @@ export async function main(argv) {
|
|
|
4207
4315
|
// twin over a live-but-unprovable native writer (sol-F3): the same
|
|
4208
4316
|
// fail-closed rule as resolveManagedHost's rule 4 applies — refuse
|
|
4209
4317
|
// to launch on an unprovable host state.
|
|
4210
|
-
const nativeExistence = labels
|
|
4318
|
+
const nativeExistence = await Promise.all(labels
|
|
4211
4319
|
.map((label) => slugify(label ?? cwd))
|
|
4212
|
-
.map((slug) => {
|
|
4213
|
-
const detail =
|
|
4320
|
+
.map(async (slug) => {
|
|
4321
|
+
const detail = sessionHost === "native"
|
|
4322
|
+
? await nativeSessionStateAsync(localSessionName(slug), true)
|
|
4323
|
+
: nativeSessionState(localSessionName(slug));
|
|
4214
4324
|
const probe = detail.state === "unknown" ? "unknown"
|
|
4215
4325
|
: detail.state === "found" && detail.session.status === "running" ? "live" : "dead";
|
|
4216
4326
|
return { slug, probe, reason: detail.state === "unknown" ? detail.reason : undefined };
|
|
4217
|
-
});
|
|
4327
|
+
}));
|
|
4218
4328
|
const unknownNativeSlugs = nativeExistence
|
|
4219
4329
|
.filter((probed) => probed.probe === "unknown");
|
|
4220
4330
|
if (unknownNativeSlugs.length > 0) {
|
|
@@ -4259,9 +4369,15 @@ export async function main(argv) {
|
|
|
4259
4369
|
// Single-writer guard: refuse to resume a conversation another live
|
|
4260
4370
|
// session (local registry / remote pod) is already writing.
|
|
4261
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"]));
|
|
4262
4377
|
const ok = await guardConvWriters({
|
|
4263
4378
|
convId: opts.resume,
|
|
4264
4379
|
cwd,
|
|
4380
|
+
nativeSessionLiveness: name => nativeLivenessByName.get(name) ?? nativeSessionLiveness(name),
|
|
4265
4381
|
...(opts.force ? { force: true } : {}),
|
|
4266
4382
|
fetchRemoteSessions: async () => {
|
|
4267
4383
|
const url = getConfiguredRemoteOptional();
|
|
@@ -4290,8 +4406,11 @@ export async function main(argv) {
|
|
|
4290
4406
|
for (const prunedId of resumeResolution.prunedIds) {
|
|
4291
4407
|
process.stderr.write(`[h2a] pruned dead registry row ${prunedId} that pinned conflicting launch options for this resume\n`);
|
|
4292
4408
|
}
|
|
4293
|
-
|
|
4294
|
-
|
|
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);
|
|
4295
4414
|
const command = localCliCommand(profile);
|
|
4296
4415
|
// A detached/background or run-once launch is a worker, even when its
|
|
4297
4416
|
// agent later emits a Claude SessionStart hook. Stamp this through tmux
|
|
@@ -4335,6 +4454,17 @@ export async function main(argv) {
|
|
|
4335
4454
|
return;
|
|
4336
4455
|
}
|
|
4337
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;
|
|
4338
4468
|
let args;
|
|
4339
4469
|
try {
|
|
4340
4470
|
args =
|
|
@@ -4345,7 +4475,7 @@ export async function main(argv) {
|
|
|
4345
4475
|
? { prompt: initialPrompt }
|
|
4346
4476
|
: {}),
|
|
4347
4477
|
...(opts.agent !== undefined ? { agent: opts.agent } : {}),
|
|
4348
|
-
...(
|
|
4478
|
+
...(runModel !== undefined ? { model: runModel } : {}),
|
|
4349
4479
|
...(opts.effort !== undefined
|
|
4350
4480
|
? { effort: opts.effort }
|
|
4351
4481
|
: {}),
|
|
@@ -4371,6 +4501,10 @@ export async function main(argv) {
|
|
|
4371
4501
|
}
|
|
4372
4502
|
let name;
|
|
4373
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;
|
|
4374
4508
|
let outputLog;
|
|
4375
4509
|
let resultJson;
|
|
4376
4510
|
let agentPane;
|
|
@@ -4383,6 +4517,10 @@ export async function main(argv) {
|
|
|
4383
4517
|
mkdirSync(runDir, { recursive: true });
|
|
4384
4518
|
outputLog = join(runDir, "output.log");
|
|
4385
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 });
|
|
4386
4524
|
const headlessPromptInput = structuredLaunch && isAgentLaunchProfile(profile)
|
|
4387
4525
|
? buildAgentLaunchStdin({
|
|
4388
4526
|
profile,
|
|
@@ -4396,14 +4534,16 @@ export async function main(argv) {
|
|
|
4396
4534
|
({ name, slug, promptFile } = startNativeHeadlessSession(profile, command, cwd, args, resultJson, outputLog, label, headlessPromptInput, structuredLaunch, sessionClass, onCreateAttempt, structuredLaunch ? (owned) => {
|
|
4397
4535
|
launchOwnership = { host: "native", sessions: [owned] };
|
|
4398
4536
|
launchGuard = startLaunchGuard(runDir, launchOwnership);
|
|
4399
|
-
} : undefined));
|
|
4537
|
+
} : undefined, opts.outputFile ? resolve(cwd, opts.outputFile) : undefined));
|
|
4400
4538
|
}
|
|
4401
4539
|
else {
|
|
4402
|
-
({ 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));
|
|
4403
4541
|
}
|
|
4404
4542
|
}
|
|
4405
4543
|
else if (sessionHost === "native") {
|
|
4406
|
-
|
|
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, {
|
|
4407
4547
|
onCreateAttempt,
|
|
4408
4548
|
...(opts.resume !== undefined ? { resumeId: opts.resume } : {}),
|
|
4409
4549
|
...(initialPrompt !== undefined
|
|
@@ -4532,6 +4672,18 @@ export async function main(argv) {
|
|
|
4532
4672
|
now: () => Date.now(),
|
|
4533
4673
|
}, { profile });
|
|
4534
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. */ }
|
|
4535
4687
|
cleanupHeadlessPromptFile(promptFile);
|
|
4536
4688
|
// Only claim the session is gone when the kill actually reported
|
|
4537
4689
|
// success. "nothing is left running" over an unchecked call is the
|
|
@@ -4561,26 +4713,64 @@ export async function main(argv) {
|
|
|
4561
4713
|
? `[h2a] last screen:\n${promptDelivery.capture}\n`
|
|
4562
4714
|
: ""));
|
|
4563
4715
|
process.exitCode = 1;
|
|
4564
|
-
if (opts.json && promptDelivery.state === "provider-blocked") {
|
|
4716
|
+
if (opts.json && (promptDelivery.state === "provider-blocked" || quotaFailure)) {
|
|
4565
4717
|
process.stdout.write(`${JSON.stringify({ kind: "h2a.run.failure", version: 1,
|
|
4566
|
-
state: "provider-blocked", launchId: slug, error: promptDelivery.reason,
|
|
4567
|
-
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 } : {}),
|
|
4568
4722
|
})}\n`);
|
|
4569
4723
|
}
|
|
4570
4724
|
return;
|
|
4571
4725
|
}
|
|
4572
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).
|
|
4573
4760
|
const pid = sessionHost === "native"
|
|
4574
|
-
? nativeSessionPid(name)
|
|
4761
|
+
? (launchedPid ?? nativeSessionPid(name))
|
|
4575
4762
|
: agentPane
|
|
4576
4763
|
? localSessionPanePid(agentPane)
|
|
4577
4764
|
: undefined;
|
|
4578
4765
|
const workerPid = sessionHost === "native"
|
|
4579
|
-
? nativeWorkerPid(name)
|
|
4766
|
+
? (launchedWorkerPid ?? nativeWorkerPid(name))
|
|
4580
4767
|
: agentPane !== undefined
|
|
4581
4768
|
? paneWorkerPid(agentPane)
|
|
4582
4769
|
: undefined;
|
|
4583
4770
|
if (structuredLaunch && pid === undefined) {
|
|
4771
|
+
// The supervisor can publish and exit DURING the PID probe.
|
|
4772
|
+
if (reportAgyCompletion())
|
|
4773
|
+
return;
|
|
4584
4774
|
cleanupHeadlessPromptFile(promptFile);
|
|
4585
4775
|
// OWN-CLEANUP, deliberately NOT resolver-derived (F1 sorting):
|
|
4586
4776
|
// `name`/`sessionHost` are the session and host THIS command
|
|
@@ -4596,8 +4786,8 @@ export async function main(argv) {
|
|
|
4596
4786
|
return;
|
|
4597
4787
|
}
|
|
4598
4788
|
// Auto-enroll in the live-session registry (feeds `h2a ls`/`restore`).
|
|
4599
|
-
//
|
|
4600
|
-
//
|
|
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.
|
|
4601
4791
|
enrollFromRun({
|
|
4602
4792
|
profile,
|
|
4603
4793
|
slug,
|
|
@@ -4609,8 +4799,10 @@ export async function main(argv) {
|
|
|
4609
4799
|
cwd,
|
|
4610
4800
|
sessionClass,
|
|
4611
4801
|
...(opts.resume !== undefined ? { convId: opts.resume } : {}),
|
|
4612
|
-
gatewayMode
|
|
4613
|
-
|
|
4802
|
+
...(gatewayMode !== "auto" || pinnedLaunch?.gatewayProvenance === "explicit"
|
|
4803
|
+
? { gatewayMode: launchGatewayMode, gatewayProvenance: "explicit" }
|
|
4804
|
+
: {}),
|
|
4805
|
+
...(enrollmentBare !== undefined ? { bare: enrollmentBare } : {}),
|
|
4614
4806
|
});
|
|
4615
4807
|
const nativeOwnerSocket = launchOwnership?.host === "native"
|
|
4616
4808
|
? launchOwnership.sessions.find(owned => owned.name === name)?.socketPath : undefined;
|
|
@@ -4650,6 +4842,8 @@ export async function main(argv) {
|
|
|
4650
4842
|
tmuxSession: only.name,
|
|
4651
4843
|
host: sessionHost === "native" ? "native" : "tmux",
|
|
4652
4844
|
profile,
|
|
4845
|
+
...(opts.model ? { model: opts.model, modelSource: "launch-argument", effectiveModel: "unknown" } : {}),
|
|
4846
|
+
...(profile === "agy" && isAgyClaudeModel(opts.model) ? { quota: readAgyClaudeQuotaStatus() } : {}),
|
|
4653
4847
|
workspace: cwd,
|
|
4654
4848
|
mode: opts.headless ? "headless" : "interactive",
|
|
4655
4849
|
background: opts.background === true,
|
|
@@ -6134,6 +6328,9 @@ export async function main(argv) {
|
|
|
6134
6328
|
...(entry.gatewayMode !== undefined
|
|
6135
6329
|
? { gatewayMode: entry.gatewayMode }
|
|
6136
6330
|
: {}),
|
|
6331
|
+
...(entry.gatewayProvenance !== undefined
|
|
6332
|
+
? { gatewayProvenance: entry.gatewayProvenance }
|
|
6333
|
+
: {}),
|
|
6137
6334
|
...(entry.bare !== undefined ? { bare: entry.bare } : {}),
|
|
6138
6335
|
}));
|
|
6139
6336
|
const result = await executeNativeRestart({
|
|
@@ -6188,7 +6385,9 @@ export async function main(argv) {
|
|
|
6188
6385
|
...(candidate.convId !== undefined
|
|
6189
6386
|
? { convId: candidate.convId }
|
|
6190
6387
|
: {}),
|
|
6191
|
-
...(mode !== "auto"
|
|
6388
|
+
...(mode !== "auto"
|
|
6389
|
+
? { gatewayMode: mode, gatewayProvenance: "explicit" }
|
|
6390
|
+
: {}),
|
|
6192
6391
|
...(candidate.bare !== undefined ? { bare: candidate.bare } : {}),
|
|
6193
6392
|
});
|
|
6194
6393
|
}
|
|
@@ -6301,7 +6500,7 @@ export async function main(argv) {
|
|
|
6301
6500
|
process.exitCode = 1;
|
|
6302
6501
|
return;
|
|
6303
6502
|
}
|
|
6304
|
-
const relaunchMode =
|
|
6503
|
+
const relaunchMode = resolveResumedGatewayMode(nativeEntry.tool, "auto", nativeEntry.gatewayMode, nativeEntry.gatewayProvenance);
|
|
6305
6504
|
await injectLlmMeshGatewayEnv(relaunchMode, relaunchMode !== "gateway", actName);
|
|
6306
6505
|
if (!killNativeSessionTree(actName)) {
|
|
6307
6506
|
process.stderr.write(`[h2a] native session ${slug} could not be killed; nothing was relaunched.\n`);
|
|
@@ -6328,7 +6527,15 @@ export async function main(argv) {
|
|
|
6328
6527
|
...(relaunched.pid !== undefined ? { pid: relaunched.pid } : {}),
|
|
6329
6528
|
cwd: nativeEntry.cwd,
|
|
6330
6529
|
sessionClass: nativeEntry.sessionClass ?? "human",
|
|
6331
|
-
|
|
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
|
+
: {}),
|
|
6332
6539
|
bare: useBare,
|
|
6333
6540
|
...(nativeEntry.convId ? { convId: nativeEntry.convId } : {}),
|
|
6334
6541
|
});
|
|
@@ -6458,14 +6665,55 @@ export async function main(argv) {
|
|
|
6458
6665
|
if (!forced) {
|
|
6459
6666
|
let ok = 0;
|
|
6460
6667
|
for (const action of plan.actions) {
|
|
6461
|
-
|
|
6462
|
-
|
|
6463
|
-
|
|
6464
|
-
|
|
6465
|
-
|
|
6466
|
-
|
|
6467
|
-
|
|
6468
|
-
|
|
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
|
+
}
|
|
6469
6717
|
if (relaunched) {
|
|
6470
6718
|
ok += 1;
|
|
6471
6719
|
process.stderr.write(`[h2a] relaunched ${action.slug}: ${action.cmd}\n`);
|
|
@@ -6541,6 +6789,11 @@ export async function main(argv) {
|
|
|
6541
6789
|
}
|
|
6542
6790
|
finalReady.push(item);
|
|
6543
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();
|
|
6544
6797
|
let ok = 0;
|
|
6545
6798
|
for (const { action, entry, sessionClass } of finalReady) {
|
|
6546
6799
|
const safety = sessionRelaunchSafety(action.name);
|
|
@@ -6567,8 +6820,9 @@ export async function main(argv) {
|
|
|
6567
6820
|
process.stderr.write(`[h2a] skipped ${action.slug}: tmux session ${action.name} changed or was no longer proven dead immediately before kill.\n`);
|
|
6568
6821
|
continue;
|
|
6569
6822
|
}
|
|
6823
|
+
const restoreLoopEnv = replaceAnthropicGatewayEnvironment(process.env, loopEntryGatewayEnv);
|
|
6570
6824
|
try {
|
|
6571
|
-
const gatewayMode =
|
|
6825
|
+
const gatewayMode = resolveResumedGatewayMode(action.profile, "auto", entry.gatewayMode, entry.gatewayProvenance);
|
|
6572
6826
|
await injectLlmMeshGatewayEnv(gatewayMode, true, localSessionName(action.slug));
|
|
6573
6827
|
const { name, agentPane } = startLocalSession(action.profile, action.command, entry.cwd, localResumeArgs(action.profile, action.convId, {
|
|
6574
6828
|
bare: resolveClaudeBare(action.profile, entry.bare),
|
|
@@ -6590,6 +6844,9 @@ export async function main(argv) {
|
|
|
6590
6844
|
...(entry.gatewayMode !== undefined
|
|
6591
6845
|
? { gatewayMode: entry.gatewayMode }
|
|
6592
6846
|
: {}),
|
|
6847
|
+
...(entry.gatewayProvenance !== undefined
|
|
6848
|
+
? { gatewayProvenance: entry.gatewayProvenance }
|
|
6849
|
+
: {}),
|
|
6593
6850
|
...(entry.bare !== undefined ? { bare: entry.bare } : {}),
|
|
6594
6851
|
});
|
|
6595
6852
|
const wake = wakeRelaunchedSession(relaunchContinuationPrompt(entry.task), {
|
|
@@ -6622,6 +6879,9 @@ export async function main(argv) {
|
|
|
6622
6879
|
catch (error) {
|
|
6623
6880
|
process.stderr.write(`[h2a] FAILED to force-restart ${action.slug} after killing ${action.name}: ${error.message}\n`);
|
|
6624
6881
|
}
|
|
6882
|
+
finally {
|
|
6883
|
+
restoreLoopEnv();
|
|
6884
|
+
}
|
|
6625
6885
|
}
|
|
6626
6886
|
process.stderr.write(`[h2a] force-restarted ${ok}/${plan.actions.length}${plan.skipped.length ? `, ${plan.skipped.length} skipped` : ""}\n`);
|
|
6627
6887
|
if (ok !== plan.actions.length)
|
|
@@ -6985,13 +7245,13 @@ export async function main(argv) {
|
|
|
6985
7245
|
}
|
|
6986
7246
|
if (opts.reattach)
|
|
6987
7247
|
restoreOpts.reattach = true;
|
|
6988
|
-
//
|
|
6989
|
-
//
|
|
6990
|
-
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") {
|
|
6991
7251
|
try {
|
|
6992
7252
|
await prepareLlmMeshForRestore({
|
|
6993
7253
|
dryRun: Boolean(opts.dryRun),
|
|
6994
|
-
mode: forceGateway
|
|
7254
|
+
mode: forceGateway,
|
|
6995
7255
|
});
|
|
6996
7256
|
}
|
|
6997
7257
|
catch (error) {
|
|
@@ -7525,13 +7785,29 @@ export async function main(argv) {
|
|
|
7525
7785
|
llmMeshAccountCommand
|
|
7526
7786
|
.command("enroll <provider>")
|
|
7527
7787
|
.description("Enroll through the sentropic-owned OAuth state machine " +
|
|
7528
|
-
"(cloud-code, codex, muse OAuth device flow,
|
|
7529
|
-
"--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")
|
|
7530
7792
|
.option("--config-ref <ref>", "sentropic configuration reference for OAuth")
|
|
7531
|
-
.option("--
|
|
7532
|
-
.
|
|
7533
|
-
|
|
7534
|
-
|
|
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;
|
|
7535
7811
|
if (normalized === "muse-code") {
|
|
7536
7812
|
// Owner decision 2026-10-10: muse-code is no longer a user entry —
|
|
7537
7813
|
// muse enrolls via the OAuth device flow by default, --cli selects
|
|
@@ -7541,20 +7817,37 @@ export async function main(argv) {
|
|
|
7541
7817
|
process.exitCode = 1;
|
|
7542
7818
|
return;
|
|
7543
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
|
+
}
|
|
7544
7835
|
if (normalized !== "cloud-code" &&
|
|
7545
7836
|
normalized !== "codex" &&
|
|
7546
7837
|
normalized !== "muse" &&
|
|
7547
|
-
normalized !== "mistral-vibe"
|
|
7548
|
-
|
|
7549
|
-
|
|
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");
|
|
7550
7842
|
process.exitCode = 1;
|
|
7551
7843
|
return;
|
|
7552
7844
|
}
|
|
7553
7845
|
try {
|
|
7554
|
-
const
|
|
7555
|
-
|
|
7556
|
-
|
|
7557
|
-
|
|
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 } : {}) });
|
|
7558
7851
|
process.stdout.write(`[h2a] llm-mesh account: enrolled ${account.provider}; ` +
|
|
7559
7852
|
"account inventory remains in Sentropic\n");
|
|
7560
7853
|
// The gateway loads the account inventory at boot only: an account
|
|
@@ -7576,6 +7869,7 @@ export async function main(argv) {
|
|
|
7576
7869
|
.description("List public accounts owned by this local llm-mesh scope")
|
|
7577
7870
|
.option("--json", "emit public account metadata as JSON")
|
|
7578
7871
|
.action(async (opts) => {
|
|
7872
|
+
const { listAccountsViaFacade, formatLlmMeshAccountList, formatLlmMeshAccountError } = await import("./llm-mesh-accounts.js");
|
|
7579
7873
|
try {
|
|
7580
7874
|
const accounts = await listAccountsViaFacade();
|
|
7581
7875
|
process.stdout.write(`${formatLlmMeshAccountList(accounts, opts.json === true)}\n`);
|
|
@@ -7590,6 +7884,7 @@ export async function main(argv) {
|
|
|
7590
7884
|
.aliases(["rm", "unenroll"])
|
|
7591
7885
|
.description("Remove one account owned by this local llm-mesh scope")
|
|
7592
7886
|
.action(async (accountId) => {
|
|
7887
|
+
const { removeAccountViaFacade, formatLlmMeshAccountError } = await import("./llm-mesh-accounts.js");
|
|
7593
7888
|
try {
|
|
7594
7889
|
const result = await removeAccountViaFacade(accountId);
|
|
7595
7890
|
process.stdout.write(`[h2a] llm-mesh account: removed ${result.accountId}\n`);
|
|
@@ -7655,17 +7950,15 @@ export async function main(argv) {
|
|
|
7655
7950
|
const config = readLlmMeshConfig() ?? {};
|
|
7656
7951
|
const existing = readGatewayPid();
|
|
7657
7952
|
if (existing) {
|
|
7658
|
-
|
|
7953
|
+
// Starting the service is not routing consent: launches stay direct
|
|
7954
|
+
// unless --gw is passed, so the durable flag is left untouched.
|
|
7659
7955
|
process.stdout.write(`[h2a] llm-mesh: already running (pid ${existing})\n`);
|
|
7660
|
-
process.stdout.write(`[h2a] llm-mesh: restore config enabled\n`);
|
|
7661
7956
|
return;
|
|
7662
7957
|
}
|
|
7663
7958
|
process.stdout.write(`[h2a] llm-mesh: starting gateway…\n`);
|
|
7664
7959
|
const result = await startGateway(config, { verbose: opts.verbose });
|
|
7665
|
-
setLlmMeshRuntimeConfig({ enabled: true });
|
|
7666
7960
|
const port = result.port;
|
|
7667
7961
|
process.stdout.write(`[h2a] llm-mesh: gateway started (pid ${result.pid}, port ${port})\n`);
|
|
7668
|
-
process.stdout.write(`[h2a] llm-mesh: restore config enabled\n`);
|
|
7669
7962
|
process.stdout.write("[h2a] llm-mesh: use `h2a run claude --gw`; the opaque bearer is not printed\n");
|
|
7670
7963
|
});
|
|
7671
7964
|
llmMeshCommand
|
|
@@ -7693,6 +7986,9 @@ export async function main(argv) {
|
|
|
7693
7986
|
else {
|
|
7694
7987
|
process.stdout.write(`[h2a] llm-mesh: not running\n`);
|
|
7695
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 });
|
|
7696
7992
|
});
|
|
7697
7993
|
llmMeshCommand
|
|
7698
7994
|
.command("restart")
|
|
@@ -7709,9 +8005,9 @@ export async function main(argv) {
|
|
|
7709
8005
|
}
|
|
7710
8006
|
process.stdout.write(`[h2a] llm-mesh: starting gateway…\n`);
|
|
7711
8007
|
const result = await startGateway(config, { verbose: opts.verbose });
|
|
7712
|
-
|
|
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.
|
|
7713
8010
|
process.stdout.write(`[h2a] llm-mesh: gateway restarted (pid ${result.pid}, port ${result.port})\n`);
|
|
7714
|
-
process.stdout.write(`[h2a] llm-mesh: restore config enabled\n`);
|
|
7715
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`);
|
|
7716
8012
|
process.stdout.write("[h2a] llm-mesh: use `h2a run claude --gw`; the opaque bearer is not printed\n");
|
|
7717
8013
|
});
|
|
@@ -7771,6 +8067,75 @@ export async function main(argv) {
|
|
|
7771
8067
|
? `[h2a] lineage ${id} resumed\n`
|
|
7772
8068
|
: `[h2a] lineage ${id} was not suspended (no-op)\n`);
|
|
7773
8069
|
});
|
|
8070
|
+
// ---------------------------------------------------------------------------
|
|
8071
|
+
// logs — last crash-log incidents (native host journal + per-component logs)
|
|
8072
|
+
// ---------------------------------------------------------------------------
|
|
8073
|
+
program
|
|
8074
|
+
.command("logs")
|
|
8075
|
+
.description("Show the last crash-log incidents: native host journal plus cli/attach/mcp-server logs.")
|
|
8076
|
+
.option("--component <name>", "only this component (native-host|cli|attach|mcp-server)")
|
|
8077
|
+
.option("-n, --lines <n>", "number of rows to show", "50")
|
|
8078
|
+
.option("--json", "emit JSON rows instead of human lines")
|
|
8079
|
+
.action((opts) => {
|
|
8080
|
+
const dir = resolveCrashLogDir();
|
|
8081
|
+
const lines = Math.max(1, parseInt(opts.lines ?? "50", 10) || 50);
|
|
8082
|
+
const only = (opts.component ?? "").trim();
|
|
8083
|
+
const wantHost = only === "" || only === "native-host";
|
|
8084
|
+
const wanted = CRASH_LOG_COMPONENTS.filter((c) => only === "" || only === c);
|
|
8085
|
+
if (!wantHost && wanted.length === 0) {
|
|
8086
|
+
process.stderr.write("h2a logs: --component must be native-host|cli|attach|mcp-server\n");
|
|
8087
|
+
process.exitCode = 1;
|
|
8088
|
+
return;
|
|
8089
|
+
}
|
|
8090
|
+
const rows = readRecentCrashLogs(wanted, lines).map((row) => ({
|
|
8091
|
+
timestamp: row.timestamp,
|
|
8092
|
+
component: row.component,
|
|
8093
|
+
version: row.version,
|
|
8094
|
+
pid: row.pid,
|
|
8095
|
+
event: row.event,
|
|
8096
|
+
...(row.sessionId ? { sessionId: row.sessionId } : {}),
|
|
8097
|
+
...(row.instanceId ? { instanceId: row.instanceId } : {}),
|
|
8098
|
+
...(row.reason ? { reason: row.reason } : {}),
|
|
8099
|
+
...(row.exitCode !== undefined ? { exitCode: row.exitCode } : {}),
|
|
8100
|
+
...(row.signal ? { signal: row.signal } : {}),
|
|
8101
|
+
...(row.detail ? { detail: row.detail } : {}),
|
|
8102
|
+
}));
|
|
8103
|
+
const hostRows = wantHost
|
|
8104
|
+
? readHostJournal(resolveHostJournalPath())
|
|
8105
|
+
.slice(-lines)
|
|
8106
|
+
.map((entry) => ({
|
|
8107
|
+
timestamp: entry.timestamp ?? "unknown",
|
|
8108
|
+
component: "native-host",
|
|
8109
|
+
...entry,
|
|
8110
|
+
}))
|
|
8111
|
+
: [];
|
|
8112
|
+
const merged = [...hostRows, ...rows]
|
|
8113
|
+
.sort((a, b) => (a.timestamp < b.timestamp ? -1 : a.timestamp > b.timestamp ? 1 : 0))
|
|
8114
|
+
.slice(-lines);
|
|
8115
|
+
// The raw server output is verbatim child stderr (unredacted at rest):
|
|
8116
|
+
// read it before the empty check so a raw-only crash is shown, and
|
|
8117
|
+
// redact it on display like every structured row.
|
|
8118
|
+
const rawPath = resolveCentralMcpLogPath();
|
|
8119
|
+
const rawTail = (only === "" || only === "mcp-server") ? readFileTailLines(rawPath, 20) : [];
|
|
8120
|
+
if (merged.length === 0 && rawTail.length === 0) {
|
|
8121
|
+
process.stdout.write(`[h2a] no crash-log incidents in ${dir}\n`);
|
|
8122
|
+
return;
|
|
8123
|
+
}
|
|
8124
|
+
if (opts.json && merged.length > 0) {
|
|
8125
|
+
process.stdout.write(`${merged.map((row) => JSON.stringify(row)).join("\n")}\n`);
|
|
8126
|
+
return;
|
|
8127
|
+
}
|
|
8128
|
+
for (const row of merged) {
|
|
8129
|
+
process.stdout.write(`${formatCrashLogHumanRow(row)}\n`);
|
|
8130
|
+
}
|
|
8131
|
+
if (rawTail.length > 0) {
|
|
8132
|
+
// The server process itself leaves no structured rows (it dies with
|
|
8133
|
+
// its witness); its raw stderr tail is the only "why" for its crash.
|
|
8134
|
+
process.stdout.write(`[h2a] --- mcp-central.log raw tail (${rawPath}) ---\n`);
|
|
8135
|
+
for (const line of rawTail)
|
|
8136
|
+
process.stdout.write(`${redactCrashText(line)}\n`);
|
|
8137
|
+
}
|
|
8138
|
+
});
|
|
7774
8139
|
// Help layout only — assign every top-level command an intention group and
|
|
7775
8140
|
// render the groups in the declared order instead of registration order.
|
|
7776
8141
|
// Presentational for every PRE-EXISTING RECOGNIZED COMMAND: `helpGroup()` and
|
|
@@ -7822,7 +8187,25 @@ export async function main(argv) {
|
|
|
7822
8187
|
applyRuntimeHelpGroups(program.createHelp().visibleCommands(program));
|
|
7823
8188
|
program.configureHelp({ groupItems: groupRuntimeHelpItems });
|
|
7824
8189
|
program.addHelpText("after", "\nGrouped map of every h2a command (core + runtime): h2a explain\n");
|
|
7825
|
-
|
|
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
|
+
}
|
|
7826
8209
|
const code = process.exitCode;
|
|
7827
8210
|
return typeof code === "number" ? code : 0;
|
|
7828
8211
|
}
|
|
@@ -7838,6 +8221,9 @@ function isEntryPoint() {
|
|
|
7838
8221
|
}
|
|
7839
8222
|
}
|
|
7840
8223
|
if (isEntryPoint()) {
|
|
8224
|
+
// Passive witness for fatal paths outside runOp: installed once inside
|
|
8225
|
+
// main() so the lazy core-binary dispatch path is covered too.
|
|
8226
|
+
installCliWitnessOnce();
|
|
7841
8227
|
main(process.argv).catch((error) => {
|
|
7842
8228
|
if (error instanceof AuthRefreshError ||
|
|
7843
8229
|
error instanceof AuthBundleMissingError) {
|
|
@@ -7853,6 +8239,13 @@ export { main as dispatchH2a };
|
|
|
7853
8239
|
/** @deprecated Compatibility for older h2a cores; new cores require dispatchH2a. */
|
|
7854
8240
|
export { main as dispatch };
|
|
7855
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);
|
|
7856
8249
|
/**
|
|
7857
8250
|
* h2a-facing surface: the read-only remote-agents projection consumed by the
|
|
7858
8251
|
* objective-loop tick's *agents adapter* in `@sentropic/h2a` (via lazy
|