mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-28 13:26:04 -06:00
fix(sessions): show collaboration details in CLI (#128035)
* fix(sessions): expose collaboration details in CLI * fix(sessions): normalize shared visibility --------- Co-authored-by: Amp <amp@ampcode.com>
This commit is contained in:
committed by
GitHub
parent
b7cafbe7e8
commit
cc56ea534f
@@ -30,6 +30,11 @@ export type SessionDisplayRow = {
|
||||
lastInteractionAt?: number;
|
||||
label?: string;
|
||||
status?: SessionEntry["status"];
|
||||
visibility?: SessionEntry["visibility"];
|
||||
createdActor?: SessionEntry["createdActor"];
|
||||
owner?: SessionEntry["owner"];
|
||||
participants?: SessionEntry["participants"];
|
||||
participantCount?: SessionEntry["participantCount"];
|
||||
systemSent?: boolean;
|
||||
abortedLastRun?: boolean;
|
||||
thinkingLevel?: string;
|
||||
@@ -76,6 +81,11 @@ export function toSessionDisplayRow(key: string, entry: SessionEntry): SessionDi
|
||||
lastInteractionAt: entry?.lastInteractionAt,
|
||||
label: entry?.label,
|
||||
status: entry?.status,
|
||||
visibility: entry?.visibility ?? "shared",
|
||||
createdActor: entry?.createdActor,
|
||||
owner: entry?.owner,
|
||||
participants: entry?.participants,
|
||||
participantCount: entry?.participantCount,
|
||||
systemSent: entry?.systemSent,
|
||||
abortedLastRun: entry?.abortedLastRun,
|
||||
thinkingLevel: entry?.thinkingLevel,
|
||||
@@ -134,6 +144,10 @@ export function formatSessionModelCell(model: string | null | undefined, rich: b
|
||||
return rich ? theme.info(label) : label;
|
||||
}
|
||||
|
||||
function formatSessionActor(actor: NonNullable<SessionEntry["createdActor"]>): string {
|
||||
return actor.label?.trim() || actor.id?.trim() || actor.type;
|
||||
}
|
||||
|
||||
/** Formats compact per-session flags for table output. */
|
||||
export function formatSessionFlagsCell(
|
||||
row: Pick<
|
||||
@@ -149,9 +163,25 @@ export function formatSessionFlagsCell(
|
||||
| "abortedLastRun"
|
||||
| "sessionId"
|
||||
| "runtimePolicySessionKey"
|
||||
| "visibility"
|
||||
| "createdActor"
|
||||
| "owner"
|
||||
| "participants"
|
||||
| "participantCount"
|
||||
>,
|
||||
rich: boolean,
|
||||
): string {
|
||||
const owner = row.owner?.actor ?? row.createdActor;
|
||||
// Match the canonical session-row participant preview bound.
|
||||
const participants = (row.participants ?? []).slice(0, 4).map(formatSessionActor);
|
||||
const remainingParticipants = Math.max(
|
||||
0,
|
||||
(row.participantCount ?? participants.length) - participants.length,
|
||||
);
|
||||
const participantSummary =
|
||||
participants.length > 0
|
||||
? `${participants.join(",")}${remainingParticipants > 0 ? `,+${remainingParticipants}` : ""}`
|
||||
: undefined;
|
||||
const flags = [
|
||||
row.thinkingLevel ? `think:${row.thinkingLevel}` : null,
|
||||
row.verboseLevel ? `verbose:${row.verboseLevel}` : null,
|
||||
@@ -162,6 +192,9 @@ export function formatSessionFlagsCell(
|
||||
row.groupActivation ? `activation:${row.groupActivation}` : null,
|
||||
row.systemSent ? "system" : null,
|
||||
row.abortedLastRun ? "aborted" : null,
|
||||
row.visibility ? `visibility:${row.visibility}` : null,
|
||||
owner ? `owner:${formatSessionActor(owner)}` : null,
|
||||
participantSummary ? `participants:${participantSummary}` : null,
|
||||
row.runtimePolicySessionKey ? `policy:${row.runtimePolicySessionKey}` : null,
|
||||
row.sessionId ? `id:${row.sessionId}` : null,
|
||||
].filter(Boolean);
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
// Sessions command tests cover listing, details, filtering, and transcript display behavior.
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
assignSessionOwner,
|
||||
recordSessionParticipant,
|
||||
} from "../config/sessions/session-accessor.js";
|
||||
import type { SessionEntry } from "../config/sessions/types.js";
|
||||
import { normalizeSessionDeliveryState } from "../utils/delivery-context.shared.js";
|
||||
import {
|
||||
cleanupStore,
|
||||
@@ -52,7 +57,7 @@ describe("sessionsCommand", () => {
|
||||
|
||||
const row = logs.find((line) => line.includes("agent:main:+15555550123")) ?? "";
|
||||
expect(row).toBe(
|
||||
"direct agent:main:+15555550123 45m ago test:opus OpenAI Codex 2.0k/200k (1%) id:abc123",
|
||||
"direct agent:main:+15555550123 45m ago test:opus OpenAI Codex 2.0k/200k (1%) visibility:shared id:abc123",
|
||||
);
|
||||
});
|
||||
|
||||
@@ -110,7 +115,7 @@ describe("sessionsCommand", () => {
|
||||
|
||||
const row = logs.find((line) => line.includes("agent:main:main")) ?? "";
|
||||
expect(row).toBe(
|
||||
"direct agent:main:main 1m ago claude-opus-4-7 Claude CLI unknown/200k (?%) id:main-session",
|
||||
"direct agent:main:main 1m ago claude-opus-4-7 Claude CLI unknown/200k (?%) visibility:shared id:main-session",
|
||||
);
|
||||
});
|
||||
|
||||
@@ -144,7 +149,7 @@ describe("sessionsCommand", () => {
|
||||
|
||||
const row = logs.find((line) => line.includes("agent:main:main")) ?? "";
|
||||
expect(row).toBe(
|
||||
"direct agent:main:main 1m ago claude-opus-4-7 Claude CLI unknown/200k (?%) id:main-session",
|
||||
"direct agent:main:main 1m ago claude-opus-4-7 Claude CLI unknown/200k (?%) visibility:shared id:main-session",
|
||||
);
|
||||
});
|
||||
|
||||
@@ -279,6 +284,99 @@ describe("sessionsCommand", () => {
|
||||
expect(group?.totalTokensFresh).toBe(false);
|
||||
});
|
||||
|
||||
it("defaults missing collaboration visibility to shared in JSON output", async () => {
|
||||
const sessionKey = "agent:main:legacy-shared";
|
||||
const store = await writeStore(
|
||||
{
|
||||
[sessionKey]: {
|
||||
sessionId: "legacy-shared-session",
|
||||
updatedAt: Date.now() - 60_000,
|
||||
model: "test:opus",
|
||||
},
|
||||
},
|
||||
"sessions-default-visibility",
|
||||
);
|
||||
|
||||
const payload = await runSessionsJson<{
|
||||
sessions?: Array<{ key: string; visibility?: SessionEntry["visibility"] }>;
|
||||
}>(sessionsCommand, store);
|
||||
expect(payload.sessions?.find((entry) => entry.key === sessionKey)).toMatchObject({
|
||||
visibility: "shared",
|
||||
});
|
||||
});
|
||||
|
||||
it("preserves collaboration metadata in JSON and human output", async () => {
|
||||
const sessionKey = "agent:main:shared";
|
||||
const store = await writeStore(
|
||||
{
|
||||
[sessionKey]: {
|
||||
sessionId: "shared-session",
|
||||
updatedAt: Date.now() - 60_000,
|
||||
model: "test:opus",
|
||||
visibility: "suggest",
|
||||
createdActor: { type: "human", id: "profile-creator", label: "Creator" },
|
||||
},
|
||||
},
|
||||
"sessions-collaboration",
|
||||
);
|
||||
const scope = { agentId: "main", sessionKey, storePath: store };
|
||||
assignSessionOwner(scope, {
|
||||
owner: { type: "human", id: "profile-owner", label: "Grace" },
|
||||
assignedBy: { type: "human", id: "profile-admin", label: "Admin" },
|
||||
assignedAt: Date.now() - 30_000,
|
||||
});
|
||||
for (const [id, label] of [
|
||||
["profile-ada", "Ada"],
|
||||
["profile-ben", "Ben"],
|
||||
["profile-cam", "Cam"],
|
||||
["profile-dee", "Dee"],
|
||||
["profile-eli", "Eli"],
|
||||
] as const) {
|
||||
recordSessionParticipant(scope, {
|
||||
actor: { type: "human", id, label },
|
||||
source: "profile",
|
||||
});
|
||||
}
|
||||
|
||||
const { runtime, logs } = makeRuntime();
|
||||
await sessionsCommand({ store }, runtime);
|
||||
const row = logs.find((line) => line.includes(sessionKey)) ?? "";
|
||||
expect(row).toContain(
|
||||
"visibility:suggest owner:profile-owner participants:profile-ada,profile-ben,profile-cam,profile-dee,+1",
|
||||
);
|
||||
|
||||
const payload = await runSessionsJson<{
|
||||
sessions?: Array<
|
||||
Pick<
|
||||
SessionEntry,
|
||||
"visibility" | "createdActor" | "owner" | "participants" | "participantCount"
|
||||
> & {
|
||||
key: string;
|
||||
sharingRole?: unknown;
|
||||
}
|
||||
>;
|
||||
}>(sessionsCommand, store);
|
||||
const shared = payload.sessions?.find((entry) => entry.key === sessionKey);
|
||||
expect(shared).toMatchObject({
|
||||
visibility: "suggest",
|
||||
createdActor: { type: "human", id: "profile-creator" },
|
||||
owner: {
|
||||
actor: { type: "human", id: "profile-owner" },
|
||||
assignedBy: { type: "human", id: "profile-admin" },
|
||||
assignedAt: Date.now() - 30_000,
|
||||
},
|
||||
participantCount: 5,
|
||||
participants: [
|
||||
{ type: "human", id: "profile-ada", source: "profile" },
|
||||
{ type: "human", id: "profile-ben", source: "profile" },
|
||||
{ type: "human", id: "profile-cam", source: "profile" },
|
||||
{ type: "human", id: "profile-dee", source: "profile" },
|
||||
{ type: "human", id: "profile-eli", source: "profile" },
|
||||
],
|
||||
});
|
||||
expect(shared).not.toHaveProperty("sharingRole");
|
||||
});
|
||||
|
||||
it("reports the SQLite database and omits the retired sessionFile field", async () => {
|
||||
const store = await writeStore({
|
||||
"agent:main:main": {
|
||||
|
||||
Reference in New Issue
Block a user