fix: canonicalize main session keys
This commit is contained in:
@@ -36,6 +36,7 @@
|
||||
- Config: add `channels.<provider>.configWrites` gating for channel-initiated config writes; migrate Slack channel IDs.
|
||||
|
||||
### Fixes
|
||||
|
||||
#### Agents
|
||||
- Agents: make user time zone and 24-hour time explicit in the system prompt. (#859) — thanks @CashWilliams.
|
||||
- Agents: strip downgraded tool call text without eating adjacent replies and filter thinking-tag leaks. (#905) — thanks @erikpr1994.
|
||||
@@ -72,6 +73,7 @@
|
||||
- WhatsApp: normalize user JIDs with device suffix for allowlist checks in groups. (#838) — thanks @peschee.
|
||||
|
||||
#### Gateway / Daemon / Sessions
|
||||
- Gateway/UI: ship session defaults in the hello snapshot so the Control UI canonicalizes main session keys (no bare `main` alias).
|
||||
- Gateway: forward termination signals to respawned CLI child processes to avoid orphaned systemd runs. (#933) — thanks @roshanasingh4.
|
||||
- Sessions: return deep clones (`structuredClone`) so cached session entries can't be mutated. (#934) — thanks @ronak-guliani.
|
||||
- Heartbeat: keep `updatedAt` monotonic when restoring heartbeat sessions. (#934) — thanks @ronak-guliani.
|
||||
|
||||
@@ -22,6 +22,16 @@ export const PresenceEntrySchema = Type.Object(
|
||||
|
||||
export const HealthSnapshotSchema = Type.Any();
|
||||
|
||||
export const SessionDefaultsSchema = Type.Object(
|
||||
{
|
||||
defaultAgentId: NonEmptyString,
|
||||
mainKey: NonEmptyString,
|
||||
mainSessionKey: NonEmptyString,
|
||||
scope: Type.Optional(NonEmptyString),
|
||||
},
|
||||
{ additionalProperties: false },
|
||||
);
|
||||
|
||||
export const StateVersionSchema = Type.Object(
|
||||
{
|
||||
presence: Type.Integer({ minimum: 0 }),
|
||||
@@ -38,6 +48,7 @@ export const SnapshotSchema = Type.Object(
|
||||
uptimeMs: Type.Integer({ minimum: 0 }),
|
||||
configPath: Type.Optional(NonEmptyString),
|
||||
stateDir: Type.Optional(NonEmptyString),
|
||||
sessionDefaults: Type.Optional(SessionDefaultsSchema),
|
||||
},
|
||||
{ additionalProperties: false },
|
||||
);
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { resolveDefaultAgentId } from "../../agents/agent-scope.js";
|
||||
import { getHealthSnapshot, type HealthSummary } from "../../commands/health.js";
|
||||
import { CONFIG_PATH_CLAWDBOT, STATE_DIR_CLAWDBOT } from "../../config/config.js";
|
||||
import { CONFIG_PATH_CLAWDBOT, STATE_DIR_CLAWDBOT, loadConfig } from "../../config/config.js";
|
||||
import { resolveMainSessionKey } from "../../config/sessions.js";
|
||||
import { normalizeMainKey } from "../../routing/session-key.js";
|
||||
import { listSystemPresence } from "../../infra/system-presence.js";
|
||||
import type { Snapshot } from "../protocol/index.js";
|
||||
|
||||
@@ -10,6 +13,11 @@ let healthRefresh: Promise<HealthSummary> | null = null;
|
||||
let broadcastHealthUpdate: ((snap: HealthSummary) => void) | null = null;
|
||||
|
||||
export function buildGatewaySnapshot(): Snapshot {
|
||||
const cfg = loadConfig();
|
||||
const defaultAgentId = resolveDefaultAgentId(cfg);
|
||||
const mainKey = normalizeMainKey(cfg.session?.mainKey);
|
||||
const mainSessionKey = resolveMainSessionKey(cfg);
|
||||
const scope = cfg.session?.scope ?? "per-sender";
|
||||
const presence = listSystemPresence();
|
||||
const uptimeMs = Math.round(process.uptime() * 1000);
|
||||
// Health is async; caller should await getHealthSnapshot and replace later if needed.
|
||||
@@ -22,6 +30,12 @@ export function buildGatewaySnapshot(): Snapshot {
|
||||
// Surface resolved paths so UIs can display the true config location.
|
||||
configPath: CONFIG_PATH_CLAWDBOT,
|
||||
stateDir: STATE_DIR_CLAWDBOT,
|
||||
sessionDefaults: {
|
||||
defaultAgentId,
|
||||
mainKey,
|
||||
mainSessionKey,
|
||||
scope,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -5,14 +5,20 @@ import { GatewayBrowserClient } from "./gateway";
|
||||
import type { EventLogEntry } from "./app-events";
|
||||
import type { PresenceEntry, HealthSnapshot, StatusSummary } from "./types";
|
||||
import type { Tab } from "./navigation";
|
||||
import type { UiSettings } from "./storage";
|
||||
import { handleAgentEvent, resetToolStream, type AgentEventPayload } from "./app-tool-stream";
|
||||
import { flushChatQueueForEvent } from "./app-chat";
|
||||
import { loadCron, refreshActiveTab, setLastActiveSessionKey } from "./app-settings";
|
||||
import {
|
||||
applySettings,
|
||||
loadCron,
|
||||
refreshActiveTab,
|
||||
setLastActiveSessionKey,
|
||||
} from "./app-settings";
|
||||
import { handleChatEvent, type ChatEventPayload } from "./controllers/chat";
|
||||
import type { ClawdbotApp } from "./app";
|
||||
|
||||
type GatewayHost = {
|
||||
settings: { gatewayUrl: string; token: string };
|
||||
settings: UiSettings;
|
||||
password: string;
|
||||
client: GatewayBrowserClient | null;
|
||||
connected: boolean;
|
||||
@@ -29,6 +35,60 @@ type GatewayHost = {
|
||||
chatRunId: string | null;
|
||||
};
|
||||
|
||||
type SessionDefaultsSnapshot = {
|
||||
defaultAgentId?: string;
|
||||
mainKey?: string;
|
||||
mainSessionKey?: string;
|
||||
scope?: string;
|
||||
};
|
||||
|
||||
function normalizeSessionKeyForDefaults(
|
||||
value: string | undefined,
|
||||
defaults: SessionDefaultsSnapshot,
|
||||
): string {
|
||||
const raw = (value ?? "").trim();
|
||||
const mainSessionKey = defaults.mainSessionKey?.trim();
|
||||
if (!mainSessionKey) return raw;
|
||||
if (!raw) return mainSessionKey;
|
||||
const mainKey = defaults.mainKey?.trim() || "main";
|
||||
const defaultAgentId = defaults.defaultAgentId?.trim();
|
||||
const isAlias =
|
||||
raw === "main" ||
|
||||
raw === mainKey ||
|
||||
(defaultAgentId &&
|
||||
(raw === `agent:${defaultAgentId}:main` ||
|
||||
raw === `agent:${defaultAgentId}:${mainKey}`));
|
||||
return isAlias ? mainSessionKey : raw;
|
||||
}
|
||||
|
||||
function applySessionDefaults(host: GatewayHost, defaults?: SessionDefaultsSnapshot) {
|
||||
if (!defaults?.mainSessionKey) return;
|
||||
const resolvedSessionKey = normalizeSessionKeyForDefaults(host.sessionKey, defaults);
|
||||
const resolvedSettingsSessionKey = normalizeSessionKeyForDefaults(
|
||||
host.settings.sessionKey,
|
||||
defaults,
|
||||
);
|
||||
const resolvedLastActiveSessionKey = normalizeSessionKeyForDefaults(
|
||||
host.settings.lastActiveSessionKey,
|
||||
defaults,
|
||||
);
|
||||
const nextSessionKey = resolvedSessionKey || resolvedSettingsSessionKey || host.sessionKey;
|
||||
const nextSettings = {
|
||||
...host.settings,
|
||||
sessionKey: resolvedSettingsSessionKey || nextSessionKey,
|
||||
lastActiveSessionKey: resolvedLastActiveSessionKey || nextSessionKey,
|
||||
};
|
||||
const shouldUpdateSettings =
|
||||
nextSettings.sessionKey !== host.settings.sessionKey ||
|
||||
nextSettings.lastActiveSessionKey !== host.settings.lastActiveSessionKey;
|
||||
if (nextSessionKey !== host.sessionKey) {
|
||||
host.sessionKey = nextSessionKey;
|
||||
}
|
||||
if (shouldUpdateSettings) {
|
||||
applySettings(host as unknown as Parameters<typeof applySettings>[0], nextSettings);
|
||||
}
|
||||
}
|
||||
|
||||
export function connectGateway(host: GatewayHost) {
|
||||
host.lastError = null;
|
||||
host.hello = null;
|
||||
@@ -113,7 +173,11 @@ export function handleGatewayEvent(host: GatewayHost, evt: GatewayEventFrame) {
|
||||
|
||||
export function applySnapshot(host: GatewayHost, hello: GatewayHelloOk) {
|
||||
const snapshot = hello.snapshot as
|
||||
| { presence?: PresenceEntry[]; health?: HealthSnapshot }
|
||||
| {
|
||||
presence?: PresenceEntry[];
|
||||
health?: HealthSnapshot;
|
||||
sessionDefaults?: SessionDefaultsSnapshot;
|
||||
}
|
||||
| undefined;
|
||||
if (snapshot?.presence && Array.isArray(snapshot.presence)) {
|
||||
host.presenceEntries = snapshot.presence;
|
||||
@@ -121,4 +185,7 @@ export function applySnapshot(host: GatewayHost, hello: GatewayHelloOk) {
|
||||
if (snapshot?.health) {
|
||||
host.debugHealth = snapshot.health;
|
||||
}
|
||||
if (snapshot?.sessionDefaults) {
|
||||
applySessionDefaults(host, snapshot.sessionDefaults);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user