Merge 3641e4633944ca5917fe8a3c241b153e1fc15d7e into 8a05c05596ca9ba0735dafd8e359885de4c2c969

This commit is contained in:
Bryan Tegomoh, MD, MPH 2026-03-21 06:05:56 +00:00 committed by GitHub
commit 53ed5aae26
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 100 additions and 4 deletions

View File

@ -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({

View File

@ -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,
}); });
} }
} }

View File

@ -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);
}); });

View File

@ -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,