@phren/cli
Version:
Knowledge layer for AI agents - CLI, MCP server, and data layer
34 lines (33 loc) • 2.65 kB
JavaScript
import { object } from "./protocol.js";
/** Copilot's transcript reader: public message, tool and usage events, and
* the reasoning summary Copilot itself prints under "Thought for Ns". Its
* encrypted and opaque reasoning stays out. */
export function visibleCopilotEvent(raw) {
if (raw.agentId || raw.ephemeral)
return undefined;
// What a skill loaded: Copilot answers the `skill` call with only "Skill
// loaded", then writes the SKILL.md it put in context as `skill.invoked`.
if (raw.type === "skill.invoked") {
const data = object(raw.data);
if (typeof data.name !== "string" || typeof data.content !== "string")
return undefined;
return { type: raw.type, timestamp: raw.timestamp, data: { name: data.name.slice(0, 200), content: data.content.slice(0, 65_536),
...(typeof data.description === "string" ? { description: data.description.slice(0, 1_000) } : {}) } };
}
if (!["user.message", "assistant.message", "assistant.message_delta", "tool.execution_start", "tool.execution_complete", "assistant.turn_start", "assistant.turn_end", "session.idle", "abort", "session.error", "session.usage_info", "assistant.usage"].includes(String(raw.type)))
return undefined;
const data = object(raw.data);
// Copilot writes its reasoning beside the message three ways: the summary
// its terminal shows (`reasoningText`), and encrypted/opaque copies for the
// model (`encryptedContent`, `reasoningOpaque`, `reasoningBlocks`). Only the
// summary the person already sees in the terminal is exported.
// `phase` marks Copilot 1.0.87's final answer (it writes no session.idle);
// `success` is false on a failed tool run.
const allowed = ["content", "source", "messageId", "deltaContent", "toolName", "toolCallId", "arguments", "result", "error", "success", "phase", "aborted", "reasoningText", "mcpServerName", "mcpToolName", "inputTokens", "outputTokens", "cacheReadTokens"];
return { type: raw.type, timestamp: raw.timestamp, data: Object.fromEntries(Object.entries(data)
// The MCP server and tool a call went to (`phren` + `get_tasks` for the
// `phren-get_tasks` tool), so the phone draws the server's own card.
.filter(([key, value]) => allowed.includes(key) && (key !== "reasoningText" || (raw.type === "assistant.message" && typeof value === "string"))
&& (!["mcpServerName", "mcpToolName"].includes(key) || (typeof value === "string" && value.length <= 200)))
.map(([key, value]) => [key, key === "reasoningText" ? String(value).slice(0, 8_000) : value])) };
}