claude-use 6.2.2 → 7.1.0
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/README.md +90 -79
- package/dist/cli.cjs +716 -621
- package/dist/index.cjs +56 -41
- package/dist/index.mjs +56 -41
- package/dist/types/cliError.d.ts +1 -1
- package/dist/types/config/classify.d.ts +3 -3
- package/dist/types/config/schema.d.ts +13 -13
- package/dist/types/frontdoor/capability.d.ts +3 -3
- package/dist/types/frontdoor/connect.d.ts +2 -2
- package/dist/types/frontdoor/ensure.d.ts +2 -2
- package/dist/types/frontdoor/probe.d.ts +1 -1
- package/dist/types/frontdoor/supervisor.d.ts +1 -1
- package/dist/types/frontdoor/x509.d.ts +1 -1
- package/dist/types/headroom/source.d.ts +8 -0
- package/dist/types/headroom/state.d.ts +1 -1
- package/dist/types/headroom/supervisor.d.ts +2 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/types/launcher/farm.d.ts +5 -5
- package/dist/types/launcher/guard.d.ts +3 -3
- package/dist/types/launcher/lock.d.ts +1 -1
- package/dist/types/launcher/ports.d.ts +3 -3
- package/dist/types/legacy.d.ts +24 -0
- package/dist/types/paths.d.ts +12 -10
- package/dist/types/resolve/conditions.d.ts +1 -1
- package/dist/types/resolve/match.d.ts +1 -1
- package/dist/types/resolve/reconcile.d.ts +1 -1
- package/dist/types/resolve/types.d.ts +3 -3
- package/dist/types/resolve/walk.d.ts +6 -6
- package/dist/types/usage/read.d.ts +1 -1
- package/dist/types/usage/schema.d.ts +2 -2
- package/package.json +5 -4
package/dist/index.mjs
CHANGED
|
@@ -17,7 +17,7 @@ function expandAllCategoryKey(pairs) {
|
|
|
17
17
|
if (all === void 0) {
|
|
18
18
|
return { ...rest };
|
|
19
19
|
}
|
|
20
|
-
const expanded = Object.fromEntries(OVERRIDABLE_CATEGORIES.map((category) => [category, all]));
|
|
20
|
+
const expanded = Object.fromEntries(OVERRIDABLE_CATEGORIES.filter((category) => !all || category !== "runtime").map((category) => [category, all]));
|
|
21
21
|
return { ...expanded, ...rest };
|
|
22
22
|
}
|
|
23
23
|
var CategoryMapSchema = z.strictObject({
|
|
@@ -91,7 +91,7 @@ var CredentialSchema = z.strictObject({
|
|
|
91
91
|
var ProviderCredentialSchema = CredentialSchema.extend({ target: z.enum(PROVIDER_CREDENTIAL_TARGETS).optional() });
|
|
92
92
|
var RESERVED_PROVIDER_ENV_KEYS = Object.values(CREDENTIAL_TARGET_VARS);
|
|
93
93
|
var ProviderEnvSchema = z.record(z.string().min(1), z.string()).refine((env) => !Object.keys(env).some((key) => RESERVED_PROVIDER_ENV_KEYS.includes(key)), {
|
|
94
|
-
message: `env may not set ${RESERVED_PROVIDER_ENV_KEYS.join(", ")}: the credential block's target sets one and
|
|
94
|
+
message: `env may not set ${RESERVED_PROVIDER_ENV_KEYS.join(", ")}: the credential block's target sets one and agent-shim removes the others`
|
|
95
95
|
});
|
|
96
96
|
var CODEX_EFFORTS = ["none", "low", "medium", "high"];
|
|
97
97
|
var CODEX_DEFAULT_TIER_MODELS = Object.freeze({
|
|
@@ -734,7 +734,7 @@ function resolveEntry(relPath, params) {
|
|
|
734
734
|
diagnostics.push({
|
|
735
735
|
code: "UNCLASSIFIED_ENTRY",
|
|
736
736
|
severity: "warning",
|
|
737
|
-
message: `"${head}" is not in the category map, so it is excluded. Run \`
|
|
737
|
+
message: `"${head}" is not in the category map, so it is excluded. Run \`agent-shim configure\` to classify it, which records the answer in the local overlay without touching the shipped default map.`,
|
|
738
738
|
subject: head
|
|
739
739
|
});
|
|
740
740
|
return { decision: { relPath, shared: false, via: "unclassified", category }, diagnostics };
|
|
@@ -1211,6 +1211,9 @@ function planReconciliation(params) {
|
|
|
1211
1211
|
return { actions, diagnostics, conservative };
|
|
1212
1212
|
}
|
|
1213
1213
|
|
|
1214
|
+
// src/legacy.ts
|
|
1215
|
+
var LEGACY_FARM_MANIFEST_FILENAME = ".claude-use-farm.json";
|
|
1216
|
+
|
|
1214
1217
|
// src/launcher/lock.ts
|
|
1215
1218
|
import { randomUUID } from "node:crypto";
|
|
1216
1219
|
import path3 from "node:path";
|
|
@@ -1220,7 +1223,7 @@ var DEFAULT_MAX_ATTEMPTS = 200;
|
|
|
1220
1223
|
var IdentityLockBusyError = class extends CliError {
|
|
1221
1224
|
constructor(identity, lockPath, holderPid) {
|
|
1222
1225
|
super(
|
|
1223
|
-
`Another
|
|
1226
|
+
`Another agent-shim resync is already running for identity "${identity}"` + (holderPid === void 0 ? "" : ` (pid ${String(holderPid)})`) + `. Its lock at ${lockPath} was still held after the full retry budget; nothing was changed.`
|
|
1224
1227
|
);
|
|
1225
1228
|
this.identity = identity;
|
|
1226
1229
|
this.lockPath = lockPath;
|
|
@@ -1299,7 +1302,7 @@ function acquireIdentityLock(params) {
|
|
|
1299
1302
|
}
|
|
1300
1303
|
|
|
1301
1304
|
// src/launcher/farm.ts
|
|
1302
|
-
var FARM_MANIFEST_FILENAME = ".
|
|
1305
|
+
var FARM_MANIFEST_FILENAME = ".agent-shim-farm.json";
|
|
1303
1306
|
var FarmManifestSchema = z2.strictObject({
|
|
1304
1307
|
version: z2.literal(1),
|
|
1305
1308
|
builtAtMs: z2.number(),
|
|
@@ -1311,7 +1314,7 @@ var FarmManifestSchema = z2.strictObject({
|
|
|
1311
1314
|
links: z2.array(z2.strictObject({ rel: z2.string(), target: z2.string() }))
|
|
1312
1315
|
});
|
|
1313
1316
|
function readFarmManifest(fs2, farmRoot) {
|
|
1314
|
-
const raw = fs2.readFileUtf8(path4.join(farmRoot, FARM_MANIFEST_FILENAME));
|
|
1317
|
+
const raw = fs2.readFileUtf8(path4.join(farmRoot, FARM_MANIFEST_FILENAME)) ?? fs2.readFileUtf8(path4.join(farmRoot, LEGACY_FARM_MANIFEST_FILENAME));
|
|
1315
1318
|
if (raw === void 0) {
|
|
1316
1319
|
return void 0;
|
|
1317
1320
|
}
|
|
@@ -1400,7 +1403,7 @@ function reconciliationScope(fs2, farmRoot, manifest) {
|
|
|
1400
1403
|
if (manifest !== void 0) {
|
|
1401
1404
|
return topLevelScopeRoots(manifest.materialised);
|
|
1402
1405
|
}
|
|
1403
|
-
return [...fs2.readdir(farmRoot)].filter((name) => name !== FARM_MANIFEST_FILENAME && fs2.lstat(path4.join(farmRoot, name))?.kind === "dir").sort();
|
|
1406
|
+
return [...fs2.readdir(farmRoot)].filter((name) => name !== FARM_MANIFEST_FILENAME && name !== LEGACY_FARM_MANIFEST_FILENAME && fs2.lstat(path4.join(farmRoot, name))?.kind === "dir").sort();
|
|
1404
1407
|
}
|
|
1405
1408
|
function collectFarmListing(fs2, farmRoot, scopeRoots) {
|
|
1406
1409
|
return scopeRoots.flatMap((root) => listSubtree(fs2, farmRoot, root));
|
|
@@ -1424,7 +1427,7 @@ function collectCanonicalCounterparts(fs2, claudeHome, farmListing) {
|
|
|
1424
1427
|
}
|
|
1425
1428
|
function carryOver(params) {
|
|
1426
1429
|
const manifest = readFarmManifest(params.fs, params.previousRoot);
|
|
1427
|
-
const accounted = /* @__PURE__ */ new Set([FARM_MANIFEST_FILENAME]);
|
|
1430
|
+
const accounted = /* @__PURE__ */ new Set([FARM_MANIFEST_FILENAME, LEGACY_FARM_MANIFEST_FILENAME]);
|
|
1428
1431
|
for (const rel of manifest?.materialised ?? []) {
|
|
1429
1432
|
const head = rel.split("/")[0];
|
|
1430
1433
|
if (head !== void 0 && head !== "") {
|
|
@@ -1722,7 +1725,7 @@ function resyncFarm(params) {
|
|
|
1722
1725
|
diagnostics.push({
|
|
1723
1726
|
code: "FARM_PREVIOUS_RETAINED",
|
|
1724
1727
|
severity: "warning",
|
|
1725
|
-
message: `The superseded farm was left at ${swap.retainedPrevious} because it still holds ${swap.collided.join(", ")}, which the new farm has its own entry for. Nothing was overwritten in either direction; run \`
|
|
1728
|
+
message: `The superseded farm was left at ${swap.retainedPrevious} because it still holds ${swap.collided.join(", ")}, which the new farm has its own entry for. Nothing was overwritten in either direction; run \`agent-shim identity resolve-conflicts ${params.identity}\` to resolve it interactively.`,
|
|
1726
1729
|
subject: swap.retainedPrevious
|
|
1727
1730
|
});
|
|
1728
1731
|
}
|
|
@@ -1759,7 +1762,7 @@ function recoveryDiagnostics(recovery, identity) {
|
|
|
1759
1762
|
}
|
|
1760
1763
|
if (recovery.retained.length > 0) {
|
|
1761
1764
|
parts.push(
|
|
1762
|
-
`kept ${recovery.retained.join(", ")}, which still holds data the current farm also has an entry for \u2014 run \`
|
|
1765
|
+
`kept ${recovery.retained.join(", ")}, which still holds data the current farm also has an entry for \u2014 run \`agent-shim identity resolve-conflicts ${identity}\` to resolve it interactively`
|
|
1763
1766
|
);
|
|
1764
1767
|
}
|
|
1765
1768
|
return [
|
|
@@ -2051,7 +2054,7 @@ function issueLeafCertificate(ca, names, notBefore, notAfter) {
|
|
|
2051
2054
|
}
|
|
2052
2055
|
|
|
2053
2056
|
// src/frontdoor/capability.ts
|
|
2054
|
-
var CONNECT_PROXY_REALM = "
|
|
2057
|
+
var CONNECT_PROXY_REALM = "agent-shim front door";
|
|
2055
2058
|
var BASIC_SCHEME = "basic";
|
|
2056
2059
|
function capabilityFromProxyAuthorization(value) {
|
|
2057
2060
|
const trimmed = value.trim();
|
|
@@ -2178,7 +2181,7 @@ async function runFrontDoorSupervisor(idleShutdownMinutes, ports, options = {})
|
|
|
2178
2181
|
const fail = (message) => {
|
|
2179
2182
|
writeFrontDoorState(fs2, paths.frontdoorStateFile, { lastError: message, ...sticky() });
|
|
2180
2183
|
fs2.removeRecursive(paths.frontdoorLockFile);
|
|
2181
|
-
ports.log(`
|
|
2184
|
+
ports.log(`agent-shim frontdoor supervisor: ${message}`);
|
|
2182
2185
|
return 1;
|
|
2183
2186
|
};
|
|
2184
2187
|
writeFrontDoorState(fs2, paths.frontdoorStateFile, { supervisorPid: ports.ownPid, ...sticky() });
|
|
@@ -2190,7 +2193,7 @@ async function runFrontDoorSupervisor(idleShutdownMinutes, ports, options = {})
|
|
|
2190
2193
|
return fail(`could not start the provider listener: ${error instanceof Error ? error.message : String(error)}`);
|
|
2191
2194
|
}
|
|
2192
2195
|
if (previousSticky !== void 0 && http2.port !== previousSticky) {
|
|
2193
|
-
ports.log(`
|
|
2196
|
+
ports.log(`agent-shim frontdoor supervisor: sticky port ${String(previousSticky)} was occupied; moving to ${String(http2.port)} (sessions launched against the old port are stale until they relaunch)`);
|
|
2194
2197
|
}
|
|
2195
2198
|
lastPort = http2.port;
|
|
2196
2199
|
const previousStickyConnect = lastConnectPort;
|
|
@@ -2202,7 +2205,7 @@ async function runFrontDoorSupervisor(idleShutdownMinutes, ports, options = {})
|
|
|
2202
2205
|
return fail(`could not start the CONNECT surface: ${error instanceof Error ? error.message : String(error)}`);
|
|
2203
2206
|
}
|
|
2204
2207
|
if (previousStickyConnect !== void 0 && connect2.port !== previousStickyConnect) {
|
|
2205
|
-
ports.log(`
|
|
2208
|
+
ports.log(`agent-shim frontdoor supervisor: sticky CONNECT port ${String(previousStickyConnect)} was occupied; moving to ${String(connect2.port)} (OAuth sessions launched against the old port are stale until they relaunch)`);
|
|
2206
2209
|
}
|
|
2207
2210
|
lastConnectPort = connect2.port;
|
|
2208
2211
|
const previousStickyDirect = lastDirectPort;
|
|
@@ -2215,12 +2218,12 @@ async function runFrontDoorSupervisor(idleShutdownMinutes, ports, options = {})
|
|
|
2215
2218
|
return fail(`could not start the direct listener: ${error instanceof Error ? error.message : String(error)}`);
|
|
2216
2219
|
}
|
|
2217
2220
|
if (previousStickyDirect !== void 0 && direct.port !== previousStickyDirect) {
|
|
2218
|
-
ports.log(`
|
|
2221
|
+
ports.log(`agent-shim frontdoor supervisor: sticky direct port ${String(previousStickyDirect)} was occupied; moving to ${String(direct.port)} (headroom must be restarted to admit the new address)`);
|
|
2219
2222
|
}
|
|
2220
2223
|
lastDirectPort = direct.port;
|
|
2221
2224
|
writeFrontDoorState(fs2, paths.frontdoorStateFile, { supervisorPid: ports.ownPid, port: http2.port, connectPort: connect2.port, directPort: direct.port, ...sticky() });
|
|
2222
2225
|
fs2.removeRecursive(paths.frontdoorLockFile);
|
|
2223
|
-
ports.log(`
|
|
2226
|
+
ports.log(`agent-shim frontdoor supervisor ${String(ports.ownPid)}: front door on 127.0.0.1:${String(http2.port)}, CONNECT surface on 127.0.0.1:${String(connect2.port)}`);
|
|
2224
2227
|
let idleSince;
|
|
2225
2228
|
let ticks = 0;
|
|
2226
2229
|
for (; ; ) {
|
|
@@ -2232,7 +2235,7 @@ async function runFrontDoorSupervisor(idleShutdownMinutes, ports, options = {})
|
|
|
2232
2235
|
if (listFrontDoorSessions(fs2, paths.frontdoorSessionsDir).length === 0) {
|
|
2233
2236
|
idleSince ??= ports.now();
|
|
2234
2237
|
if (ports.now() - idleSince >= idleShutdownMinutes * MS_PER_MINUTE) {
|
|
2235
|
-
ports.log(`
|
|
2238
|
+
ports.log(`agent-shim frontdoor supervisor: no sessions for ${String(idleShutdownMinutes)} minute(s); closing the front door and exiting`);
|
|
2236
2239
|
await direct.close();
|
|
2237
2240
|
await connect2.close();
|
|
2238
2241
|
await http2.close();
|
|
@@ -2272,7 +2275,7 @@ var CONNECT_LIMITS = {
|
|
|
2272
2275
|
};
|
|
2273
2276
|
var CA_VALIDITY_DAYS = 3650;
|
|
2274
2277
|
var LEAF_VALIDITY_DAYS = 397;
|
|
2275
|
-
var CA_SUBJECT_COMMON_NAME = "
|
|
2278
|
+
var CA_SUBJECT_COMMON_NAME = "agent-shim front door CA";
|
|
2276
2279
|
var MS_PER_DAY = 864e5;
|
|
2277
2280
|
function parseConnectTarget(authority) {
|
|
2278
2281
|
const colon = authority.lastIndexOf(":");
|
|
@@ -2666,7 +2669,7 @@ function realConnectEffects() {
|
|
|
2666
2669
|
response.destroy(error);
|
|
2667
2670
|
return;
|
|
2668
2671
|
}
|
|
2669
|
-
const body = `
|
|
2672
|
+
const body = `agent-shim headroom: upstream unreachable (${error.message})`;
|
|
2670
2673
|
response.writeHead(HTTP_BAD_GATEWAY, { "content-type": "text/plain", "content-length": String(body.length) });
|
|
2671
2674
|
response.end(body);
|
|
2672
2675
|
});
|
|
@@ -2755,7 +2758,7 @@ function readHeadroomState(fs2, stateFile) {
|
|
|
2755
2758
|
}
|
|
2756
2759
|
function writeHeadroomState(fs2, stateFile, state) {
|
|
2757
2760
|
fs2.mkdirp(path6.dirname(stateFile));
|
|
2758
|
-
fs2.
|
|
2761
|
+
fs2.writeFilePrivate(stateFile, `${JSON.stringify(state, null, 2)}
|
|
2759
2762
|
`);
|
|
2760
2763
|
}
|
|
2761
2764
|
function headroomAllowlist(providers) {
|
|
@@ -2866,13 +2869,13 @@ function ensureFrontDoor(params) {
|
|
|
2866
2869
|
}
|
|
2867
2870
|
lastRejection = `the listener on 127.0.0.1:${String(state.port)} (recorded for pid ${String(supervisorPid)}) failed authentication: ${verdict.reason}`;
|
|
2868
2871
|
if (spawned) {
|
|
2869
|
-
throw new FrontDoorStartError(`
|
|
2872
|
+
throw new FrontDoorStartError(`agent-shim: ${lastRejection}; refusing to send this session's credentials to it. Daemon log: ${paths.frontdoorLogPath}`);
|
|
2870
2873
|
}
|
|
2871
2874
|
distrusted.add(supervisorPid);
|
|
2872
2875
|
continue;
|
|
2873
2876
|
}
|
|
2874
2877
|
if (state?.lastError !== void 0) {
|
|
2875
|
-
throw new FrontDoorStartError(`
|
|
2878
|
+
throw new FrontDoorStartError(`agent-shim: the front door reported a fatal error and is not serving: ${state.lastError} (daemon log: ${paths.frontdoorLogPath})`);
|
|
2876
2879
|
}
|
|
2877
2880
|
} else if (!spawned) {
|
|
2878
2881
|
ports.fs.mkdirp(paths.frontdoorDir);
|
|
@@ -2892,7 +2895,7 @@ function ensureFrontDoor(params) {
|
|
|
2892
2895
|
if (ports.now() >= deadline) {
|
|
2893
2896
|
const lastError = state?.lastError;
|
|
2894
2897
|
throw new FrontDoorStartError(
|
|
2895
|
-
`
|
|
2898
|
+
`agent-shim: the front door did not become ready within ${String(FRONTDOOR_START_TIMEOUT_MS)}ms` + (lastError === void 0 ? "" : ` (last error: ${lastError})`) + (lastRejection === void 0 ? "" : ` (${lastRejection})`) + `. Daemon log: ${paths.frontdoorLogPath}`
|
|
2896
2899
|
);
|
|
2897
2900
|
}
|
|
2898
2901
|
ports.sleep(FRONTDOOR_LAUNCHER_POLL_MS);
|
|
@@ -3057,7 +3060,7 @@ function isIdentityName(name) {
|
|
|
3057
3060
|
// src/usage/read.ts
|
|
3058
3061
|
var UsageSnapshotError = class extends Error {
|
|
3059
3062
|
constructor(file, detail) {
|
|
3060
|
-
super(`${file} is not a usage snapshot this
|
|
3063
|
+
super(`${file} is not a usage snapshot this agent-shim can read (schema version ${String(USAGE_SCHEMA_VERSION)}): ${detail}`);
|
|
3061
3064
|
this.file = file;
|
|
3062
3065
|
this.name = "UsageSnapshotError";
|
|
3063
3066
|
}
|
|
@@ -3124,7 +3127,7 @@ function ensureHeadroom(params) {
|
|
|
3124
3127
|
}
|
|
3125
3128
|
if (state.lastError !== void 0) {
|
|
3126
3129
|
throw new HeadroomStartError(
|
|
3127
|
-
`
|
|
3130
|
+
`agent-shim: the headroom daemon reported a fatal error and is not serving: ${state.lastError} (daemon log: ${paths.headroomLogPath})`
|
|
3128
3131
|
);
|
|
3129
3132
|
}
|
|
3130
3133
|
} else if (!spawnedSupervisor) {
|
|
@@ -3148,7 +3151,7 @@ function ensureHeadroom(params) {
|
|
|
3148
3151
|
if (ports.now() >= deadline) {
|
|
3149
3152
|
const lastError = state?.lastError;
|
|
3150
3153
|
throw new HeadroomStartError(
|
|
3151
|
-
`
|
|
3154
|
+
`agent-shim: the headroom daemon did not become ready within ${String(HEADROOM_START_TIMEOUT_MS)}ms` + (lastError === void 0 ? "" : ` (last error: ${lastError})`) + `. Daemon log: ${paths.headroomLogPath}`
|
|
3152
3155
|
);
|
|
3153
3156
|
}
|
|
3154
3157
|
ports.sleep(HEADROOM_LAUNCHER_POLL_MS);
|
|
@@ -3184,6 +3187,16 @@ function hashSettings(settings) {
|
|
|
3184
3187
|
return createHash3("sha256").update(JSON.stringify([...settingsArgs(settings), JSON.stringify(settingsEnv(settings))])).digest("hex");
|
|
3185
3188
|
}
|
|
3186
3189
|
|
|
3190
|
+
// src/headroom/source.ts
|
|
3191
|
+
import { z as z6 } from "zod";
|
|
3192
|
+
var GIT_SPEC_RE = /git\+[^@\s]+(?:@([^\s#]+))?/;
|
|
3193
|
+
var COMMIT_SHA_RE = /^[0-9a-f]{40}$/i;
|
|
3194
|
+
var DirectUrlSchema = z6.object({ vcs_info: z6.object({ commit_id: z6.string() }) });
|
|
3195
|
+
function pinnedGitCommit(spec) {
|
|
3196
|
+
const ref = GIT_SPEC_RE.exec(spec)?.[1];
|
|
3197
|
+
return ref !== void 0 && COMMIT_SHA_RE.test(ref) ? ref.toLowerCase() : void 0;
|
|
3198
|
+
}
|
|
3199
|
+
|
|
3187
3200
|
// src/headroom/supervisor.ts
|
|
3188
3201
|
function resolveSupervisorConfig(configured) {
|
|
3189
3202
|
return {
|
|
@@ -3257,7 +3270,7 @@ async function runSupervisor(initialConfig, ports, options = {}) {
|
|
|
3257
3270
|
let config = initialConfig;
|
|
3258
3271
|
const previousState = readHeadroomState(fs2, paths.headroomStateFile);
|
|
3259
3272
|
if (previousState?.supervisorPid !== void 0 && previousState.supervisorPid !== ports.ownPid && ports.isRunning(previousState.supervisorPid)) {
|
|
3260
|
-
ports.log(`
|
|
3273
|
+
ports.log(`agent-shim headroom supervisor ${String(ports.ownPid)}: supervisor ${String(previousState.supervisorPid)} already owns the daemon; exiting`);
|
|
3261
3274
|
return 0;
|
|
3262
3275
|
}
|
|
3263
3276
|
let installedSource = previousState?.installedSource;
|
|
@@ -3278,17 +3291,19 @@ async function runSupervisor(initialConfig, ports, options = {}) {
|
|
|
3278
3291
|
if (writeOwnState({ lastError: message, ...sticky() })) {
|
|
3279
3292
|
fs2.removeRecursive(paths.headroomLockFile);
|
|
3280
3293
|
}
|
|
3281
|
-
ports.log(`
|
|
3294
|
+
ports.log(`agent-shim headroom supervisor: ${message}`);
|
|
3282
3295
|
return 1;
|
|
3283
3296
|
};
|
|
3284
3297
|
const ensureInstalled = () => {
|
|
3285
3298
|
const version2 = ports.headroomVersion();
|
|
3286
3299
|
const versionOk = version2 !== void 0 && versionSatisfies(version2, config.source);
|
|
3287
|
-
|
|
3300
|
+
const pinned = pinnedGitCommit(config.source);
|
|
3301
|
+
const commitOk = pinned === void 0 || ports.installedCommit() === pinned;
|
|
3302
|
+
if (versionOk && commitOk && (installedSource === void 0 || installedSource === config.source)) {
|
|
3288
3303
|
installedSource = config.source;
|
|
3289
3304
|
return true;
|
|
3290
3305
|
}
|
|
3291
|
-
ports.log(`
|
|
3306
|
+
ports.log(`agent-shim headroom supervisor: installing headroom from ${config.source}`);
|
|
3292
3307
|
const result = ports.install(config.source);
|
|
3293
3308
|
if (!result.ok) {
|
|
3294
3309
|
return false;
|
|
@@ -3301,10 +3316,10 @@ async function runSupervisor(initialConfig, ports, options = {}) {
|
|
|
3301
3316
|
}
|
|
3302
3317
|
const version = ports.headroomVersion() ?? "unknown";
|
|
3303
3318
|
writeOwnState({ supervisorPid: ports.ownPid, version, installedSource, ...sticky() });
|
|
3304
|
-
ports.log(`
|
|
3319
|
+
ports.log(`agent-shim headroom supervisor ${String(ports.ownPid)}: managing headroom on allowlist [${allowlistOf(ports).join(", ")}]`);
|
|
3305
3320
|
const orphanPid = previousState?.headroomPid;
|
|
3306
3321
|
if (orphanPid !== void 0 && ports.isRunning(orphanPid)) {
|
|
3307
|
-
ports.log(`
|
|
3322
|
+
ports.log(`agent-shim headroom supervisor: stopping daemon pid ${String(orphanPid)} left behind by the previous supervisor`);
|
|
3308
3323
|
ports.stopProcess(orphanPid);
|
|
3309
3324
|
}
|
|
3310
3325
|
let headroomPid;
|
|
@@ -3326,13 +3341,13 @@ async function runSupervisor(initialConfig, ports, options = {}) {
|
|
|
3326
3341
|
pruneDeadSessions(fs2, paths.headroomSessionsDir, ports.isRunning);
|
|
3327
3342
|
const ownsNow = ownsState();
|
|
3328
3343
|
if (!ownsNow && !superseded) {
|
|
3329
|
-
ports.log("
|
|
3344
|
+
ports.log("agent-shim headroom supervisor: another supervisor now owns state.json; serving this daemon's own sessions until they end");
|
|
3330
3345
|
}
|
|
3331
3346
|
superseded = !ownsNow;
|
|
3332
3347
|
const crashed = headroomPid !== void 0 && !ports.isRunning(headroomPid);
|
|
3333
3348
|
if (headroomPid === void 0 || crashed) {
|
|
3334
3349
|
if (crashed) {
|
|
3335
|
-
ports.log(`
|
|
3350
|
+
ports.log(`agent-shim headroom supervisor: headroom pid ${String(headroomPid)} died`);
|
|
3336
3351
|
headroomPid = void 0;
|
|
3337
3352
|
runningHash = void 0;
|
|
3338
3353
|
runningSettingsHash = void 0;
|
|
@@ -3361,7 +3376,7 @@ async function runSupervisor(initialConfig, ports, options = {}) {
|
|
|
3361
3376
|
lastPort = port;
|
|
3362
3377
|
if (previousSticky !== void 0 && port !== previousSticky) {
|
|
3363
3378
|
ports.log(
|
|
3364
|
-
`
|
|
3379
|
+
`agent-shim headroom supervisor: sticky port ${String(previousSticky)} was occupied; moving to ${String(port)} (sessions launched against the old port are stale until they relaunch)`
|
|
3365
3380
|
);
|
|
3366
3381
|
}
|
|
3367
3382
|
const recorded = writeOwnState({
|
|
@@ -3378,12 +3393,12 @@ async function runSupervisor(initialConfig, ports, options = {}) {
|
|
|
3378
3393
|
fs2.removeRecursive(paths.headroomLockFile);
|
|
3379
3394
|
}
|
|
3380
3395
|
idleSince = void 0;
|
|
3381
|
-
ports.log(`
|
|
3396
|
+
ports.log(`agent-shim headroom supervisor: headroom pid ${String(pid)} ready on 127.0.0.1:${String(port)}`);
|
|
3382
3397
|
} else {
|
|
3383
3398
|
ports.stopProcess(pid);
|
|
3384
3399
|
consecutiveFailures += 1;
|
|
3385
3400
|
ports.log(
|
|
3386
|
-
`
|
|
3401
|
+
`agent-shim headroom supervisor: headroom did not become ready on port ${String(port)} (attempt ${String(consecutiveFailures)} of ${String(HEADROOM_START_RETRY_BUDGET)})`
|
|
3387
3402
|
);
|
|
3388
3403
|
await ports.sleep(backoffForAttempt(consecutiveFailures));
|
|
3389
3404
|
continue;
|
|
@@ -3391,7 +3406,7 @@ async function runSupervisor(initialConfig, ports, options = {}) {
|
|
|
3391
3406
|
} else {
|
|
3392
3407
|
if (runningHash !== allowlistHash || runningSettingsHash !== settingsHash || installedSource !== config.source) {
|
|
3393
3408
|
if (sessionsForSupervisor(fs2, paths.headroomSessionsDir, ports.ownPid).length === 0) {
|
|
3394
|
-
ports.log("
|
|
3409
|
+
ports.log("agent-shim headroom supervisor: configuration drifted and no sessions are live; restarting headroom");
|
|
3395
3410
|
ports.stopProcess(headroomPid);
|
|
3396
3411
|
headroomPid = void 0;
|
|
3397
3412
|
runningHash = void 0;
|
|
@@ -3405,7 +3420,7 @@ async function runSupervisor(initialConfig, ports, options = {}) {
|
|
|
3405
3420
|
const idleMs = ports.now() - idleSince;
|
|
3406
3421
|
if (idleMs >= config.idleShutdownMinutes * MS_PER_MINUTE2) {
|
|
3407
3422
|
ports.log(
|
|
3408
|
-
`
|
|
3423
|
+
`agent-shim headroom supervisor: no sessions for ${String(config.idleShutdownMinutes)} minute(s); stopping headroom and exiting`
|
|
3409
3424
|
);
|
|
3410
3425
|
ports.stopProcess(headroomPid);
|
|
3411
3426
|
writeOwnState({
|
|
@@ -3464,12 +3479,12 @@ function detectAmbientCredential(env, injected) {
|
|
|
3464
3479
|
return void 0;
|
|
3465
3480
|
}
|
|
3466
3481
|
function formatAmbientCredentialGuardMessage(variable, identityName) {
|
|
3467
|
-
const identitySetCommand = identityName === void 0 ? "
|
|
3482
|
+
const identitySetCommand = identityName === void 0 ? "agent-shim identity set <name> --allow-ambient-credential" : `agent-shim identity set ${identityName} --allow-ambient-credential`;
|
|
3468
3483
|
return [
|
|
3469
3484
|
`error: ${variable} is set in the environment. This identity's isolated`,
|
|
3470
3485
|
"credential would be bypassed \u2014 every identity authenticates as this same key",
|
|
3471
3486
|
"while it's set. Unset it, or if this is deliberate, opt in per-launch with",
|
|
3472
|
-
"
|
|
3487
|
+
"AGENT_SHIM_ALLOW_AMBIENT_CREDENTIAL=1, or persistently for this identity with",
|
|
3473
3488
|
`\`${identitySetCommand}\`.`
|
|
3474
3489
|
].join("\n");
|
|
3475
3490
|
}
|
package/dist/types/cliError.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ export declare const EXIT_FAILURE = 1;
|
|
|
3
3
|
/** Exit status for a usage error: an unknown command or option, a malformed flag value, or required input missing with no terminal to prompt on. */
|
|
4
4
|
export declare const EXIT_USAGE = 2;
|
|
5
5
|
/**
|
|
6
|
-
* Base class for every error this CLI throws to represent an expected, user-facing failure: bad input, a missing identity/profile/rule, a malformed config file. `reportFatalError` prints this class as `
|
|
6
|
+
* Base class for every error this CLI throws to represent an expected, user-facing failure: bad input, a missing identity/profile/rule, a malformed config file. `reportFatalError` prints this class as `agent-shim: <message>` with no stack trace and exits with `exitCode`; anything that does not extend it is an unexpected bug, reported by message alone unless `AGENT_SHIM_DEBUG` asks for the stack.
|
|
7
7
|
*/
|
|
8
8
|
export declare abstract class CliError extends Error {
|
|
9
9
|
/** The process exit status this failure maps to. `EXIT_FAILURE` unless a subclass says otherwise. */
|
|
@@ -17,9 +17,9 @@ export interface ClassificationPattern {
|
|
|
17
17
|
export interface ClassifyResult {
|
|
18
18
|
/** Every input name mapped to its category, or to null when nothing in either map recognises it. */
|
|
19
19
|
readonly classification: ReadonlyMap<string, CategoryName | null>;
|
|
20
|
-
/** The input names that nothing recognised, in input order. These are the names `
|
|
20
|
+
/** The input names that nothing recognised, in input order. These are the names `agent-shim configure` prompts about. */
|
|
21
21
|
readonly unclassified: readonly string[];
|
|
22
|
-
/** For each classified name, the pattern that decided it — used by `
|
|
22
|
+
/** For each classified name, the pattern that decided it — used by `agent-shim check` to explain a classification. */
|
|
23
23
|
readonly decidedBy: ReadonlyMap<string, ClassificationPattern>;
|
|
24
24
|
}
|
|
25
25
|
/** True when `pattern` contains no glob metacharacter, so it matches exactly one literal name. */
|
|
@@ -40,7 +40,7 @@ export declare function loadClassification(paths: LayoutPaths): LoadedClassifica
|
|
|
40
40
|
/**
|
|
41
41
|
* Classifies a list of real top-level `~/.claude` entry names against the shipped category map plus an optional local overlay.
|
|
42
42
|
*
|
|
43
|
-
* An unrecognised name maps to `null` rather than being silently assumed safe or silently dropped: the resolver treats it as not-shared and reports it, and `
|
|
43
|
+
* An unrecognised name maps to `null` rather than being silently assumed safe or silently dropped: the resolver treats it as not-shared and reports it, and `agent-shim configure` prompts for an answer that is then written to the local overlay.
|
|
44
44
|
*/
|
|
45
45
|
export declare function classifyEntries(names: readonly string[], options: {
|
|
46
46
|
defaults: CategoryClassification;
|
|
@@ -16,13 +16,13 @@ export declare function isCategoryName(name: string): name is CategoryName;
|
|
|
16
16
|
*/
|
|
17
17
|
export type CategoryMap = Partial<Record<OverridableCategory, boolean>>;
|
|
18
18
|
/**
|
|
19
|
-
* Expands the `all` pseudo-category key
|
|
19
|
+
* Expands the `all` pseudo-category key, dropping `all` itself from the result. `all: true` opens every category meant to be shared across identities, which is every overridable category except `runtime`: `runtime` holds live per-process and per-machine state (daemon locks and auth status, PIDs, IDE and shell snapshots) that must never be pooled across running identities, so it opens only when named explicitly. `all: false` closes every overridable category, `runtime` included, so it also closes a `runtime` an earlier layer opened. An explicit named category always wins over the `all` expansion regardless of where it appears relative to `all` in the input: `{ all: true, runtime: true }` opens `runtime` too, and `{ all: true, history: false }` means "share everything except history", not "history is false, then immediately overwritten back to true".
|
|
20
20
|
*
|
|
21
|
-
* This is the one shared implementation `CategoryMapSchema`'s own transform, `launcher/cliOverride.ts`'s `--category`/`
|
|
21
|
+
* This is the one shared implementation `CategoryMapSchema`'s own transform, `launcher/cliOverride.ts`'s `--category`/`AGENT_SHIM_CATEGORY_OVERRIDE` handling, and `configProfiles.ts`'s `agent-shim profile set --category` all call, so `all` means the same thing regardless of which of those three input paths it arrived through.
|
|
22
22
|
*/
|
|
23
23
|
export declare function expandAllCategoryKey(pairs: Readonly<Record<string, boolean>>): Record<string, boolean>;
|
|
24
24
|
/**
|
|
25
|
-
* The category toggle map as written by hand: the four overridable categories, plus `all` as shorthand for "every
|
|
25
|
+
* The category toggle map as written by hand: the four overridable categories, plus `all` as shorthand for "every shareable category at once" (expanded by `expandAllCategoryKey` above). `secret` is omitted from the shape entirely, so `{ "categories": { "secret": true } }` is rejected at parse time rather than relying solely on the resolver's runtime floor check. The closed shape is also what lets the published JSON Schema offer real key-name autocomplete, which an open record type cannot.
|
|
26
26
|
*/
|
|
27
27
|
export declare const CategoryMapSchema: z.ZodPipe<z.ZodObject<{
|
|
28
28
|
all: z.ZodOptional<z.ZodBoolean>;
|
|
@@ -40,7 +40,7 @@ export declare const CategoryMapSchema: z.ZodPipe<z.ZodObject<{
|
|
|
40
40
|
/** A duration literal: a positive integer count followed by a unit. Used by `newerThan`/`olderThan`. */
|
|
41
41
|
export declare const DURATION_RE: RegExp;
|
|
42
42
|
/**
|
|
43
|
-
* A conditional guard on an entries value or a whole directory rule. Every field present within one `when` object must hold (AND logic). An empty object is vacuously true — `
|
|
43
|
+
* A conditional guard on an entries value or a whole directory rule. Every field present within one `when` object must hold (AND logic). An empty object is vacuously true — `agent-shim check` warns about it, it is never an error.
|
|
44
44
|
*/
|
|
45
45
|
export declare const WhenSchema: z.ZodObject<{
|
|
46
46
|
newerThan: z.ZodOptional<z.ZodString>;
|
|
@@ -93,7 +93,7 @@ export declare const CREDENTIAL_TARGETS: readonly ["bearer", "apiKey", "oauthTok
|
|
|
93
93
|
export type CredentialTarget = (typeof CREDENTIAL_TARGETS)[number];
|
|
94
94
|
/** The targets a provider's credential may use: every target except `oauthToken`, which only an identity can carry. */
|
|
95
95
|
export declare const PROVIDER_CREDENTIAL_TARGETS: readonly ["bearer", "apiKey"];
|
|
96
|
-
/** The environment variable each credential target exports the token as. These are also the variables no provider `env` block may set, since
|
|
96
|
+
/** The environment variable each credential target exports the token as. These are also the variables no provider `env` block may set, since agent-shim sets and clears them itself. */
|
|
97
97
|
export declare const CREDENTIAL_TARGET_VARS: {
|
|
98
98
|
readonly bearer: "ANTHROPIC_AUTH_TOKEN";
|
|
99
99
|
readonly apiKey: "ANTHROPIC_API_KEY";
|
|
@@ -133,7 +133,7 @@ export declare const CredentialSourceSchema: z.ZodUnion<readonly [z.ZodObject<{
|
|
|
133
133
|
literal: z.ZodString;
|
|
134
134
|
}, z.core.$strict>]>;
|
|
135
135
|
export type CredentialSource = z.infer<typeof CredentialSourceSchema>;
|
|
136
|
-
/** Where a cached credential is kept: the macOS login Keychain, or a mode-0600 file under the
|
|
136
|
+
/** Where a cached credential is kept: the macOS login Keychain, or a mode-0600 file under the agent-shim home. */
|
|
137
137
|
export declare const CREDENTIAL_CACHE_STORES: readonly ["keychain", "file"];
|
|
138
138
|
export type CredentialCacheStore = (typeof CREDENTIAL_CACHE_STORES)[number];
|
|
139
139
|
/**
|
|
@@ -187,7 +187,7 @@ export declare const CredentialSchema: z.ZodObject<{
|
|
|
187
187
|
}, z.core.$strict>>;
|
|
188
188
|
}, z.core.$strict>;
|
|
189
189
|
export type Credential = z.infer<typeof CredentialSchema>;
|
|
190
|
-
/** The kinds of provider: `http` is an Anthropic Messages endpoint at a fixed `baseUrl` (the default when `kind` is absent), and `codex` is ChatGPT's Codex backend reached through
|
|
190
|
+
/** The kinds of provider: `http` is an Anthropic Messages endpoint at a fixed `baseUrl` (the default when `kind` is absent), and `codex` is ChatGPT's Codex backend reached through agent-shim's own supervised translation daemon. */
|
|
191
191
|
export declare const PROVIDER_KINDS: readonly ["http", "codex"];
|
|
192
192
|
/**
|
|
193
193
|
* The Claude model tiers the codex translator maps, in the order they are matched: the first tier whose name is a substring of the requested model (case-insensitive) decides the codex model. `fable` comes before `opus` because a fable model name is the most specific.
|
|
@@ -382,7 +382,7 @@ export type Provider = z.infer<typeof ProviderSchema>;
|
|
|
382
382
|
/** True when `provider` is a codex provider. `kind` is the tag, since a codex provider's own distinguishing block is optional. */
|
|
383
383
|
export declare function isCodexProvider(provider: Provider): provider is CodexProvider;
|
|
384
384
|
/**
|
|
385
|
-
* A named, reusable configuration profile at `~/.
|
|
385
|
+
* A named, reusable configuration profile at `~/.agent-shim/config-profiles/<name>.json`.
|
|
386
386
|
*
|
|
387
387
|
* `extends` is a flat array of other profiles' *names*, deliberately not a self-referential `z.lazy()` schema: nothing in this shape points back at a profile object, so each file validates in isolation and the extends graph is walked at resolve time. That also means Zod cannot detect a circular `extends` definition — the walker in `src/resolve/extends.ts` carries its own cycle guard.
|
|
388
388
|
*/
|
|
@@ -431,7 +431,7 @@ declare const PoolSchema: z.ZodObject<{
|
|
|
431
431
|
identities: z.ZodArray<z.ZodString>;
|
|
432
432
|
}, z.core.$strict>;
|
|
433
433
|
export type Pool = z.infer<typeof PoolSchema>;
|
|
434
|
-
/** One directory rule in `~/.
|
|
434
|
+
/** One directory rule in `~/.agent-shim/directory-rules.json`, scoped to an explicit absolute (or `~`-rooted) path. */
|
|
435
435
|
export declare const DirectoryRuleSchema: z.ZodObject<{
|
|
436
436
|
entries: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodBoolean, z.ZodObject<{
|
|
437
437
|
value: z.ZodBoolean;
|
|
@@ -477,7 +477,7 @@ export declare const DirectoryRuleSchema: z.ZodObject<{
|
|
|
477
477
|
}, z.core.$strict>>;
|
|
478
478
|
}, z.core.$strict>;
|
|
479
479
|
export type DirectoryRule = z.infer<typeof DirectoryRuleSchema>;
|
|
480
|
-
/** The `~/.
|
|
480
|
+
/** The `~/.agent-shim/directory-rules.json` file. */
|
|
481
481
|
export declare const DirectoryRulesSchema: z.ZodObject<{
|
|
482
482
|
$schema: z.ZodOptional<z.ZodString>;
|
|
483
483
|
rules: z.ZodArray<z.ZodObject<{
|
|
@@ -527,7 +527,7 @@ export declare const DirectoryRulesSchema: z.ZodObject<{
|
|
|
527
527
|
}, z.core.$strict>;
|
|
528
528
|
export type DirectoryRules = z.infer<typeof DirectoryRulesSchema>;
|
|
529
529
|
/**
|
|
530
|
-
* A committed `.
|
|
530
|
+
* A committed `.agent-shim.json` (or its gitignored `.agent-shim.local.json` sibling). Structurally a directory rule without the `path` field: its scope is implicit — wherever the file lives, and everything below it — which is exactly what makes it portable across clone locations.
|
|
531
531
|
*/
|
|
532
532
|
export declare const PortableConfigSchema: z.ZodObject<{
|
|
533
533
|
entries: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodBoolean, z.ZodObject<{
|
|
@@ -607,7 +607,7 @@ export declare const HEADROOM_DEFAULT_IDLE_SHUTDOWN_MINUTES = 15;
|
|
|
607
607
|
* The default `frontdoor.idleShutdownMinutes`, the same trade-off as headroom's: warm through a break, gone the same working day once abandoned.
|
|
608
608
|
*/
|
|
609
609
|
export declare const FRONTDOOR_DEFAULT_IDLE_SHUTDOWN_MINUTES = 15;
|
|
610
|
-
/** The user-global `~/.
|
|
610
|
+
/** The user-global `~/.agent-shim/config.json`. */
|
|
611
611
|
export declare const GlobalConfigSchema: z.ZodObject<{
|
|
612
612
|
$schema: z.ZodOptional<z.ZodString>;
|
|
613
613
|
defaultConfigProfile: z.ZodOptional<z.ZodString>;
|
|
@@ -729,7 +729,7 @@ export declare const CategoryClassificationSchema: z.ZodObject<{
|
|
|
729
729
|
settings: z.ZodArray<z.ZodString>;
|
|
730
730
|
}, z.core.$strict>;
|
|
731
731
|
export type CategoryClassification = z.infer<typeof CategoryClassificationSchema>;
|
|
732
|
-
/** The gitignored `~/.
|
|
732
|
+
/** The gitignored `~/.agent-shim/categories.local.json` overlay: any subset of the classification lists, answering "what category is this new entry?" without editing the shipped default map. */
|
|
733
733
|
export declare const CategoryClassificationOverlaySchema: z.ZodObject<{
|
|
734
734
|
$schema: z.ZodOptional<z.ZodString>;
|
|
735
735
|
secret: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* How a launch's capability token is presented and checked. The token reaches the child twice: as the `x-
|
|
2
|
+
* How a launch's capability token is presented and checked. The token reaches the child twice: as the `x-agent-shim-auth` header on every request it sends (see `AUTH_HEADER`), and as the password in the `HTTPS_PROXY` URL an OAuth launch gets, which every proxy-aware client turns into `Proxy-Authorization: Basic ...` on its CONNECT request. Both are checked against the same live registry with the same constant-time comparison.
|
|
3
3
|
*/
|
|
4
4
|
/** The user name in the proxy URL. A label only: the surface checks the password, which is the capability, and ignores the user name, so nothing depends on how a client spells it. */
|
|
5
|
-
export declare const CONNECT_PROXY_USER = "
|
|
5
|
+
export declare const CONNECT_PROXY_USER = "agent-shim";
|
|
6
6
|
/** The realm the CONNECT surface's 407 challenge names, so a client that waits for a challenge before sending credentials knows which ones to send. */
|
|
7
|
-
export declare const CONNECT_PROXY_REALM = "
|
|
7
|
+
export declare const CONNECT_PROXY_REALM = "agent-shim front door";
|
|
8
8
|
/**
|
|
9
9
|
* The `HTTPS_PROXY` value for a launch: the CONNECT surface's loopback address with the capability as the URL's password. Both userinfo halves are percent-encoded, because a client percent-decodes userinfo before it builds the `Proxy-Authorization` value, so the surface receives exactly the token whatever characters it holds.
|
|
10
10
|
*/
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { IncomingHttpHeaders, IncomingMessage, ServerResponse } from "node:http";
|
|
2
2
|
import * as net from "node:net";
|
|
3
3
|
/**
|
|
4
|
-
* The front door's CONNECT surface, so an OAuth session can have
|
|
4
|
+
* The front door's CONNECT surface, so an OAuth session can have agent-shim's routing AND Claude Code's Remote Control at once: Remote Control refuses any `ANTHROPIC_BASE_URL` other than the real API, but happily honours `HTTPS_PROXY`, so OAuth launches route at the proxy layer instead.
|
|
5
5
|
*
|
|
6
6
|
* Nothing is tunnelled or intercepted for a client that has not authenticated: every CONNECT request must present a live launch's capability as its proxy credential (`Proxy-Authorization: Basic`, which clients derive from the credential in the `HTTPS_PROXY` URL the launcher sets), or it is answered 407 before any target is dialled, so the port is no open proxy for other local processes. Pending and authenticated connections are bounded by `ConnectLimits`.
|
|
7
7
|
*
|
|
@@ -69,7 +69,7 @@ export interface LeafCert {
|
|
|
69
69
|
readonly certPem: string;
|
|
70
70
|
readonly keyPem: string;
|
|
71
71
|
}
|
|
72
|
-
/** Generates the local CA: one RSA keypair, self-signed, CA-only by basicConstraints and keyUsage, with a random serial. Built on `node:crypto` alone (see `x509.ts`), no openssl, so the same code works on every platform
|
|
72
|
+
/** Generates the local CA: one RSA keypair, self-signed, CA-only by basicConstraints and keyUsage, with a random serial. Built on `node:crypto` alone (see `x509.ts`), no openssl, so the same code works on every platform agent-shim ships to. */
|
|
73
73
|
export declare function generateCa(now: Readonly<Date>): CaMaterial;
|
|
74
74
|
/** The names the front door's provider listener answers to: the loopback address every provider session's base URL names, plus the loopback hostname for a client that spells it that way. */
|
|
75
75
|
export declare const LOOPBACK_LEAF_NAMES: readonly string[];
|
|
@@ -15,13 +15,13 @@ export interface EnsureFrontDoorPorts {
|
|
|
15
15
|
readonly sleep: (ms: number) => void;
|
|
16
16
|
/** Spawns the detached front-door supervisor, returning its pid. */
|
|
17
17
|
readonly spawnSupervisor: (paths: LayoutPaths) => number;
|
|
18
|
-
/** Authenticates the provider listener on `port`: a TLS handshake chaining to
|
|
18
|
+
/** Authenticates the provider listener on `port`: a TLS handshake chaining to agent-shim's CA and a healthy answer, with no capability sent (see `probeFrontDoorSync`). */
|
|
19
19
|
readonly verifyListener: (port: number) => ListenerVerdict;
|
|
20
20
|
}
|
|
21
21
|
/**
|
|
22
22
|
* Brings the front door up for this launch, or finds it serving, and registers this launcher pid in its session registry: the fact that keeps the door from idling out while this session lives, and the record that holds this launch's capability token. The same lock-and-poll coordination as headroom's and the old codex daemon's ensure: the exclusive-create start lock decides which of several concurrent launches spawns the one supervisor, everyone waits on state.json, and a lock whose holder died is removed and retried. Ready means state names a live supervisor and both listeners' ports, which the supervisor writes only after both have bound (and the provider listener has answered its own health probe).
|
|
23
23
|
*
|
|
24
|
-
* A live pid in state proves nothing about who holds the port it names: the door may have crashed and left the port to anyone, or the pid may have been reused by an unrelated process. So before anything is registered or returned, the provider listener is authenticated over TLS against
|
|
24
|
+
* A live pid in state proves nothing about who holds the port it names: the door may have crashed and left the port to anyone, or the pid may have been reused by an unrelated process. So before anything is registered or returned, the provider listener is authenticated over TLS against agent-shim's CA. A listener that fails (wrong certificate, nothing answering, a bad answer) is treated as a stale or hostile owner: its pid is distrusted for the rest of this call and a replacement supervisor is spawned, which binds a free port when the sticky one is held and is then verified in turn. If a supervisor this call spawned also fails verification, the launch is refused naming the reason and the daemon log; no capability is written and no address is handed to the child.
|
|
25
25
|
*/
|
|
26
26
|
export declare function ensureFrontDoor(params: {
|
|
27
27
|
readonly paths: LayoutPaths;
|
|
@@ -6,7 +6,7 @@ export type ListenerVerdict = {
|
|
|
6
6
|
readonly reason: string;
|
|
7
7
|
};
|
|
8
8
|
/**
|
|
9
|
-
* Authenticates the front door's provider listener before a launch trusts it: a TLS handshake that must chain to
|
|
9
|
+
* Authenticates the front door's provider listener before a launch trusts it: a TLS handshake that must chain to agent-shim's CA (whose key only this user can read) for 127.0.0.1, then a healthy answer. A process that merely holds the port (a stale state file's port re-bound by someone else, or a reused pid's listener) cannot complete that handshake, so it fails here and the launch never hands its child that address.
|
|
10
10
|
*
|
|
11
11
|
* Synchronous because the launcher is synchronous right through to `spawnSync`: the HTTPS request runs on a worker thread and this thread blocks on a shared buffer until the worker posts its verdict.
|
|
12
12
|
*/
|
|
@@ -17,7 +17,7 @@ export interface FrontDoorSupervisorPorts {
|
|
|
17
17
|
/** Zombie-aware liveness, the notion every coordination decision in this project uses. */
|
|
18
18
|
readonly isRunning: (pid: number) => boolean;
|
|
19
19
|
/**
|
|
20
|
-
* Starts the provider listener in this process on `preferredPort` (falling back to any free port when the sticky one is taken, decided at bind time rather than by a racy check-then-bind probe) and resolves once it has bound and answered its own health probe over TLS. This is the HTTPS listener every provider session's base URL points at, serving a loopback leaf signed by
|
|
20
|
+
* Starts the provider listener in this process on `preferredPort` (falling back to any free port when the sticky one is taken, decided at bind time rather than by a racy check-then-bind probe) and resolves once it has bound and answered its own health probe over TLS. This is the HTTPS listener every provider session's base URL points at, serving a loopback leaf signed by agent-shim's CA.
|
|
21
21
|
*/
|
|
22
22
|
readonly startProviderListener: (preferredPort: number | undefined) => Promise<FrontDoorListenerHandle>;
|
|
23
23
|
/**
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/** An IP address as the 4 or 16 octets an iPAddress GeneralName carries. */
|
|
2
2
|
export declare function ipBytes(address: string): Buffer;
|
|
3
|
-
/** A key pair in the PEM forms
|
|
3
|
+
/** A key pair in the PEM forms agent-shim persists. */
|
|
4
4
|
export interface PemKeyPair {
|
|
5
5
|
readonly certPem: string;
|
|
6
6
|
readonly keyPem: string;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether an install spec resolves through a git ref that can move underneath it. A full commit SHA never moves; a branch name, a tag a maintainer can re-point, or no ref at all (the remote's default branch) can, and a rebased, renamed or deleted branch changes or breaks every fresh install. A registry requirement is not a git source.
|
|
3
|
+
*/
|
|
4
|
+
export declare function isMovingGitSource(spec: string): boolean;
|
|
5
|
+
/** The full commit SHA an install spec pins, lowercased, or undefined when the spec is a registry requirement or follows a moving ref. */
|
|
6
|
+
export declare function pinnedGitCommit(spec: string): string | undefined;
|
|
7
|
+
/** The commit an install was built from, read from the text of its `direct_url.json`, or undefined when the file records no VCS commit (a registry install) or is not valid JSON. */
|
|
8
|
+
export declare function parseInstalledCommit(directUrlJson: string): string | undefined;
|
|
@@ -38,7 +38,7 @@ export type HeadroomLock = z.infer<typeof HeadroomLockSchema>;
|
|
|
38
38
|
/** Reads state.json, or undefined when absent. A malformed file is treated as absent: the lock-and-spawn flow below overwrites it with a fresh supervisor's state rather than crashing every future launch over one bad write. */
|
|
39
39
|
export declare function readHeadroomState(fs: HeadroomFs, stateFile: string): HeadroomState | undefined;
|
|
40
40
|
/**
|
|
41
|
-
* Writes state.json
|
|
41
|
+
* Writes state.json atomically (a temporary sibling renamed into place), so a concurrent reader never sees a partial file. A reader treats a malformed file as absent, and a launcher that finds no supervisor in state spawns one, so a torn write read mid-flight started a redundant supervisor and daemon.
|
|
42
42
|
*/
|
|
43
43
|
export declare function writeHeadroomState(fs: HeadroomFs, stateFile: string, state: Readonly<HeadroomState>): void;
|
|
44
44
|
/** The allowlist the daemon is started with: every provider's base URL plus Claude Code's own API, so a session with no provider selected reaches the real Anthropic upstream through the same proxy. */
|