



























@@ -38,6 +38,7 @@ import {
3838ensureDir,
3939existsDir,
4040fileExists,
41+parseSessionStoreJson5,
4142readSessionStoreJson5,
4243type SessionEntryLike,
4344safeReadDir,
@@ -466,6 +467,213 @@ function canonicalizeSessionStore(params: {
466467return { store: canonical, legacyKeys };
467468}
468469470+function skipJson5Trivia(raw: string, index: number): number {
471+let i = index;
472+while (i < raw.length) {
473+const ch = raw[i];
474+if (ch === " " || ch === "\n" || ch === "\r" || ch === "\t") {
475+i++;
476+continue;
477+}
478+if (ch === "/" && raw[i + 1] === "/") {
479+i += 2;
480+while (i < raw.length && raw[i] !== "\n") {
481+i++;
482+}
483+continue;
484+}
485+if (ch === "/" && raw[i + 1] === "*") {
486+i += 2;
487+while (i < raw.length && !(raw[i] === "*" && raw[i + 1] === "/")) {
488+i++;
489+}
490+return i < raw.length ? i + 2 : i;
491+}
492+break;
493+}
494+return i;
495+}
496+497+function readJson5String(raw: string, index: number): { value: string; next: number } | null {
498+const quote = raw[index];
499+if (quote !== '"' && quote !== "'") {
500+return null;
501+}
502+let i = index + 1;
503+let value = "";
504+while (i < raw.length) {
505+const ch = raw[i];
506+if (ch === quote) {
507+return { value, next: i + 1 };
508+}
509+if (ch === "\\") {
510+return null;
511+}
512+value += ch;
513+i++;
514+}
515+return null;
516+}
517+518+function readJson5BareKey(raw: string, index: number): { value: string; next: number } | null {
519+let i = index;
520+while (i < raw.length) {
521+const ch = raw[i];
522+if (
523+ch === ":" ||
524+ch === " " ||
525+ch === "\n" ||
526+ch === "\r" ||
527+ch === "\t" ||
528+ch === "," ||
529+ch === "}" ||
530+ch === "{" ||
531+ch === "[" ||
532+ch === "]"
533+) {
534+break;
535+}
536+i++;
537+}
538+if (i === index) {
539+return null;
540+}
541+return { value: raw.slice(index, i), next: i };
542+}
543+544+function listTopLevelSessionStoreKeys(raw: string): string[] | null {
545+let i = skipJson5Trivia(raw, 0);
546+if (raw[i] !== "{") {
547+return null;
548+}
549+i++;
550+const keys: string[] = [];
551+let depth = 1;
552+let expectingKey = true;
553+554+while (i < raw.length) {
555+i = skipJson5Trivia(raw, i);
556+const ch = raw[i];
557+if (ch === undefined) {
558+return null;
559+}
560+if (depth === 1 && ch === "}") {
561+return keys;
562+}
563+if (depth === 1 && expectingKey) {
564+const key = ch === '"' || ch === "'" ? readJson5String(raw, i) : readJson5BareKey(raw, i);
565+if (!key) {
566+return null;
567+}
568+i = skipJson5Trivia(raw, key.next);
569+if (raw[i] !== ":") {
570+return null;
571+}
572+keys.push(key.value);
573+i++;
574+expectingKey = false;
575+continue;
576+}
577+if (ch === '"' || ch === "'") {
578+const str = readJson5String(raw, i);
579+if (!str) {
580+return null;
581+}
582+i = str.next;
583+continue;
584+}
585+if (ch === "{" || ch === "[") {
586+depth++;
587+i++;
588+continue;
589+}
590+if (ch === "}" || ch === "]") {
591+depth--;
592+i++;
593+if (depth < 1) {
594+return keys;
595+}
596+continue;
597+}
598+if (depth === 1 && ch === ",") {
599+expectingKey = true;
600+i++;
601+continue;
602+}
603+i++;
604+}
605+return null;
606+}
607+608+export function sessionStoreTextMayNeedCanonicalization(params: {
609+raw: string;
610+storeAgentIds: Iterable<string>;
611+mainKey: string;
612+scope?: SessionScope;
613+}): boolean {
614+const keys = listTopLevelSessionStoreKeys(params.raw);
615+if (!keys) {
616+return true;
617+}
618+const storeAgentIds = new Set([...params.storeAgentIds].map((id) => normalizeAgentId(id)));
619+const hasNonMainAgent = [...storeAgentIds].some((id) => id !== DEFAULT_AGENT_ID);
620+for (const key of keys) {
621+const rawKey = key.trim();
622+if (rawKey !== key) {
623+return true;
624+}
625+if (!rawKey) {
626+continue;
627+}
628+const lowerKey = normalizeLowercaseStringOrEmpty(rawKey);
629+if (lowerKey !== rawKey) {
630+return true;
631+}
632+if (lowerKey === "global" || lowerKey === "unknown") {
633+continue;
634+}
635+if (lowerKey === DEFAULT_MAIN_KEY || lowerKey === params.mainKey) {
636+return true;
637+}
638+if (lowerKey.startsWith("subagent:")) {
639+return true;
640+}
641+if (lowerKey.startsWith("group:") || lowerKey.startsWith("channel:")) {
642+return true;
643+}
644+if (!lowerKey.startsWith("agent:")) {
645+return true;
646+}
647+for (const storeAgentId of storeAgentIds) {
648+const agentMainAlias = `agent:${storeAgentId}:${DEFAULT_MAIN_KEY}`;
649+const agentMainKey = `agent:${storeAgentId}:${params.mainKey}`;
650+if (
651+lowerKey === agentMainAlias &&
652+(params.mainKey !== DEFAULT_MAIN_KEY || params.scope === "global")
653+) {
654+return true;
655+}
656+if (lowerKey === agentMainKey && params.scope === "global") {
657+return true;
658+}
659+}
660+if (
661+lowerKey === `agent:${DEFAULT_AGENT_ID}:${DEFAULT_MAIN_KEY}` &&
662+(params.mainKey !== DEFAULT_MAIN_KEY || hasNonMainAgent || params.scope === "global")
663+) {
664+return true;
665+}
666+if (
667+lowerKey === `agent:${DEFAULT_AGENT_ID}:${params.mainKey}` &&
668+hasNonMainAgent &&
669+!storeAgentIds.has(DEFAULT_AGENT_ID)
670+) {
671+return true;
672+}
673+}
674+return false;
675+}
676+469677function listLegacySessionKeys(params: {
470678store: Record<string, SessionEntryLike>;
471679agentId: string;
@@ -1163,9 +1371,26 @@ export async function migrateOrphanedSessionKeys(params: {
11631371if (!fileExists(storePath)) {
11641372continue;
11651373}
1374+let raw: string;
1375+try {
1376+raw = fs.readFileSync(storePath, "utf-8");
1377+} catch (err) {
1378+warnings.push(`Could not read ${storePath}: ${String(err)}`);
1379+continue;
1380+}
1381+if (
1382+!sessionStoreTextMayNeedCanonicalization({
1383+ raw,
1384+ storeAgentIds,
1385+ mainKey,
1386+ scope,
1387+})
1388+) {
1389+continue;
1390+}
11661391let parsed: ReturnType<typeof readSessionStoreJson5>;
11671392try {
1168-parsed = readSessionStoreJson5(storePath);
1393+parsed = parseSessionStoreJson5(raw);
11691394} catch (err) {
11701395warnings.push(`Could not read ${storePath}: ${String(err)}`);
11711396continue;
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。