@sentropic/h2a-runtime 0.98.6 → 0.98.7-rc.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +2 -0
- package/dist/agy-claude.d.ts.map +1 -0
- package/dist/agy-claude.js +3 -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 +2 -0
- package/dist/attach.d.ts.map +1 -1
- package/dist/attach.js +73 -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 +2 -0
- package/dist/central-mcp.d.ts.map +1 -1
- package/dist/central-mcp.js +90 -3
- 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/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/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/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-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-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-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/index.d.ts.map +1 -1
- package/dist/index.js +290 -23
- package/dist/index.js.map +1 -1
- package/dist/llm-mesh-accounts.d.ts +70 -4
- package/dist/llm-mesh-accounts.d.ts.map +1 -1
- package/dist/llm-mesh-accounts.js +296 -9
- package/dist/llm-mesh-accounts.js.map +1 -1
- package/dist/native-host.d.ts +1 -1
- package/dist/native-host.d.ts.map +1 -1
- package/dist/native-host.js +30 -17
- package/dist/native-host.js.map +1 -1
- package/dist/native-terminal/host.d.ts +6 -1
- package/dist/native-terminal/host.d.ts.map +1 -1
- package/dist/native-terminal/host.js +6 -25
- 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 +13 -0
- package/dist/native-terminal/op.d.ts.map +1 -1
- package/dist/native-terminal/op.js +64 -5
- package/dist/native-terminal/op.js.map +1 -1
- 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/run.d.ts.map +1 -1
- package/dist/run.js +79 -16
- package/dist/run.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 +26 -4
- package/dist/tmux.d.ts.map +1 -1
- package/dist/tmux.js +60 -25
- 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 +2 -2
package/dist/index.js
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
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 { agyClaudeQuotaError, assertAgyClaudeBuildAllowed, detectAgyClaudeQuotaExhaustion, isAgyClaudeModel, normalizeAgyEffort, readAgyClaudeQuotaStatus, resolveAgyModel, writeAgyClaudeQuotaStatus } from "./agy-claude.js";
|
|
9
10
|
import { applyRuntimeHelpGroups, groupRuntimeHelpItems, } from "./cli-help-groups.js";
|
|
10
11
|
import { attach, createRemoteSession, getRemoteSession, listRemoteSessions, refreshRemoteSession, renameRemoteSession, sessionTerminalHealth, stopRemoteSession, } from "./attach.js";
|
|
11
12
|
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";
|
|
13
|
+
import { prepareCentralMcpForLaunch, prepareCentralMcpForRestore, resolveCentralMcpLogPath, } from "./central-mcp.js";
|
|
14
|
+
import { CRASH_LOG_COMPONENTS, formatCrashLogHumanRow, installCliWitnessOnce, installCrashHandlers, readFileTailLines, redactCrashText, readRecentCrashLogs, resolveCrashLogDir, } from "./crash-log.js";
|
|
15
|
+
import { readHostJournal, resolveHostJournalPath } from "./native-terminal/journal.js";
|
|
13
16
|
import { ensureConnected, stopTunnel } from "./tunnel.js";
|
|
14
17
|
import { detectToolAuth, KNOWN_TOOLS, partitionTools } from "./auth-tools.js";
|
|
15
18
|
import { transmittedSecrets, secretsSummary } from "./secrets.js";
|
|
@@ -1434,7 +1437,7 @@ async function injectLlmMeshGatewayEnv(mode = "auto", allowDirectFallback = true
|
|
|
1434
1437
|
delete process.env.ANTHROPIC_BASE_URL;
|
|
1435
1438
|
delete process.env.ANTHROPIC_AUTH_TOKEN;
|
|
1436
1439
|
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");
|
|
1440
|
+
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
1441
|
}
|
|
1439
1442
|
if (allowDirectFallback) {
|
|
1440
1443
|
process.stderr.write("[h2a] llm-mesh: gateway unavailable; continuing with native authentication.\n");
|
|
@@ -1516,7 +1519,7 @@ function renderNativeRestartResult(result, json) {
|
|
|
1516
1519
|
export async function prepareStructuredGateway(mode, inject = (selectedMode) => injectLlmMeshGatewayEnv(selectedMode, selectedMode !== "gateway")) {
|
|
1517
1520
|
const gateway = await inject(mode);
|
|
1518
1521
|
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");
|
|
1522
|
+
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
1523
|
}
|
|
1521
1524
|
return gateway;
|
|
1522
1525
|
}
|
|
@@ -1718,7 +1721,45 @@ function setAndReportDefaultRemote(url) {
|
|
|
1718
1721
|
ensureManagedTmuxProfile(getTmuxProfileConfig().profile);
|
|
1719
1722
|
process.stderr.write(`[h2a] default remote set to ${configured}\n`);
|
|
1720
1723
|
}
|
|
1724
|
+
// Root commander error boundary (DEC-119): commander never prints its own
|
|
1725
|
+
// error text. `outputError` is a no-op so the commander-built message (which
|
|
1726
|
+
// echoes argv, possibly multi-line) is dropped; `exitOverride` writes a
|
|
1727
|
+
// static message chosen ONLY from `err.code` — never `err.message`, never
|
|
1728
|
+
// user-typed text. Registered on the ROOT program before any `.command()`
|
|
1729
|
+
// so the whole tree inherits it via `copyInheritedSettings`.
|
|
1730
|
+
function staticCommanderErrorForCode(code) {
|
|
1731
|
+
switch (code) {
|
|
1732
|
+
case "commander.unknownOption":
|
|
1733
|
+
return "error: unknown option\n";
|
|
1734
|
+
case "commander.unknownCommand":
|
|
1735
|
+
return "error: unknown command\n";
|
|
1736
|
+
case "commander.excessArguments":
|
|
1737
|
+
return "error: too many arguments\n";
|
|
1738
|
+
case "commander.missingArgument":
|
|
1739
|
+
return "error: missing required argument\n";
|
|
1740
|
+
case "commander.optionMissingArgument":
|
|
1741
|
+
return "error: option argument missing\n";
|
|
1742
|
+
case "commander.missingMandatoryOptionValue":
|
|
1743
|
+
return "error: required option not specified\n";
|
|
1744
|
+
case "commander.conflictingOption":
|
|
1745
|
+
return "error: conflicting option\n";
|
|
1746
|
+
case "commander.invalidArgument":
|
|
1747
|
+
return "error: invalid argument\n";
|
|
1748
|
+
default:
|
|
1749
|
+
return "error: command error\n";
|
|
1750
|
+
}
|
|
1751
|
+
}
|
|
1752
|
+
function isCommanderNonErrorCode(code) {
|
|
1753
|
+
return (code === "commander.help" ||
|
|
1754
|
+
code === "commander.helpDisplayed" ||
|
|
1755
|
+
code === "commander.version" ||
|
|
1756
|
+
code === "commander.executeSubCommandAsync");
|
|
1757
|
+
}
|
|
1721
1758
|
export async function main(argv) {
|
|
1759
|
+
// Passive fatal-path witness on the real entry path (see
|
|
1760
|
+
// installCliWitnessOnce): observes via uncaughtExceptionMonitor, so the
|
|
1761
|
+
// default crash/exit behavior is unchanged.
|
|
1762
|
+
installCliWitnessOnce();
|
|
1722
1763
|
// ②b — auto-migrate the config home remote-cli/ -> h2a/ on first run (backup +
|
|
1723
1764
|
// compat symlink). Best-effort: never let it break a command. Runs once
|
|
1724
1765
|
// (idempotent) before any config/registry read.
|
|
@@ -1767,6 +1808,25 @@ export async function main(argv) {
|
|
|
1767
1808
|
"the separate remote and track CLIs. The heavy session runtime loads on " +
|
|
1768
1809
|
"demand. h2a runs and coordinates agents; it is not itself an agent.")
|
|
1769
1810
|
.version("0.0.0");
|
|
1811
|
+
// Root error boundary (DEC-119): commander never prints its own error
|
|
1812
|
+
// text. `outputError` is a no-op (commander message dropped);
|
|
1813
|
+
// `exitOverride` writes the static per-code message. Registered here —
|
|
1814
|
+
// before any `.command()` — so the whole tree inherits it through
|
|
1815
|
+
// `copyInheritedSettings`.
|
|
1816
|
+
program.configureOutput({
|
|
1817
|
+
writeErr: (str) => {
|
|
1818
|
+
process.stderr.write(str);
|
|
1819
|
+
},
|
|
1820
|
+
outputError: () => { },
|
|
1821
|
+
});
|
|
1822
|
+
program.showHelpAfterError(false);
|
|
1823
|
+
program.showSuggestionAfterError(false);
|
|
1824
|
+
program.exitOverride((err) => {
|
|
1825
|
+
if (isCommanderNonErrorCode(err.code))
|
|
1826
|
+
throw err;
|
|
1827
|
+
process.stderr.write(staticCommanderErrorForCode(err.code));
|
|
1828
|
+
throw err;
|
|
1829
|
+
});
|
|
1770
1830
|
const identityCommand = program
|
|
1771
1831
|
.command("identity")
|
|
1772
1832
|
.description("Inspect identity bindings or unlock a token-fenced identity lock");
|
|
@@ -3991,8 +4051,11 @@ export async function main(argv) {
|
|
|
3991
4051
|
.option("--prompt-stdin", "read the initial Claude/Codex/AGY prompt from stdin (keeps it out of process argv)")
|
|
3992
4052
|
.option("--agent <agent>", "AGY agent override (for example: stp)")
|
|
3993
4053
|
.option("--model <model>", "Claude/Codex/AGY model override")
|
|
3994
|
-
.option("--
|
|
3995
|
-
.option("--
|
|
4054
|
+
.option("--role <role>", "launch purpose: review|challenge|build; AGY Claude builds require explicit opt-in")
|
|
4055
|
+
.option("--allow-claude-build", "explicitly spend the reserved AGY Claude pool on a build")
|
|
4056
|
+
.option("--output-file <path>", "expected AGY Claude headless artifact; report missing or unchanged output at exit")
|
|
4057
|
+
.option("--effort <level>", "reasoning effort: low|medium|high|xhigh (AGY: low|medium|high, max maps to high)")
|
|
4058
|
+
.option("--headless", "run once and exit (Claude -p / Codex exec / AGY stream-json), recording output under .h2a/runs/<name>")
|
|
3996
4059
|
.option("--background", "classify this detached session as a background worker excluded from human restore")
|
|
3997
4060
|
.option("--json", "emit one machine-readable h2a.run.result object")
|
|
3998
4061
|
.option("--llm-gateway", "launch the CLI through the local llm-mesh gateway")
|
|
@@ -4014,13 +4077,30 @@ export async function main(argv) {
|
|
|
4014
4077
|
process.exitCode = 2;
|
|
4015
4078
|
return;
|
|
4016
4079
|
}
|
|
4017
|
-
|
|
4018
|
-
|
|
4080
|
+
try {
|
|
4081
|
+
if (profile === "agy") {
|
|
4082
|
+
const effort = normalizeAgyEffort(opts.effort);
|
|
4083
|
+
if (effort !== undefined)
|
|
4084
|
+
opts.effort = effort;
|
|
4085
|
+
const model = resolveAgyModel(opts.model, effort);
|
|
4086
|
+
if (model !== undefined)
|
|
4087
|
+
opts.model = model;
|
|
4088
|
+
}
|
|
4089
|
+
assertAgyClaudeBuildAllowed({ profile, model: opts.model, role: opts.role, allowClaudeBuild: opts.allowClaudeBuild });
|
|
4090
|
+
if (opts.outputFile && !(profile === "agy" && isAgyClaudeModel(opts.model) && opts.headless)) {
|
|
4091
|
+
throw new Error("--output-file requires an AGY Claude headless run");
|
|
4092
|
+
}
|
|
4093
|
+
}
|
|
4094
|
+
catch (error) {
|
|
4095
|
+
process.stderr.write(`[h2a] ${error.message}\n`);
|
|
4096
|
+
if (opts.json)
|
|
4097
|
+
process.stdout.write(`${JSON.stringify({ kind: "h2a.run.failure", version: 1, state: "not-started",
|
|
4098
|
+
code: error.code ?? "invalid-agy-launch", error: error.message, retrySafe: false })}\n`);
|
|
4019
4099
|
process.exitCode = 2;
|
|
4020
4100
|
return;
|
|
4021
4101
|
}
|
|
4022
|
-
if (
|
|
4023
|
-
process.stderr.write(
|
|
4102
|
+
if (opts.effort !== undefined && !isAgentLaunchEffort(opts.effort)) {
|
|
4103
|
+
process.stderr.write(`[h2a] --effort must be low|medium|high|xhigh\n`);
|
|
4024
4104
|
process.exitCode = 2;
|
|
4025
4105
|
return;
|
|
4026
4106
|
}
|
|
@@ -4383,6 +4463,10 @@ export async function main(argv) {
|
|
|
4383
4463
|
mkdirSync(runDir, { recursive: true });
|
|
4384
4464
|
outputLog = join(runDir, "output.log");
|
|
4385
4465
|
resultJson = join(runDir, "result.json");
|
|
4466
|
+
// Name admission above has completed. Retire the previous result
|
|
4467
|
+
// before returning a path or starting the new run-once supervisor.
|
|
4468
|
+
if (profile === "agy" && isAgyClaudeModel(opts.model))
|
|
4469
|
+
rmSync(resultJson, { force: true });
|
|
4386
4470
|
const headlessPromptInput = structuredLaunch && isAgentLaunchProfile(profile)
|
|
4387
4471
|
? buildAgentLaunchStdin({
|
|
4388
4472
|
profile,
|
|
@@ -4396,10 +4480,10 @@ export async function main(argv) {
|
|
|
4396
4480
|
({ name, slug, promptFile } = startNativeHeadlessSession(profile, command, cwd, args, resultJson, outputLog, label, headlessPromptInput, structuredLaunch, sessionClass, onCreateAttempt, structuredLaunch ? (owned) => {
|
|
4397
4481
|
launchOwnership = { host: "native", sessions: [owned] };
|
|
4398
4482
|
launchGuard = startLaunchGuard(runDir, launchOwnership);
|
|
4399
|
-
} : undefined));
|
|
4483
|
+
} : undefined, opts.outputFile ? resolve(cwd, opts.outputFile) : undefined));
|
|
4400
4484
|
}
|
|
4401
4485
|
else {
|
|
4402
|
-
({ name, slug, agentPane, promptFile } = startHeadlessSession(profile, command, cwd, args, resultJson, outputLog, label, getTmuxProfileConfig().profile, headlessPromptInput, structuredLaunch, sessionClass, onCreateAttempt));
|
|
4486
|
+
({ 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
4487
|
}
|
|
4404
4488
|
}
|
|
4405
4489
|
else if (sessionHost === "native") {
|
|
@@ -4532,6 +4616,18 @@ export async function main(argv) {
|
|
|
4532
4616
|
now: () => Date.now(),
|
|
4533
4617
|
}, { profile });
|
|
4534
4618
|
if (promptDelivery.state !== "working") {
|
|
4619
|
+
const quotaDiagnostic = promptDelivery.state === "submitted-idle" ? "" : `${promptDelivery.reason}\n${promptDelivery.capture ?? ""}`;
|
|
4620
|
+
const quotaVerdict = profile === "agy" && isAgyClaudeModel(opts.model)
|
|
4621
|
+
? detectAgyClaudeQuotaExhaustion(quotaDiagnostic) : { exhausted: false };
|
|
4622
|
+
const quotaFailure = profile === "agy" && isAgyClaudeModel(opts.model) &&
|
|
4623
|
+
promptDelivery.state === "provider-blocked" && promptDelivery.providerError
|
|
4624
|
+
? promptDelivery.providerError : quotaVerdict.exhausted
|
|
4625
|
+
? agyClaudeQuotaError(quotaVerdict.signature, quotaDiagnostic) : undefined;
|
|
4626
|
+
if (quotaFailure)
|
|
4627
|
+
try {
|
|
4628
|
+
writeAgyClaudeQuotaStatus(quotaFailure.quota);
|
|
4629
|
+
}
|
|
4630
|
+
catch { /* Response still carries the observation. */ }
|
|
4535
4631
|
cleanupHeadlessPromptFile(promptFile);
|
|
4536
4632
|
// Only claim the session is gone when the kill actually reported
|
|
4537
4633
|
// success. "nothing is left running" over an unchecked call is the
|
|
@@ -4561,15 +4657,47 @@ export async function main(argv) {
|
|
|
4561
4657
|
? `[h2a] last screen:\n${promptDelivery.capture}\n`
|
|
4562
4658
|
: ""));
|
|
4563
4659
|
process.exitCode = 1;
|
|
4564
|
-
if (opts.json && promptDelivery.state === "provider-blocked") {
|
|
4660
|
+
if (opts.json && (promptDelivery.state === "provider-blocked" || quotaFailure)) {
|
|
4565
4661
|
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:
|
|
4662
|
+
state: "provider-blocked", launchId: slug, error: quotaFailure?.message ?? (promptDelivery.state === "submitted-idle" ? "agent idle after submission" : promptDelivery.reason),
|
|
4663
|
+
stopped, retrySafe: false, prompt: { delivered: promptDelivery.state === "provider-blocked", ...("waitedMs" in promptDelivery ? { waitedMs: promptDelivery.waitedMs } : {}) },
|
|
4664
|
+
...(quotaFailure ? { code: quotaFailure.code, error: quotaFailure.message, quota: quotaFailure.quota,
|
|
4665
|
+
resetsAt: quotaFailure.resetsAt, retryable: false, model: opts.model } : {}),
|
|
4568
4666
|
})}\n`);
|
|
4569
4667
|
}
|
|
4570
4668
|
return;
|
|
4571
4669
|
}
|
|
4572
4670
|
}
|
|
4671
|
+
const reportAgyCompletion = () => {
|
|
4672
|
+
if (!(profile === "agy" && isAgyClaudeModel(opts.model) && opts.headless && resultJson))
|
|
4673
|
+
return false;
|
|
4674
|
+
try {
|
|
4675
|
+
const completed = JSON.parse(readFileSync(resultJson, "utf8"));
|
|
4676
|
+
// The previous result was removed before this supervisor started.
|
|
4677
|
+
// Filesystem and wall-clock timestamps cannot prove generation.
|
|
4678
|
+
if (!["h2a.run.failure", "h2a.run.completion"].includes(completed.kind))
|
|
4679
|
+
return false;
|
|
4680
|
+
launchGuard?.complete();
|
|
4681
|
+
if (opts.json)
|
|
4682
|
+
process.stdout.write(`${JSON.stringify({ ...completed, launchId: slug, result: { path: resultJson }, logs: { path: outputLog } })}\n`);
|
|
4683
|
+
else
|
|
4684
|
+
process.stderr.write(`[h2a] ${completed.error ?? "AGY run completed"}\n`);
|
|
4685
|
+
process.exitCode = completed.state === "done" ? 0 : 1;
|
|
4686
|
+
return true;
|
|
4687
|
+
}
|
|
4688
|
+
catch {
|
|
4689
|
+
return false;
|
|
4690
|
+
} // Still running; the durable result may appear later.
|
|
4691
|
+
};
|
|
4692
|
+
if (profile === "agy" && isAgyClaudeModel(opts.model) && opts.headless && resultJson) {
|
|
4693
|
+
// A fast provider refusal may finish before the host PID probe.
|
|
4694
|
+
// Return that typed completion rather than a misleading PID error.
|
|
4695
|
+
for (let attempt = 0; attempt < 5; attempt++) {
|
|
4696
|
+
if (reportAgyCompletion())
|
|
4697
|
+
return;
|
|
4698
|
+
sleepSync(50);
|
|
4699
|
+
}
|
|
4700
|
+
}
|
|
4573
4701
|
const pid = sessionHost === "native"
|
|
4574
4702
|
? nativeSessionPid(name)
|
|
4575
4703
|
: agentPane
|
|
@@ -4581,6 +4709,9 @@ export async function main(argv) {
|
|
|
4581
4709
|
? paneWorkerPid(agentPane)
|
|
4582
4710
|
: undefined;
|
|
4583
4711
|
if (structuredLaunch && pid === undefined) {
|
|
4712
|
+
// The supervisor can publish and exit DURING the PID probe.
|
|
4713
|
+
if (reportAgyCompletion())
|
|
4714
|
+
return;
|
|
4584
4715
|
cleanupHeadlessPromptFile(promptFile);
|
|
4585
4716
|
// OWN-CLEANUP, deliberately NOT resolver-derived (F1 sorting):
|
|
4586
4717
|
// `name`/`sessionHost` are the session and host THIS command
|
|
@@ -4650,6 +4781,8 @@ export async function main(argv) {
|
|
|
4650
4781
|
tmuxSession: only.name,
|
|
4651
4782
|
host: sessionHost === "native" ? "native" : "tmux",
|
|
4652
4783
|
profile,
|
|
4784
|
+
...(opts.model ? { model: opts.model, modelSource: "launch-argument", effectiveModel: "unknown" } : {}),
|
|
4785
|
+
...(profile === "agy" && isAgyClaudeModel(opts.model) ? { quota: readAgyClaudeQuotaStatus() } : {}),
|
|
4653
4786
|
workspace: cwd,
|
|
4654
4787
|
mode: opts.headless ? "headless" : "interactive",
|
|
4655
4788
|
background: opts.background === true,
|
|
@@ -7525,11 +7658,26 @@ export async function main(argv) {
|
|
|
7525
7658
|
llmMeshAccountCommand
|
|
7526
7659
|
.command("enroll <provider>")
|
|
7527
7660
|
.description("Enroll through the sentropic-owned OAuth state machine " +
|
|
7528
|
-
"(cloud-code, codex, muse OAuth device flow,
|
|
7529
|
-
"--cli imports
|
|
7661
|
+
"(cloud-code, codex, muse OAuth device flow, mistral-vibe browser sign-in — alias: vibe, or claude-code). " +
|
|
7662
|
+
"--from oauth|cli|token selects the source (default oauth): cli imports from the provider's official CLI store (muse, claude-code), " +
|
|
7663
|
+
"token imports a serialized credential from a secret manager via stdin pipe or masked prompt (claude-code only). " +
|
|
7664
|
+
"--cli / --token are the same modes as booleans")
|
|
7530
7665
|
.option("--config-ref <ref>", "sentropic configuration reference for OAuth")
|
|
7531
|
-
.option("--
|
|
7532
|
-
.
|
|
7666
|
+
.option("--from <mode>", "enrollment source: oauth (default), cli (provider official CLI store) or token (secret-manager credential)")
|
|
7667
|
+
.option("--cli", "import credentials from the provider's local CLI store (muse, claude-code) instead of the OAuth flow. Takes no value")
|
|
7668
|
+
.option("--token", "import a serialized credential from a secret manager into llm-mesh (claude-code only): pipe it on stdin, or enter it at the masked prompt. Takes no value; never argv, never logged")
|
|
7669
|
+
.allowExcessArguments(true)
|
|
7670
|
+
.action(async (provider, opts, command) => {
|
|
7671
|
+
// Static excess-argument refusal (m1): commander would otherwise echo
|
|
7672
|
+
// every positional value (including a secret) on stderr before the
|
|
7673
|
+
// action runs. Excess positionals are refused here without echoing
|
|
7674
|
+
// any value. Token material travels on stdin only, never argv.
|
|
7675
|
+
const positionalCount = Array.isArray(command?.args) ? command.args.length : 1;
|
|
7676
|
+
if (positionalCount > 1) {
|
|
7677
|
+
process.stderr.write("[h2a] llm-mesh account: too many arguments (expected provider only; never pass secrets in argv)\n");
|
|
7678
|
+
process.exitCode = 1;
|
|
7679
|
+
return;
|
|
7680
|
+
}
|
|
7533
7681
|
// "vibe" is the CLI-friendly alias of the mistral-vibe transport.
|
|
7534
7682
|
const normalized = provider === "vibe" ? "mistral-vibe" : provider;
|
|
7535
7683
|
if (normalized === "muse-code") {
|
|
@@ -7541,19 +7689,48 @@ export async function main(argv) {
|
|
|
7541
7689
|
process.exitCode = 1;
|
|
7542
7690
|
return;
|
|
7543
7691
|
}
|
|
7692
|
+
if (opts.from !== undefined && opts.from !== "oauth" && opts.from !== "cli" && opts.from !== "token") {
|
|
7693
|
+
process.stderr.write("[h2a] llm-mesh account: --from must be one of oauth, cli, token\n");
|
|
7694
|
+
process.exitCode = 1;
|
|
7695
|
+
return;
|
|
7696
|
+
}
|
|
7697
|
+
if (opts.from !== undefined && (opts.cli || opts.token)) {
|
|
7698
|
+
process.stderr.write("[h2a] llm-mesh account: --from cannot be combined with --cli or --token\n");
|
|
7699
|
+
process.exitCode = 1;
|
|
7700
|
+
return;
|
|
7701
|
+
}
|
|
7702
|
+
if (opts.cli && opts.token) {
|
|
7703
|
+
process.stderr.write("[h2a] llm-mesh account: --cli and --token cannot be combined\n");
|
|
7704
|
+
process.exitCode = 1;
|
|
7705
|
+
return;
|
|
7706
|
+
}
|
|
7707
|
+
if ((opts.cli || opts.from === "cli") && normalized !== "muse" && normalized !== "claude-code") {
|
|
7708
|
+
process.stderr.write("[h2a] llm-mesh account: provider has no CLI-store import; " +
|
|
7709
|
+
"--cli/--from cli is only valid for providers with a local CLI import (muse, claude-code)\n");
|
|
7710
|
+
process.exitCode = 1;
|
|
7711
|
+
return;
|
|
7712
|
+
}
|
|
7713
|
+
if ((opts.token || opts.from === "token") && normalized !== "claude-code") {
|
|
7714
|
+
process.stderr.write(`[h2a] llm-mesh account: --token/--from token is only valid for claude-code\n`);
|
|
7715
|
+
process.exitCode = 1;
|
|
7716
|
+
return;
|
|
7717
|
+
}
|
|
7544
7718
|
if (normalized !== "cloud-code" &&
|
|
7545
7719
|
normalized !== "codex" &&
|
|
7546
7720
|
normalized !== "muse" &&
|
|
7547
|
-
normalized !== "mistral-vibe"
|
|
7548
|
-
|
|
7549
|
-
|
|
7721
|
+
normalized !== "mistral-vibe" &&
|
|
7722
|
+
normalized !== "claude-code") {
|
|
7723
|
+
process.stderr.write("[h2a] llm-mesh account: unsupported provider. " +
|
|
7724
|
+
"Supported: cloud-code, codex, muse (OAuth; --cli imports the CLI store), mistral-vibe (alias: vibe), claude-code (OAuth; --cli imports the CLI store; --token imports a secret-manager credential)\n");
|
|
7550
7725
|
process.exitCode = 1;
|
|
7551
7726
|
return;
|
|
7552
7727
|
}
|
|
7553
7728
|
try {
|
|
7554
7729
|
const account = await enrollViaFacade(normalized, {
|
|
7555
7730
|
...(opts.configRef ? { configRef: opts.configRef } : {}),
|
|
7731
|
+
...(opts.from ? { from: opts.from } : {}),
|
|
7556
7732
|
...(opts.cli ? { cliImport: true } : {}),
|
|
7733
|
+
...(opts.token ? { tokenImport: true } : {}),
|
|
7557
7734
|
});
|
|
7558
7735
|
process.stdout.write(`[h2a] llm-mesh account: enrolled ${account.provider}; ` +
|
|
7559
7736
|
"account inventory remains in Sentropic\n");
|
|
@@ -7771,6 +7948,75 @@ export async function main(argv) {
|
|
|
7771
7948
|
? `[h2a] lineage ${id} resumed\n`
|
|
7772
7949
|
: `[h2a] lineage ${id} was not suspended (no-op)\n`);
|
|
7773
7950
|
});
|
|
7951
|
+
// ---------------------------------------------------------------------------
|
|
7952
|
+
// logs — last crash-log incidents (native host journal + per-component logs)
|
|
7953
|
+
// ---------------------------------------------------------------------------
|
|
7954
|
+
program
|
|
7955
|
+
.command("logs")
|
|
7956
|
+
.description("Show the last crash-log incidents: native host journal plus cli/attach/mcp-server logs.")
|
|
7957
|
+
.option("--component <name>", "only this component (native-host|cli|attach|mcp-server)")
|
|
7958
|
+
.option("-n, --lines <n>", "number of rows to show", "50")
|
|
7959
|
+
.option("--json", "emit JSON rows instead of human lines")
|
|
7960
|
+
.action((opts) => {
|
|
7961
|
+
const dir = resolveCrashLogDir();
|
|
7962
|
+
const lines = Math.max(1, parseInt(opts.lines ?? "50", 10) || 50);
|
|
7963
|
+
const only = (opts.component ?? "").trim();
|
|
7964
|
+
const wantHost = only === "" || only === "native-host";
|
|
7965
|
+
const wanted = CRASH_LOG_COMPONENTS.filter((c) => only === "" || only === c);
|
|
7966
|
+
if (!wantHost && wanted.length === 0) {
|
|
7967
|
+
process.stderr.write("h2a logs: --component must be native-host|cli|attach|mcp-server\n");
|
|
7968
|
+
process.exitCode = 1;
|
|
7969
|
+
return;
|
|
7970
|
+
}
|
|
7971
|
+
const rows = readRecentCrashLogs(wanted, lines).map((row) => ({
|
|
7972
|
+
timestamp: row.timestamp,
|
|
7973
|
+
component: row.component,
|
|
7974
|
+
version: row.version,
|
|
7975
|
+
pid: row.pid,
|
|
7976
|
+
event: row.event,
|
|
7977
|
+
...(row.sessionId ? { sessionId: row.sessionId } : {}),
|
|
7978
|
+
...(row.instanceId ? { instanceId: row.instanceId } : {}),
|
|
7979
|
+
...(row.reason ? { reason: row.reason } : {}),
|
|
7980
|
+
...(row.exitCode !== undefined ? { exitCode: row.exitCode } : {}),
|
|
7981
|
+
...(row.signal ? { signal: row.signal } : {}),
|
|
7982
|
+
...(row.detail ? { detail: row.detail } : {}),
|
|
7983
|
+
}));
|
|
7984
|
+
const hostRows = wantHost
|
|
7985
|
+
? readHostJournal(resolveHostJournalPath())
|
|
7986
|
+
.slice(-lines)
|
|
7987
|
+
.map((entry) => ({
|
|
7988
|
+
timestamp: entry.timestamp ?? "unknown",
|
|
7989
|
+
component: "native-host",
|
|
7990
|
+
...entry,
|
|
7991
|
+
}))
|
|
7992
|
+
: [];
|
|
7993
|
+
const merged = [...hostRows, ...rows]
|
|
7994
|
+
.sort((a, b) => (a.timestamp < b.timestamp ? -1 : a.timestamp > b.timestamp ? 1 : 0))
|
|
7995
|
+
.slice(-lines);
|
|
7996
|
+
// The raw server output is verbatim child stderr (unredacted at rest):
|
|
7997
|
+
// read it before the empty check so a raw-only crash is shown, and
|
|
7998
|
+
// redact it on display like every structured row.
|
|
7999
|
+
const rawPath = resolveCentralMcpLogPath();
|
|
8000
|
+
const rawTail = (only === "" || only === "mcp-server") ? readFileTailLines(rawPath, 20) : [];
|
|
8001
|
+
if (merged.length === 0 && rawTail.length === 0) {
|
|
8002
|
+
process.stdout.write(`[h2a] no crash-log incidents in ${dir}\n`);
|
|
8003
|
+
return;
|
|
8004
|
+
}
|
|
8005
|
+
if (opts.json && merged.length > 0) {
|
|
8006
|
+
process.stdout.write(`${merged.map((row) => JSON.stringify(row)).join("\n")}\n`);
|
|
8007
|
+
return;
|
|
8008
|
+
}
|
|
8009
|
+
for (const row of merged) {
|
|
8010
|
+
process.stdout.write(`${formatCrashLogHumanRow(row)}\n`);
|
|
8011
|
+
}
|
|
8012
|
+
if (rawTail.length > 0) {
|
|
8013
|
+
// The server process itself leaves no structured rows (it dies with
|
|
8014
|
+
// its witness); its raw stderr tail is the only "why" for its crash.
|
|
8015
|
+
process.stdout.write(`[h2a] --- mcp-central.log raw tail (${rawPath}) ---\n`);
|
|
8016
|
+
for (const line of rawTail)
|
|
8017
|
+
process.stdout.write(`${redactCrashText(line)}\n`);
|
|
8018
|
+
}
|
|
8019
|
+
});
|
|
7774
8020
|
// Help layout only — assign every top-level command an intention group and
|
|
7775
8021
|
// render the groups in the declared order instead of registration order.
|
|
7776
8022
|
// Presentational for every PRE-EXISTING RECOGNIZED COMMAND: `helpGroup()` and
|
|
@@ -7822,7 +8068,25 @@ export async function main(argv) {
|
|
|
7822
8068
|
applyRuntimeHelpGroups(program.createHelp().visibleCommands(program));
|
|
7823
8069
|
program.configureHelp({ groupItems: groupRuntimeHelpItems });
|
|
7824
8070
|
program.addHelpText("after", "\nGrouped map of every h2a command (core + runtime): h2a explain\n");
|
|
7825
|
-
|
|
8071
|
+
try {
|
|
8072
|
+
await program.parseAsync([...argv]);
|
|
8073
|
+
}
|
|
8074
|
+
catch (err) {
|
|
8075
|
+
// Commander errors already wrote their static per-code message in
|
|
8076
|
+
// `exitOverride`; only set the exit code here, never `err.message`.
|
|
8077
|
+
if (err !== null &&
|
|
8078
|
+
typeof err === "object" &&
|
|
8079
|
+
"code" in err &&
|
|
8080
|
+
typeof err.code === "string") {
|
|
8081
|
+
const code = err.code;
|
|
8082
|
+
if (code.startsWith("commander.")) {
|
|
8083
|
+
const exitCode = err.exitCode;
|
|
8084
|
+
process.exitCode = typeof exitCode === "number" ? exitCode : 1;
|
|
8085
|
+
return process.exitCode;
|
|
8086
|
+
}
|
|
8087
|
+
}
|
|
8088
|
+
throw err;
|
|
8089
|
+
}
|
|
7826
8090
|
const code = process.exitCode;
|
|
7827
8091
|
return typeof code === "number" ? code : 0;
|
|
7828
8092
|
}
|
|
@@ -7838,6 +8102,9 @@ function isEntryPoint() {
|
|
|
7838
8102
|
}
|
|
7839
8103
|
}
|
|
7840
8104
|
if (isEntryPoint()) {
|
|
8105
|
+
// Passive witness for fatal paths outside runOp: installed once inside
|
|
8106
|
+
// main() so the lazy core-binary dispatch path is covered too.
|
|
8107
|
+
installCliWitnessOnce();
|
|
7841
8108
|
main(process.argv).catch((error) => {
|
|
7842
8109
|
if (error instanceof AuthRefreshError ||
|
|
7843
8110
|
error instanceof AuthBundleMissingError) {
|