feat: mac node exec policy + remote skills hot reload

This commit is contained in:
Peter Steinberger
2026-01-16 03:45:03 +00:00
parent abcca86e4e
commit b2b331230b
36 changed files with 977 additions and 40 deletions

View File

@@ -3,6 +3,7 @@ import type { CanvasHostHandler, CanvasHostServer } from "../canvas-host/server.
import { startCanvasHost } from "../canvas-host/server.js";
import type { CliDeps } from "../cli/deps.js";
import type { HealthSummary } from "../commands/health.js";
import type { ClawdbotConfig } from "../config/config.js";
import { deriveDefaultBridgePort, deriveDefaultCanvasHostPort } from "../config/port-defaults.js";
import type { NodeBridgeServer } from "../infra/bridge/server.js";
import { pickPrimaryTailnetIPv4, pickPrimaryTailnetIPv6 } from "../infra/tailnet.js";
@@ -33,15 +34,7 @@ export type GatewayBridgeRuntime = {
};
export async function startGatewayBridgeRuntime(params: {
cfg: {
bridge?: {
enabled?: boolean;
port?: number;
bind?: "loopback" | "lan" | "auto" | "custom";
};
canvasHost?: { port?: number; root?: string; liveReload?: boolean };
discovery?: { wideArea?: { enabled?: boolean } };
};
cfg: ClawdbotConfig;
port: number;
canvasHostEnabled: boolean;
canvasHost: CanvasHostHandler | null;
@@ -200,6 +193,7 @@ export async function startGatewayBridgeRuntime(params: {
: undefined;
const bridgeRuntime = await startGatewayNodeBridge({
cfg: params.cfg,
bridgeEnabled,
bridgePort,
bridgeHost,

View File

@@ -3,6 +3,7 @@ import { installSkill } from "../../agents/skills-install.js";
import { buildWorkspaceSkillStatus } from "../../agents/skills-status.js";
import type { ClawdbotConfig } from "../../config/config.js";
import { loadConfig, writeConfigFile } from "../../config/config.js";
import { getRemoteSkillEligibility } from "../../infra/skills-remote.js";
import {
ErrorCodes,
errorShape,
@@ -30,6 +31,7 @@ export const skillsHandlers: GatewayRequestHandlers = {
const workspaceDir = resolveAgentWorkspaceDir(cfg, resolveDefaultAgentId(cfg));
const report = buildWorkspaceSkillStatus(workspaceDir, {
config: cfg,
eligibility: { remote: getRemoteSkillEligibility() },
});
respond(true, report, undefined);
},

View File

@@ -1,5 +1,8 @@
import type { NodeBridgeServer } from "../infra/bridge/server.js";
import { startNodeBridgeServer } from "../infra/bridge/server.js";
import type { ClawdbotConfig } from "../config/config.js";
import { bumpSkillsSnapshotVersion } from "../agents/skills/refresh.js";
import { recordRemoteNodeInfo, refreshRemoteNodeBins } from "../infra/skills-remote.js";
import { listSystemPresence, upsertPresence } from "../infra/system-presence.js";
import { loadVoiceWakeConfig } from "../infra/voicewake.js";
import { isLoopbackAddress } from "./net.js";
@@ -16,6 +19,7 @@ export type GatewayNodeBridgeRuntime = {
};
export async function startGatewayNodeBridge(params: {
cfg: ClawdbotConfig;
bridgeEnabled: boolean;
bridgePort: number;
bridgeHost: string | null;
@@ -114,6 +118,21 @@ export async function startGatewayNodeBridge(params: {
onAuthenticated: async (node) => {
beaconNodePresence(node, "node-connected");
startNodePresenceTimer(node);
recordRemoteNodeInfo({
nodeId: node.nodeId,
displayName: node.displayName,
platform: node.platform,
deviceFamily: node.deviceFamily,
commands: node.commands,
});
bumpSkillsSnapshotVersion({ reason: "remote-node" });
await refreshRemoteNodeBins({
nodeId: node.nodeId,
platform: node.platform,
deviceFamily: node.deviceFamily,
commands: node.commands,
cfg: params.cfg,
});
try {
const cfg = await loadVoiceWakeConfig();

View File

@@ -1,5 +1,6 @@
import { resolveAgentWorkspaceDir, resolveDefaultAgentId } from "../agents/agent-scope.js";
import { initSubagentRegistry } from "../agents/subagent-registry.js";
import { registerSkillsChangeListener } from "../agents/skills/refresh.js";
import type { CanvasHostServer } from "../canvas-host/server.js";
import { type ChannelId, listChannelPlugins } from "../channels/plugins/index.js";
import { createDefaultDeps } from "../cli/deps.js";
@@ -16,6 +17,11 @@ import { onHeartbeatEvent } from "../infra/heartbeat-events.js";
import { startHeartbeatRunner } from "../infra/heartbeat-runner.js";
import { getMachineDisplayName } from "../infra/machine-name.js";
import { ensureClawdbotCliOnPath } from "../infra/path-env.js";
import {
primeRemoteSkillsCache,
refreshRemoteBinsForConnectedNodes,
setSkillsRemoteBridge,
} from "../infra/skills-remote.js";
import { autoMigrateLegacyState } from "../infra/state-migrations.js";
import { createSubsystemLogger, runtimeForLogger } from "../logging.js";
import type { PluginServicesHandle } from "../plugins/services.js";
@@ -288,6 +294,13 @@ export async function startGatewayServer(
const bridgeSendToAllSubscribed = bridgeRuntime.bridgeSendToAllSubscribed;
const broadcastVoiceWakeChanged = bridgeRuntime.broadcastVoiceWakeChanged;
setSkillsRemoteBridge(bridge);
void primeRemoteSkillsCache();
registerSkillsChangeListener(() => {
const latest = loadConfig();
void refreshRemoteBinsForConnectedNodes(latest);
});
const { tickInterval, healthInterval, dedupeCleanup } = startGatewayMaintenanceTimers({
broadcast,
bridgeSendToAllSubscribed,