feat: add Tailscale binary detection, IP binding modes, and health probe password fix
This PR includes three main improvements:
1. Tailscale Binary Detection with Fallback Strategies
- Added findTailscaleBinary() with multi-strategy detection:
* PATH lookup via 'which' command
* Known macOS app path (/Applications/Tailscale.app/Contents/MacOS/Tailscale)
* find /Applications for Tailscale.app
* locate database lookup
- Added getTailscaleBinary() with caching
- Updated all Tailscale operations to use detected binary
- Added TUI warning when Tailscale binary not found for serve/funnel modes
2. Custom Gateway IP Binding with Fallback
- New bind mode "custom" allowing user-specified IP with fallback to 0.0.0.0
- Removed "tailnet" mode (folded into "auto")
- All modes now support graceful fallback: custom (if fail → 0.0.0.0), loopback (127.0.0.1 → 0.0.0.0), auto (tailnet → 0.0.0.0), lan (0.0.0.0)
- Added customBindHost config option for custom bind mode
- Added canBindTo() helper to test IP availability before binding
- Updated configure and onboarding wizards with new bind mode options
3. Health Probe Password Auth Fix
- Gateway probe now tries both new and old passwords
- Fixes issue where password change fails health check if gateway hasn't restarted yet
- Uses nextConfig password first, falls back to baseConfig password if needed
Files changed:
- src/infra/tailscale.ts: Binary detection + caching
- src/gateway/net.ts: IP binding with fallback logic
- src/config/types.ts: BridgeBindMode type + customBindHost field
- src/commands/configure.ts: Health probe dual-password try + Tailscale detection warning + bind mode UI
- src/wizard/onboarding.ts: Tailscale detection warning + bind mode UI
- src/gateway/server.ts: Use new resolveGatewayBindHost
- src/gateway/call.ts: Updated preferTailnet logic (removed "tailnet" mode)
- src/commands/onboard-types.ts: Updated GatewayBind type
- src/commands/onboard-helpers.ts: resolveControlUiLinks updated
- src/cli/*.ts: Updated bind mode casts
- src/gateway/call.test.ts: Removed "tailnet" mode test
This commit is contained in:
committed by
Peter Steinberger
parent
f94ad21f1e
commit
c851bdd47a
@@ -417,6 +417,85 @@ type EmbeddedPiQueueHandle = {
|
||||
|
||||
const log = createSubsystemLogger("agent/embedded");
|
||||
const GOOGLE_TURN_ORDERING_CUSTOM_TYPE = "google-turn-ordering-bootstrap";
|
||||
const GOOGLE_SCHEMA_UNSUPPORTED_KEYWORDS = new Set([
|
||||
"patternProperties",
|
||||
"additionalProperties",
|
||||
"$schema",
|
||||
"$id",
|
||||
"$ref",
|
||||
"$defs",
|
||||
"definitions",
|
||||
"examples",
|
||||
"minLength",
|
||||
"maxLength",
|
||||
"minimum",
|
||||
"maximum",
|
||||
"multipleOf",
|
||||
"pattern",
|
||||
"format",
|
||||
"minItems",
|
||||
"maxItems",
|
||||
"uniqueItems",
|
||||
"minProperties",
|
||||
"maxProperties",
|
||||
]);
|
||||
|
||||
function findUnsupportedSchemaKeywords(
|
||||
schema: unknown,
|
||||
path: string,
|
||||
): string[] {
|
||||
if (!schema || typeof schema !== "object") return [];
|
||||
if (Array.isArray(schema)) {
|
||||
return schema.flatMap((item, index) =>
|
||||
findUnsupportedSchemaKeywords(item, `${path}[${index}]`),
|
||||
);
|
||||
}
|
||||
const record = schema as Record<string, unknown>;
|
||||
const violations: string[] = [];
|
||||
for (const [key, value] of Object.entries(record)) {
|
||||
if (GOOGLE_SCHEMA_UNSUPPORTED_KEYWORDS.has(key)) {
|
||||
violations.push(`${path}.${key}`);
|
||||
}
|
||||
if (value && typeof value === "object") {
|
||||
violations.push(
|
||||
...findUnsupportedSchemaKeywords(value, `${path}.${key}`),
|
||||
);
|
||||
}
|
||||
}
|
||||
return violations;
|
||||
}
|
||||
|
||||
function logToolSchemasForGoogle(params: {
|
||||
tools: AgentTool[];
|
||||
provider: string;
|
||||
}) {
|
||||
if (
|
||||
params.provider !== "google-antigravity" &&
|
||||
params.provider !== "google-gemini-cli"
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const toolNames = params.tools.map((tool, index) => `${index}:${tool.name}`);
|
||||
log.info("google tool schema snapshot", {
|
||||
provider: params.provider,
|
||||
toolCount: params.tools.length,
|
||||
tools: toolNames,
|
||||
});
|
||||
for (const [index, tool] of params.tools.entries()) {
|
||||
const violations = findUnsupportedSchemaKeywords(
|
||||
tool.parameters,
|
||||
`${tool.name}.parameters`,
|
||||
);
|
||||
if (violations.length > 0) {
|
||||
log.warn("google tool schema has unsupported keywords", {
|
||||
index,
|
||||
tool: tool.name,
|
||||
violations: violations.slice(0, 12),
|
||||
violationCount: violations.length,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
registerUnhandledRejectionHandler((reason) => {
|
||||
const message = describeUnknownError(reason);
|
||||
@@ -1178,6 +1257,7 @@ export async function compactEmbeddedPiSession(params: {
|
||||
modelAuthMode: resolveModelAuthMode(model.provider, params.config),
|
||||
// No currentChannelId/currentThreadTs for compaction - not in message context
|
||||
});
|
||||
logToolSchemasForGoogle({ tools, provider });
|
||||
const machineName = await getMachineDisplayName();
|
||||
const runtimeProvider = normalizeMessageProvider(
|
||||
params.messageProvider,
|
||||
@@ -1620,6 +1700,7 @@ export async function runEmbeddedPiAgent(params: {
|
||||
replyToMode: params.replyToMode,
|
||||
hasRepliedRef: params.hasRepliedRef,
|
||||
});
|
||||
logToolSchemasForGoogle({ tools, provider });
|
||||
const machineName = await getMachineDisplayName();
|
||||
const runtimeInfo = {
|
||||
host: machineName,
|
||||
|
||||
Reference in New Issue
Block a user