File size: 6,042 Bytes
78d0c37 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 | import { describe, expect, it } from "vitest";
import {
mergeSessionEntry,
resolveSessionResetPolicy,
type InternalSessionEntry as SessionEntry,
} from "../../config/sessions.js";
import { buildAgentSessionPatch, type AgentSessionPatchBuild } from "./agent-session-patch.js";
function buildPatch(touchInteraction: boolean, opts?: { requestLabel?: string; label?: string }) {
const now = 1_000;
const entry: SessionEntry = {
sessionId: "session",
updatedAt: now,
lifecycleRunId: "completed-run",
lastRunId: "completed-run",
status: "failed",
agentStatus: { note: "Need a password", attention: "key", expiresAt: now + 60_000 },
...(opts?.label ? { label: opts.label } : {}),
};
return buildAgentSessionPatch({
freshEntry: entry,
initialEntry: entry,
...(opts?.requestLabel ? { requestLabel: opts.requestLabel } : {}),
cfg: {},
sessionAgentId: "main",
canonicalSessionKey: "agent:main:main",
storePath: "/tmp/openclaw-agent-status-test.json",
normalizedSpawned: {},
requestDeliveryHint: undefined,
expectedExistingSessionId: entry.sessionId,
hasRestoredCronContinuation: false,
resetPolicy: resolveSessionResetPolicy({ resetType: "direct" }),
now,
isSystemGatewayRun: true,
visibleRequest: true,
fallbackSessionId: "fallback",
touchInteraction,
failedSessionTranscriptMissing: () => false,
}).patch;
}
function buildCreationPatch(opts: {
canonicalSessionKey?: string;
explicitSessionKey?: string;
freshEntry?: SessionEntry;
isSystemGatewayRun?: boolean;
visibleRequest?: boolean;
}): AgentSessionPatchBuild {
const now = 1_000;
return buildAgentSessionPatch({
freshEntry: opts.freshEntry,
initialEntry: opts.freshEntry,
cfg: {},
sessionAgentId: "main",
canonicalSessionKey: opts.canonicalSessionKey ?? "agent:main:incident-42",
storePath: "/tmp/openclaw-explicit-session-name-test.json",
normalizedSpawned: {},
requestDeliveryHint: undefined,
...(opts.explicitSessionKey ? { explicitSessionKey: opts.explicitSessionKey } : {}),
...(opts.freshEntry?.sessionId ? { expectedExistingSessionId: opts.freshEntry.sessionId } : {}),
hasRestoredCronContinuation: false,
resetPolicy: resolveSessionResetPolicy({ resetType: "direct" }),
now,
isSystemGatewayRun: opts.isSystemGatewayRun ?? false,
visibleRequest: opts.visibleRequest ?? true,
fallbackSessionId: "fallback",
touchInteraction: false,
failedSessionTranscriptMissing: () => false,
});
}
describe("agent session patch", () => {
it("clears agent status at the next human interaction boundary", () => {
const patch = buildPatch(true);
expect(Object.hasOwn(patch, "agentStatus")).toBe(true);
expect(patch.agentStatus).toBeUndefined();
expect(Object.hasOwn(patch, "lifecycleRunId")).toBe(true);
expect(patch.lifecycleRunId).toBeUndefined();
expect(patch.lastRunId).toBeUndefined();
});
it("does not clear agent status for lifecycle-only patches", () => {
expect(Object.hasOwn(buildPatch(false), "agentStatus")).toBe(false);
});
// Public agent RPC labels retain their run-start contract; native spawn labels are creation-owned.
it("persists the request label at run start", () => {
expect(buildPatch(false, { requestLabel: "Fix flaky auth test" }).label).toBe(
"Fix flaky auth test",
);
});
it("keeps the existing label when the request has none", () => {
expect(buildPatch(false, { label: "Existing" }).label).toBe("Existing");
});
it("names a new session from an explicit agent session key", () => {
const result = buildCreationPatch({
canonicalSessionKey: "agent:main:incident-42 ",
explicitSessionKey: "agent:main:incident-42",
});
expect(result.patch.displayName).toBe("incident-42");
});
it("does not name an existing session", () => {
const entry: SessionEntry = { sessionId: "existing", updatedAt: 1_000 };
const result = buildCreationPatch({
explicitSessionKey: "agent:main:incident-42",
freshEntry: entry,
});
expect(result.patch).not.toHaveProperty("displayName");
});
it.each([
["label", { label: "Existing label" }],
["displayName", { displayName: "Existing display name" }],
["subject", { subject: "Existing subject" }],
] as const)("does not replace an existing %s", (_field, namedEntry) => {
const entry: SessionEntry = { sessionId: "existing", updatedAt: 1_000, ...namedEntry };
const result = buildCreationPatch({
explicitSessionKey: "agent:main:incident-42",
freshEntry: entry,
});
expect(mergeSessionEntry(entry, result.patch)).toMatchObject(namedEntry);
});
it("does not name a defaulted main session", () => {
expect(buildCreationPatch({ canonicalSessionKey: "agent:main:main" }).patch).not.toHaveProperty(
"displayName",
);
});
it("does not name a channel-derived session", () => {
expect(
buildCreationPatch({ canonicalSessionKey: "agent:main:telegram:direct:123" }).patch,
).not.toHaveProperty("displayName");
});
it.each([
["cron", "agent:main:cron:job-1", true, false],
["heartbeat", "agent:main:heartbeat:main", true, false],
["internal", "agent:main:internal:probe", false, false],
] as const)(
"does not name a %s run",
(_kind, canonicalSessionKey, isSystemGatewayRun, visibleRequest) => {
const result = buildCreationPatch({
canonicalSessionKey,
explicitSessionKey: canonicalSessionKey,
isSystemGatewayRun,
visibleRequest,
});
expect(result.patch).not.toHaveProperty("displayName");
},
);
it.each([
["subagent", "agent:main:subagent:worker-1"],
["ACP", "agent:main:acp:thread-1"],
] as const)("does not name a %s session", (_kind, canonicalSessionKey) => {
const result = buildCreationPatch({
canonicalSessionKey,
explicitSessionKey: canonicalSessionKey,
});
expect(result.patch).not.toHaveProperty("displayName");
});
});
|