




















@@ -11,6 +11,7 @@ import {
1111import { refreshVisibleToolsEffectiveForCurrentSession } from "../controllers/agents.ts";
1212import { loadSessions } from "../controllers/sessions.ts";
1313import { pushUniqueTrimmedSelectOption } from "../select-options.ts";
14+import { isCronSessionKey, resolveSessionDisplayName } from "../session-display.ts";
1415import {
1516buildAgentMainSessionKey,
1617isSubagentSessionKey,
@@ -439,127 +440,6 @@ async function switchChatThinkingLevel(state: AppViewState, nextThinkingLevel: s
439440}
440441}
441442442-/* Channel display labels. */
443-const CHANNEL_LABELS: Record<string, string> = {
444-imessage: "iMessage",
445-telegram: "Telegram",
446-discord: "Discord",
447-signal: "Signal",
448-slack: "Slack",
449-whatsapp: "WhatsApp",
450-matrix: "Matrix",
451-email: "Email",
452-sms: "SMS",
453-};
454-455-const KNOWN_CHANNEL_KEYS = Object.keys(CHANNEL_LABELS);
456-457-/** Parsed type / context extracted from a session key. */
458-export type SessionKeyInfo = {
459-/** Prefix for typed sessions (Subagent:/Cron:). Empty for others. */
460-prefix: string;
461-/** Human-readable fallback when no label / displayName is available. */
462-fallbackName: string;
463-};
464-465-function capitalize(s: string): string {
466-return s.charAt(0).toUpperCase() + s.slice(1);
467-}
468-469-/**
470- * Parse a session key to extract type information and a human-readable
471- * fallback display name. Exported for testing.
472- */
473-export function parseSessionKey(key: string): SessionKeyInfo {
474-const normalized = normalizeLowercaseStringOrEmpty(key);
475-476-// Main session.
477-if (key === "main" || key === "agent:main:main") {
478-return { prefix: "", fallbackName: "Main Session" };
479-}
480-481-// Subagent.
482-if (key.includes(":subagent:")) {
483-return { prefix: "Subagent:", fallbackName: "Subagent:" };
484-}
485-486-// Cron job.
487-if (normalized.startsWith("cron:") || key.includes(":cron:")) {
488-return { prefix: "Cron:", fallbackName: "Cron Job:" };
489-}
490-491-// Direct chat: agent:<x>:<channel>:direct:<id>.
492-const directMatch = key.match(/^agent:[^:]+:([^:]+):direct:(.+)$/);
493-if (directMatch) {
494-const channel = directMatch[1];
495-const identifier = directMatch[2];
496-const channelLabel = CHANNEL_LABELS[channel] ?? capitalize(channel);
497-return { prefix: "", fallbackName: `${channelLabel} · ${identifier}` };
498-}
499-500-// Group chat: agent:<x>:<channel>:group:<id>.
501-const groupMatch = key.match(/^agent:[^:]+:([^:]+):group:(.+)$/);
502-if (groupMatch) {
503-const channel = groupMatch[1];
504-const channelLabel = CHANNEL_LABELS[channel] ?? capitalize(channel);
505-return { prefix: "", fallbackName: `${channelLabel} Group` };
506-}
507-508-// Channel-prefixed legacy keys, for example "imessage:g-...".
509-for (const ch of KNOWN_CHANNEL_KEYS) {
510-if (key === ch || key.startsWith(`${ch}:`)) {
511-return { prefix: "", fallbackName: `${CHANNEL_LABELS[ch]} Session` };
512-}
513-}
514-515-// Unknown: return key as-is.
516-return { prefix: "", fallbackName: key };
517-}
518-519-export function resolveSessionDisplayName(
520-key: string,
521-row?: SessionsListResult["sessions"][number],
522-): string {
523-const label = normalizeOptionalString(row?.label) ?? "";
524-const displayName = normalizeOptionalString(row?.displayName) ?? "";
525-const { prefix, fallbackName } = parseSessionKey(key);
526-527-const applyTypedPrefix = (name: string): string => {
528-if (!prefix) {
529-return name;
530-}
531-const prefixPattern = new RegExp(`^${prefix.replace(/[.*+?^${}()|[\\]\\]/g, "\\$&")}\\s*`, "i");
532-return prefixPattern.test(name) ? name : `${prefix} ${name}`;
533-};
534-535-if (label && label !== key) {
536-return applyTypedPrefix(label);
537-}
538-if (displayName && displayName !== key) {
539-return applyTypedPrefix(displayName);
540-}
541-return fallbackName;
542-}
543-544-export function isCronSessionKey(key: string): boolean {
545-const normalized = normalizeLowercaseStringOrEmpty(key);
546-if (!normalized) {
547-return false;
548-}
549-if (normalized.startsWith("cron:")) {
550-return true;
551-}
552-if (!normalized.startsWith("agent:")) {
553-return false;
554-}
555-const parts = normalized.split(":").filter(Boolean);
556-if (parts.length < 3) {
557-return false;
558-}
559-const rest = parts.slice(2).join(":");
560-return rest.startsWith("cron:");
561-}
562-563443type SessionOptionEntry = {
564444key: string;
565445label: string;
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。