refactor(sessions): dedupe sessions.resolve
This commit is contained in:
@@ -80,6 +80,7 @@ import {
|
|||||||
type SessionsPatchResult,
|
type SessionsPatchResult,
|
||||||
} from "./session-utils.js";
|
} from "./session-utils.js";
|
||||||
import { applySessionsPatchToStore } from "./sessions-patch.js";
|
import { applySessionsPatchToStore } from "./sessions-patch.js";
|
||||||
|
import { resolveSessionKeyFromResolveParams } from "./sessions-resolve.js";
|
||||||
import { formatForLog } from "./ws-log.js";
|
import { formatForLog } from "./ws-log.js";
|
||||||
|
|
||||||
export type BridgeHandlersContext = {
|
export type BridgeHandlersContext = {
|
||||||
@@ -314,93 +315,20 @@ export function createBridgeHandlers(ctx: BridgeHandlersContext) {
|
|||||||
|
|
||||||
const p = params as SessionsResolveParams;
|
const p = params as SessionsResolveParams;
|
||||||
const cfg = loadConfig();
|
const cfg = loadConfig();
|
||||||
|
const resolved = resolveSessionKeyFromResolveParams({ cfg, p });
|
||||||
const key = typeof p.key === "string" ? p.key.trim() : "";
|
if (!resolved.ok) {
|
||||||
const label = typeof p.label === "string" ? p.label.trim() : "";
|
|
||||||
const hasKey = key.length > 0;
|
|
||||||
const hasLabel = label.length > 0;
|
|
||||||
if (hasKey && hasLabel) {
|
|
||||||
return {
|
return {
|
||||||
ok: false,
|
ok: false,
|
||||||
error: {
|
error: {
|
||||||
code: ErrorCodes.INVALID_REQUEST,
|
code: resolved.error.code,
|
||||||
message: "Provide either key or label (not both)",
|
message: resolved.error.message,
|
||||||
},
|
details: resolved.error.details,
|
||||||
};
|
|
||||||
}
|
|
||||||
if (!hasKey && !hasLabel) {
|
|
||||||
return {
|
|
||||||
ok: false,
|
|
||||||
error: {
|
|
||||||
code: ErrorCodes.INVALID_REQUEST,
|
|
||||||
message: "Either key or label is required",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasKey) {
|
|
||||||
const target = resolveGatewaySessionStoreTarget({ cfg, key });
|
|
||||||
const store = loadSessionStore(target.storePath);
|
|
||||||
const existingKey = target.storeKeys.find(
|
|
||||||
(candidate) => store[candidate],
|
|
||||||
);
|
|
||||||
if (!existingKey) {
|
|
||||||
return {
|
|
||||||
ok: false,
|
|
||||||
error: {
|
|
||||||
code: ErrorCodes.INVALID_REQUEST,
|
|
||||||
message: `No session found: ${key}`,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
ok: true,
|
|
||||||
payloadJSON: JSON.stringify({
|
|
||||||
ok: true,
|
|
||||||
key: target.canonicalKey,
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const { storePath, store } = loadCombinedSessionStoreForGateway(cfg);
|
|
||||||
const list = listSessionsFromStore({
|
|
||||||
cfg,
|
|
||||||
storePath,
|
|
||||||
store,
|
|
||||||
opts: {
|
|
||||||
includeGlobal: p.includeGlobal === true,
|
|
||||||
includeUnknown: p.includeUnknown === true,
|
|
||||||
label,
|
|
||||||
agentId: p.agentId,
|
|
||||||
spawnedBy: p.spawnedBy,
|
|
||||||
limit: 2,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
if (list.sessions.length === 0) {
|
|
||||||
return {
|
|
||||||
ok: false,
|
|
||||||
error: {
|
|
||||||
code: ErrorCodes.INVALID_REQUEST,
|
|
||||||
message: `No session found with label: ${label}`,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (list.sessions.length > 1) {
|
|
||||||
const keys = list.sessions.map((s) => s.key).join(", ");
|
|
||||||
return {
|
|
||||||
ok: false,
|
|
||||||
error: {
|
|
||||||
code: ErrorCodes.INVALID_REQUEST,
|
|
||||||
message: `Multiple sessions found with label: ${label} (${keys})`,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
ok: true,
|
ok: true,
|
||||||
payloadJSON: JSON.stringify({
|
payloadJSON: JSON.stringify({ ok: true, key: resolved.key }),
|
||||||
ok: true,
|
|
||||||
key: list.sessions[0]?.key,
|
|
||||||
}),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
case "sessions.patch": {
|
case "sessions.patch": {
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ import {
|
|||||||
type SessionsPatchResult,
|
type SessionsPatchResult,
|
||||||
} from "../session-utils.js";
|
} from "../session-utils.js";
|
||||||
import { applySessionsPatchToStore } from "../sessions-patch.js";
|
import { applySessionsPatchToStore } from "../sessions-patch.js";
|
||||||
|
import { resolveSessionKeyFromResolveParams } from "../sessions-resolve.js";
|
||||||
import type { GatewayRequestHandlers } from "./types.js";
|
import type { GatewayRequestHandlers } from "./types.js";
|
||||||
|
|
||||||
export const sessionsHandlers: GatewayRequestHandlers = {
|
export const sessionsHandlers: GatewayRequestHandlers = {
|
||||||
@@ -76,106 +77,12 @@ export const sessionsHandlers: GatewayRequestHandlers = {
|
|||||||
const p = params as import("../protocol/index.js").SessionsResolveParams;
|
const p = params as import("../protocol/index.js").SessionsResolveParams;
|
||||||
const cfg = loadConfig();
|
const cfg = loadConfig();
|
||||||
|
|
||||||
const key = typeof p.key === "string" ? p.key.trim() : "";
|
const resolved = resolveSessionKeyFromResolveParams({ cfg, p });
|
||||||
const label = typeof p.label === "string" ? p.label.trim() : "";
|
if (!resolved.ok) {
|
||||||
const hasKey = key.length > 0;
|
respond(false, undefined, resolved.error);
|
||||||
const hasLabel = label.length > 0;
|
|
||||||
if (hasKey && hasLabel) {
|
|
||||||
respond(
|
|
||||||
false,
|
|
||||||
undefined,
|
|
||||||
errorShape(
|
|
||||||
ErrorCodes.INVALID_REQUEST,
|
|
||||||
"Provide either key or label (not both)",
|
|
||||||
),
|
|
||||||
);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!hasKey && !hasLabel) {
|
respond(true, { ok: true, key: resolved.key }, undefined);
|
||||||
respond(
|
|
||||||
false,
|
|
||||||
undefined,
|
|
||||||
errorShape(
|
|
||||||
ErrorCodes.INVALID_REQUEST,
|
|
||||||
"Either key or label is required",
|
|
||||||
),
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasKey) {
|
|
||||||
if (!key) {
|
|
||||||
respond(
|
|
||||||
false,
|
|
||||||
undefined,
|
|
||||||
errorShape(ErrorCodes.INVALID_REQUEST, "key required"),
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const target = resolveGatewaySessionStoreTarget({ cfg, key });
|
|
||||||
const store = loadSessionStore(target.storePath);
|
|
||||||
const existingKey = target.storeKeys.find(
|
|
||||||
(candidate) => store[candidate],
|
|
||||||
);
|
|
||||||
if (!existingKey) {
|
|
||||||
respond(
|
|
||||||
false,
|
|
||||||
undefined,
|
|
||||||
errorShape(ErrorCodes.INVALID_REQUEST, `No session found: ${key}`),
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
respond(true, { ok: true, key: target.canonicalKey }, undefined);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!label) {
|
|
||||||
respond(
|
|
||||||
false,
|
|
||||||
undefined,
|
|
||||||
errorShape(ErrorCodes.INVALID_REQUEST, "label required"),
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { storePath, store } = loadCombinedSessionStoreForGateway(cfg);
|
|
||||||
const list = listSessionsFromStore({
|
|
||||||
cfg,
|
|
||||||
storePath,
|
|
||||||
store,
|
|
||||||
opts: {
|
|
||||||
includeGlobal: p.includeGlobal === true,
|
|
||||||
includeUnknown: p.includeUnknown === true,
|
|
||||||
label,
|
|
||||||
agentId: p.agentId,
|
|
||||||
spawnedBy: p.spawnedBy,
|
|
||||||
limit: 2,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
if (list.sessions.length === 0) {
|
|
||||||
respond(
|
|
||||||
false,
|
|
||||||
undefined,
|
|
||||||
errorShape(
|
|
||||||
ErrorCodes.INVALID_REQUEST,
|
|
||||||
`No session found with label: ${label}`,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (list.sessions.length > 1) {
|
|
||||||
const keys = list.sessions.map((s) => s.key).join(", ");
|
|
||||||
respond(
|
|
||||||
false,
|
|
||||||
undefined,
|
|
||||||
errorShape(
|
|
||||||
ErrorCodes.INVALID_REQUEST,
|
|
||||||
`Multiple sessions found with label: ${label} (${keys})`,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
respond(true, { ok: true, key: list.sessions[0]?.key }, undefined);
|
|
||||||
},
|
},
|
||||||
"sessions.patch": async ({ params, respond, context }) => {
|
"sessions.patch": async ({ params, respond, context }) => {
|
||||||
if (!validateSessionsPatchParams(params)) {
|
if (!validateSessionsPatchParams(params)) {
|
||||||
|
|||||||
107
src/gateway/sessions-resolve.ts
Normal file
107
src/gateway/sessions-resolve.ts
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
import type { ClawdbotConfig } from "../config/config.js";
|
||||||
|
import { loadSessionStore } from "../config/sessions.js";
|
||||||
|
import { parseSessionLabel } from "../sessions/session-label.js";
|
||||||
|
import {
|
||||||
|
ErrorCodes,
|
||||||
|
type ErrorShape,
|
||||||
|
errorShape,
|
||||||
|
type SessionsResolveParams,
|
||||||
|
} from "./protocol/index.js";
|
||||||
|
import {
|
||||||
|
listSessionsFromStore,
|
||||||
|
loadCombinedSessionStoreForGateway,
|
||||||
|
resolveGatewaySessionStoreTarget,
|
||||||
|
} from "./session-utils.js";
|
||||||
|
|
||||||
|
export type SessionsResolveResult =
|
||||||
|
| { ok: true; key: string }
|
||||||
|
| { ok: false; error: ErrorShape };
|
||||||
|
|
||||||
|
export function resolveSessionKeyFromResolveParams(params: {
|
||||||
|
cfg: ClawdbotConfig;
|
||||||
|
p: SessionsResolveParams;
|
||||||
|
}): SessionsResolveResult {
|
||||||
|
const { cfg, p } = params;
|
||||||
|
|
||||||
|
const key = typeof p.key === "string" ? p.key.trim() : "";
|
||||||
|
const hasKey = key.length > 0;
|
||||||
|
const hasLabel = typeof p.label === "string" && p.label.trim().length > 0;
|
||||||
|
if (hasKey && hasLabel) {
|
||||||
|
return {
|
||||||
|
ok: false,
|
||||||
|
error: errorShape(
|
||||||
|
ErrorCodes.INVALID_REQUEST,
|
||||||
|
"Provide either key or label (not both)",
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (!hasKey && !hasLabel) {
|
||||||
|
return {
|
||||||
|
ok: false,
|
||||||
|
error: errorShape(
|
||||||
|
ErrorCodes.INVALID_REQUEST,
|
||||||
|
"Either key or label is required",
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasKey) {
|
||||||
|
const target = resolveGatewaySessionStoreTarget({ cfg, key });
|
||||||
|
const store = loadSessionStore(target.storePath);
|
||||||
|
const existingKey = target.storeKeys.find((candidate) => store[candidate]);
|
||||||
|
if (!existingKey) {
|
||||||
|
return {
|
||||||
|
ok: false,
|
||||||
|
error: errorShape(
|
||||||
|
ErrorCodes.INVALID_REQUEST,
|
||||||
|
`No session found: ${key}`,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return { ok: true, key: target.canonicalKey };
|
||||||
|
}
|
||||||
|
|
||||||
|
const parsedLabel = parseSessionLabel(p.label);
|
||||||
|
if (!parsedLabel.ok) {
|
||||||
|
return {
|
||||||
|
ok: false,
|
||||||
|
error: errorShape(ErrorCodes.INVALID_REQUEST, parsedLabel.error),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const { storePath, store } = loadCombinedSessionStoreForGateway(cfg);
|
||||||
|
const list = listSessionsFromStore({
|
||||||
|
cfg,
|
||||||
|
storePath,
|
||||||
|
store,
|
||||||
|
opts: {
|
||||||
|
includeGlobal: p.includeGlobal === true,
|
||||||
|
includeUnknown: p.includeUnknown === true,
|
||||||
|
label: parsedLabel.label,
|
||||||
|
agentId: p.agentId,
|
||||||
|
spawnedBy: p.spawnedBy,
|
||||||
|
limit: 2,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (list.sessions.length === 0) {
|
||||||
|
return {
|
||||||
|
ok: false,
|
||||||
|
error: errorShape(
|
||||||
|
ErrorCodes.INVALID_REQUEST,
|
||||||
|
`No session found with label: ${parsedLabel.label}`,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (list.sessions.length > 1) {
|
||||||
|
const keys = list.sessions.map((s) => s.key).join(", ");
|
||||||
|
return {
|
||||||
|
ok: false,
|
||||||
|
error: errorShape(
|
||||||
|
ErrorCodes.INVALID_REQUEST,
|
||||||
|
`Multiple sessions found with label: ${parsedLabel.label} (${keys})`,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return { ok: true, key: String(list.sessions[0]?.key ?? "") };
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user