Merge 3641e4633944ca5917fe8a3c241b153e1fc15d7e into 8a05c05596ca9ba0735dafd8e359885de4c2c969
This commit is contained in:
commit
53ed5aae26
@ -203,6 +203,59 @@ describe("session_status tool", () => {
|
|||||||
expect(updateSessionStoreMock).not.toHaveBeenCalled();
|
expect(updateSessionStoreMock).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("resolves sessionKey=current to the requester session", async () => {
|
||||||
|
resetSessionStore({
|
||||||
|
main: {
|
||||||
|
sessionId: "s1",
|
||||||
|
updatedAt: 10,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const tool = getSessionStatusTool();
|
||||||
|
|
||||||
|
const result = await tool.execute("call-current", { sessionKey: "current" });
|
||||||
|
const details = result.details as { ok?: boolean; sessionKey?: string };
|
||||||
|
expect(details.ok).toBe(true);
|
||||||
|
expect(details.sessionKey).toBe("main");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("resolves sessionKey=current to the requester agent session", async () => {
|
||||||
|
loadSessionStoreMock.mockClear();
|
||||||
|
updateSessionStoreMock.mockClear();
|
||||||
|
callGatewayMock.mockClear();
|
||||||
|
loadCombinedSessionStoreForGatewayMock.mockClear();
|
||||||
|
const stores = new Map<string, Record<string, unknown>>([
|
||||||
|
[
|
||||||
|
"/tmp/main/sessions.json",
|
||||||
|
{
|
||||||
|
"agent:main:main": { sessionId: "s-main", updatedAt: 10 },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"/tmp/support/sessions.json",
|
||||||
|
{
|
||||||
|
main: { sessionId: "s-support", updatedAt: 20 },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
loadSessionStoreMock.mockImplementation((storePath: string) => {
|
||||||
|
return stores.get(storePath) ?? {};
|
||||||
|
});
|
||||||
|
loadCombinedSessionStoreForGatewayMock.mockReturnValue({
|
||||||
|
storePath: "(multiple)",
|
||||||
|
store: Object.fromEntries([...stores.values()].flatMap((s) => Object.entries(s))),
|
||||||
|
});
|
||||||
|
|
||||||
|
const tool = getSessionStatusTool("agent:support:main");
|
||||||
|
|
||||||
|
// "current" resolves to the support agent's own session via the "main" alias.
|
||||||
|
const result = await tool.execute("call-current-child", { sessionKey: "current" });
|
||||||
|
const details = result.details as { ok?: boolean; sessionKey?: string };
|
||||||
|
expect(details.ok).toBe(true);
|
||||||
|
// The resolved key is "main" within the support agent's store scope.
|
||||||
|
expect(details.sessionKey).toBe("main");
|
||||||
|
});
|
||||||
|
|
||||||
it("resolves sessionId inputs", async () => {
|
it("resolves sessionId inputs", async () => {
|
||||||
const sessionId = "sess-main";
|
const sessionId = "sess-main";
|
||||||
resetSessionStore({
|
resetSessionStore({
|
||||||
|
|||||||
@ -56,6 +56,7 @@ function resolveSessionEntry(params: {
|
|||||||
keyRaw: string;
|
keyRaw: string;
|
||||||
alias: string;
|
alias: string;
|
||||||
mainKey: string;
|
mainKey: string;
|
||||||
|
requesterInternalKey?: string;
|
||||||
}): { key: string; entry: SessionEntry } | null {
|
}): { key: string; entry: SessionEntry } | null {
|
||||||
const keyRaw = params.keyRaw.trim();
|
const keyRaw = params.keyRaw.trim();
|
||||||
if (!keyRaw) {
|
if (!keyRaw) {
|
||||||
@ -65,6 +66,7 @@ function resolveSessionEntry(params: {
|
|||||||
key: keyRaw,
|
key: keyRaw,
|
||||||
alias: params.alias,
|
alias: params.alias,
|
||||||
mainKey: params.mainKey,
|
mainKey: params.mainKey,
|
||||||
|
requesterInternalKey: params.requesterInternalKey,
|
||||||
});
|
});
|
||||||
|
|
||||||
const candidates = new Set<string>([keyRaw, internal]);
|
const candidates = new Set<string>([keyRaw, internal]);
|
||||||
@ -72,7 +74,7 @@ function resolveSessionEntry(params: {
|
|||||||
candidates.add(`agent:${DEFAULT_AGENT_ID}:${keyRaw}`);
|
candidates.add(`agent:${DEFAULT_AGENT_ID}:${keyRaw}`);
|
||||||
candidates.add(`agent:${DEFAULT_AGENT_ID}:${internal}`);
|
candidates.add(`agent:${DEFAULT_AGENT_ID}:${internal}`);
|
||||||
}
|
}
|
||||||
if (keyRaw === "main") {
|
if (keyRaw === "main" || keyRaw === "current") {
|
||||||
candidates.add(
|
candidates.add(
|
||||||
buildAgentMainSessionKey({
|
buildAgentMainSessionKey({
|
||||||
agentId: DEFAULT_AGENT_ID,
|
agentId: DEFAULT_AGENT_ID,
|
||||||
@ -251,6 +253,12 @@ export function createSessionStatusTool(opts?: {
|
|||||||
if (!requestedKeyRaw?.trim()) {
|
if (!requestedKeyRaw?.trim()) {
|
||||||
throw new Error("sessionKey required");
|
throw new Error("sessionKey required");
|
||||||
}
|
}
|
||||||
|
// Normalize "current" to the requester's own session key. The "main"
|
||||||
|
// alias is already scoped to the requester agent's store, so this gives
|
||||||
|
// every caller their own session regardless of agent identity.
|
||||||
|
if (requestedKeyRaw.trim().toLowerCase() === "current") {
|
||||||
|
requestedKeyRaw = "main";
|
||||||
|
}
|
||||||
const ensureAgentAccess = (targetAgentId: string) => {
|
const ensureAgentAccess = (targetAgentId: string) => {
|
||||||
if (targetAgentId === requesterAgentId) {
|
if (targetAgentId === requesterAgentId) {
|
||||||
return;
|
return;
|
||||||
@ -290,6 +298,7 @@ export function createSessionStatusTool(opts?: {
|
|||||||
keyRaw: requestedKeyRaw,
|
keyRaw: requestedKeyRaw,
|
||||||
alias,
|
alias,
|
||||||
mainKey,
|
mainKey,
|
||||||
|
requesterInternalKey: effectiveRequesterKey,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!resolved && shouldResolveSessionIdInput(requestedKeyRaw)) {
|
if (!resolved && shouldResolveSessionIdInput(requestedKeyRaw)) {
|
||||||
@ -310,6 +319,7 @@ export function createSessionStatusTool(opts?: {
|
|||||||
keyRaw: requestedKeyRaw,
|
keyRaw: requestedKeyRaw,
|
||||||
alias,
|
alias,
|
||||||
mainKey,
|
mainKey,
|
||||||
|
requesterInternalKey: effectiveRequesterKey,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -67,6 +67,23 @@ describe("session key display/internal mapping", () => {
|
|||||||
resolveInternalSessionKey({ key: "agent:ops:main", alias: "global", mainKey: "main" }),
|
resolveInternalSessionKey({ key: "agent:ops:main", alias: "global", mainKey: "main" }),
|
||||||
).toBe("agent:ops:main");
|
).toBe("agent:ops:main");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("maps current to requester session key", () => {
|
||||||
|
expect(
|
||||||
|
resolveInternalSessionKey({
|
||||||
|
key: "current",
|
||||||
|
alias: "global",
|
||||||
|
mainKey: "main",
|
||||||
|
requesterInternalKey: "agent:support:main",
|
||||||
|
}),
|
||||||
|
).toBe("agent:support:main");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("maps current to alias when no requester key is provided", () => {
|
||||||
|
expect(resolveInternalSessionKey({ key: "current", alias: "global", mainKey: "main" })).toBe(
|
||||||
|
"global",
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("session reference shape detection", () => {
|
describe("session reference shape detection", () => {
|
||||||
@ -77,6 +94,7 @@ describe("session reference shape detection", () => {
|
|||||||
|
|
||||||
it("detects canonical session key families", () => {
|
it("detects canonical session key families", () => {
|
||||||
expect(looksLikeSessionKey("main")).toBe(true);
|
expect(looksLikeSessionKey("main")).toBe(true);
|
||||||
|
expect(looksLikeSessionKey("current")).toBe(true);
|
||||||
expect(looksLikeSessionKey("agent:main:main")).toBe(true);
|
expect(looksLikeSessionKey("agent:main:main")).toBe(true);
|
||||||
expect(looksLikeSessionKey("cron:daily-report")).toBe(true);
|
expect(looksLikeSessionKey("cron:daily-report")).toBe(true);
|
||||||
expect(looksLikeSessionKey("node:macbook")).toBe(true);
|
expect(looksLikeSessionKey("node:macbook")).toBe(true);
|
||||||
@ -86,6 +104,7 @@ describe("session reference shape detection", () => {
|
|||||||
|
|
||||||
it("treats non-keys as session-id candidates", () => {
|
it("treats non-keys as session-id candidates", () => {
|
||||||
expect(shouldResolveSessionIdInput("agent:main:main")).toBe(false);
|
expect(shouldResolveSessionIdInput("agent:main:main")).toBe(false);
|
||||||
|
expect(shouldResolveSessionIdInput("current")).toBe(false);
|
||||||
expect(shouldResolveSessionIdInput("d4f5a5a1-9f75-42cf-83a6-8d170e6a1538")).toBe(true);
|
expect(shouldResolveSessionIdInput("d4f5a5a1-9f75-42cf-83a6-8d170e6a1538")).toBe(true);
|
||||||
expect(shouldResolveSessionIdInput("random-slug")).toBe(true);
|
expect(shouldResolveSessionIdInput("random-slug")).toBe(true);
|
||||||
});
|
});
|
||||||
|
|||||||
@ -25,7 +25,16 @@ export function resolveDisplaySessionKey(params: { key: string; alias: string; m
|
|||||||
return params.key;
|
return params.key;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function resolveInternalSessionKey(params: { key: string; alias: string; mainKey: string }) {
|
export function resolveInternalSessionKey(params: {
|
||||||
|
key: string;
|
||||||
|
alias: string;
|
||||||
|
mainKey: string;
|
||||||
|
requesterInternalKey?: string;
|
||||||
|
}) {
|
||||||
|
if (params.key === "current") {
|
||||||
|
// "current" resolves to the requester's own session, falling back to alias.
|
||||||
|
return params.requesterInternalKey ?? params.alias;
|
||||||
|
}
|
||||||
if (params.key === "main") {
|
if (params.key === "main") {
|
||||||
return params.alias;
|
return params.alias;
|
||||||
}
|
}
|
||||||
@ -121,7 +130,7 @@ export function looksLikeSessionKey(value: string): boolean {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// These are canonical key shapes that should never be treated as sessionIds.
|
// These are canonical key shapes that should never be treated as sessionIds.
|
||||||
if (raw === "main" || raw === "global" || raw === "unknown") {
|
if (raw === "main" || raw === "global" || raw === "unknown" || raw === "current") {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (isAcpSessionKey(raw)) {
|
if (isAcpSessionKey(raw)) {
|
||||||
@ -264,7 +273,11 @@ export async function resolveSessionReference(params: {
|
|||||||
requesterInternalKey?: string;
|
requesterInternalKey?: string;
|
||||||
restrictToSpawned: boolean;
|
restrictToSpawned: boolean;
|
||||||
}): Promise<SessionReferenceResolution> {
|
}): Promise<SessionReferenceResolution> {
|
||||||
const raw = params.sessionKey.trim();
|
const rawInput = params.sessionKey.trim();
|
||||||
|
// Normalize "current" to the requester's own session key so every session
|
||||||
|
// tool resolves it consistently without per-tool special-casing.
|
||||||
|
const raw =
|
||||||
|
rawInput === "current" && params.requesterInternalKey ? params.requesterInternalKey : rawInput;
|
||||||
if (shouldResolveSessionIdInput(raw)) {
|
if (shouldResolveSessionIdInput(raw)) {
|
||||||
// Prefer key resolution to avoid misclassifying custom keys as sessionIds.
|
// Prefer key resolution to avoid misclassifying custom keys as sessionIds.
|
||||||
const resolvedByKey = await resolveSessionKeyFromKey({
|
const resolvedByKey = await resolveSessionKeyFromKey({
|
||||||
@ -290,6 +303,7 @@ export async function resolveSessionReference(params: {
|
|||||||
key: raw,
|
key: raw,
|
||||||
alias: params.alias,
|
alias: params.alias,
|
||||||
mainKey: params.mainKey,
|
mainKey: params.mainKey,
|
||||||
|
requesterInternalKey: params.requesterInternalKey,
|
||||||
});
|
});
|
||||||
const displayKey = resolveDisplaySessionKey({
|
const displayKey = resolveDisplaySessionKey({
|
||||||
key: resolvedKey,
|
key: resolvedKey,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user