Skip to content

Commit ab34389

Browse files
Persist Assistant bridge sessions across routes
1 parent b7d8d4b commit ab34389

2 files changed

Lines changed: 52 additions & 12 deletions

File tree

js/agent_bridge_client.js

Lines changed: 46 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -984,12 +984,40 @@ C8O.assistantAgentBridge = C8O.assistantAgentBridge || {};
984984
return params && trim(params.__sequence) === "agent_events" ? "events" : "commands";
985985
}
986986

987-
function bridgeSessionCookie(slot) {
987+
function bridgeSessionCacheFile(params, slot) {
988+
try {
989+
params = params || {};
990+
var workspaceRoot = trim(params.workspaceRoot);
991+
if (!workspaceRoot.length) {
992+
workspaceRoot = normalizeWorkspaceRootPath(Engine.USER_WORKSPACE_PATH);
993+
}
994+
var identity = trim(params.handle || params.conversationId);
995+
if (!identity.length) {
996+
identity = trim(params.userId) + "|" + trim(params.projectId) + "|settings";
997+
}
998+
var dir = new File(new File(new File(workspaceRoot), "agents"), "assistant-bridge-sessions");
999+
return new File(dir, hashShort(identity) + "-" + slot + ".cookie");
1000+
} catch (_ignoreBridgeCachePath) {
1001+
return null;
1002+
}
1003+
}
1004+
1005+
function bridgeSessionCookie(params, slot) {
9881006
try {
9891007
var session = context && context.httpSession;
9901008
var value = session && session.getAttribute ? session.getAttribute(BRIDGE_SESSION_COOKIE_ATTR + slot) : null;
991-
return value === null || typeof value === "undefined" ? "" : trim(value);
1009+
var cookie = value === null || typeof value === "undefined" ? "" : trim(value);
1010+
if (cookie.length) {
1011+
return cookie;
1012+
}
9921013
} catch (_ignoreBridgeSession) {
1014+
// Nested routed sequences can use a detached session wrapper; use the workspace fallback below.
1015+
}
1016+
try {
1017+
var file = bridgeSessionCacheFile(params, slot);
1018+
var stored = file === null || !file.exists() ? "" : responseSessionCookie(readTextFile(file));
1019+
return stored;
1020+
} catch (_ignoreBridgeSessionCache) {
9931021
return "";
9941022
}
9951023
}
@@ -1000,7 +1028,7 @@ C8O.assistantAgentBridge = C8O.assistantAgentBridge || {};
10001028
return match ? match[1] : "";
10011029
}
10021030

1003-
function rememberBridgeSessionCookie(slot, header) {
1031+
function rememberBridgeSessionCookie(params, slot, header) {
10041032
var cookie = responseSessionCookie(header);
10051033
if (!cookie.length) {
10061034
return "";
@@ -1011,7 +1039,19 @@ C8O.assistantAgentBridge = C8O.assistantAgentBridge || {};
10111039
session.setAttribute(BRIDGE_SESSION_COOKIE_ATTR + slot, cookie);
10121040
}
10131041
} catch (_ignoreBridgeSession) {
1014-
// A request without an outer session keeps the historical stateless behavior.
1042+
// The workspace cache below also covers routed sequences with detached session wrappers.
1043+
}
1044+
try {
1045+
var file = bridgeSessionCacheFile(params, slot);
1046+
if (file !== null) {
1047+
writeTextFile(file, cookie + "\n");
1048+
file.setReadable(false, false);
1049+
file.setWritable(false, false);
1050+
file.setReadable(true, true);
1051+
file.setWritable(true, true);
1052+
}
1053+
} catch (_ignoreBridgeSessionCache) {
1054+
// Falling back to the outer session preserves authoring if the workspace is read-only.
10151055
}
10161056
return cookie;
10171057
}
@@ -1024,7 +1064,7 @@ C8O.assistantAgentBridge = C8O.assistantAgentBridge || {};
10241064
conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
10251065
conn.setRequestProperty("Accept", "application/json");
10261066
var sessionSlot = bridgeSessionSlot(params);
1027-
var sessionCookie = bridgeSessionCookie(sessionSlot);
1067+
var sessionCookie = bridgeSessionCookie(params, sessionSlot);
10281068
if (sessionCookie.length) {
10291069
conn.setRequestProperty("Cookie", sessionCookie);
10301070
}
@@ -1037,7 +1077,7 @@ C8O.assistantAgentBridge = C8O.assistantAgentBridge || {};
10371077
out.close();
10381078

10391079
var code = conn.getResponseCode();
1040-
rememberBridgeSessionCookie(sessionSlot, conn.getHeaderField("Set-Cookie"));
1080+
rememberBridgeSessionCookie(params, sessionSlot, conn.getHeaderField("Set-Cookie"));
10411081
var text = readStream(code >= 400 ? conn.getErrorStream() : conn.getInputStream());
10421082
if (code >= 400) {
10431083
throw new Error("HTTP " + code + " from agent bridge: " + text);

tests/assistant_routing.test.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -60,13 +60,13 @@ global.context = {
6060
};
6161
assert.equal(testApi.bridgeSessionSlot({ __sequence: "agent_events" }), "events");
6262
assert.equal(testApi.bridgeSessionSlot({ __sequence: "agent_settings" }), "commands");
63-
assert.equal(testApi.bridgeSessionCookie("commands"), "");
63+
assert.equal(testApi.bridgeSessionCookie({}, "commands"), "");
6464
assert.equal(testApi.responseSessionCookie("JSESSIONID=bridge-1; Path=/convertigo; HttpOnly"), "JSESSIONID=bridge-1");
65-
assert.equal(testApi.rememberBridgeSessionCookie("commands", "JSESSIONID=bridge-1; Path=/convertigo; HttpOnly"), "JSESSIONID=bridge-1");
66-
assert.equal(testApi.bridgeSessionCookie("commands"), "JSESSIONID=bridge-1");
67-
assert.equal(testApi.bridgeSessionCookie("events"), "");
68-
assert.equal(testApi.rememberBridgeSessionCookie("events", "ignored=value; Path=/"), "");
65+
assert.equal(testApi.rememberBridgeSessionCookie({}, "commands", "JSESSIONID=bridge-1; Path=/convertigo; HttpOnly"), "JSESSIONID=bridge-1");
66+
assert.equal(testApi.bridgeSessionCookie({}, "commands"), "JSESSIONID=bridge-1");
67+
assert.equal(testApi.bridgeSessionCookie({}, "events"), "");
68+
assert.equal(testApi.rememberBridgeSessionCookie({}, "events", "ignored=value; Path=/"), "");
6969
global.context = {};
70-
assert.equal(testApi.bridgeSessionCookie("commands"), "");
70+
assert.equal(testApi.bridgeSessionCookie({}, "commands"), "");
7171

7272
console.log("Assistant routing contract OK");

0 commit comments

Comments
 (0)