@geminixiang/mikan 0.2.2 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +46 -0
- package/README.md +1 -1
- package/dist/adapters/slack/bot.d.ts +1 -1
- package/dist/adapters/slack/bot.d.ts.map +1 -1
- package/dist/adapters/slack/bot.js +13 -10
- package/dist/adapters/slack/bot.js.map +1 -1
- package/dist/adapters/slack/session.d.ts +5 -5
- package/dist/adapters/slack/session.d.ts.map +1 -1
- package/dist/adapters/slack/session.js +7 -9
- package/dist/adapters/slack/session.js.map +1 -1
- package/dist/adapters/slack/thread-manager.d.ts +20 -0
- package/dist/adapters/slack/thread-manager.d.ts.map +1 -0
- package/dist/adapters/slack/thread-manager.js +14 -0
- package/dist/adapters/slack/thread-manager.js.map +1 -0
- package/dist/agent.d.ts +1 -0
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +16 -4
- package/dist/agent.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/login/portal.d.ts +1 -1
- package/dist/login/portal.d.ts.map +1 -1
- package/dist/login/portal.js.map +1 -1
- package/dist/login/{session.d.ts → store.d.ts} +1 -1
- package/dist/login/store.d.ts.map +1 -0
- package/dist/login/{session.js → store.js} +1 -1
- package/dist/login/store.js.map +1 -0
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +1 -1
- package/dist/main.js.map +1 -1
- package/dist/provisioner.d.ts +2 -0
- package/dist/provisioner.d.ts.map +1 -1
- package/dist/provisioner.js +16 -0
- package/dist/provisioner.js.map +1 -1
- package/dist/runtime/conversation-orchestrator.d.ts +5 -1
- package/dist/runtime/conversation-orchestrator.d.ts.map +1 -1
- package/dist/runtime/conversation-orchestrator.js +9 -10
- package/dist/runtime/conversation-orchestrator.js.map +1 -1
- package/dist/runtime/session-runtime.d.ts +0 -1
- package/dist/runtime/session-runtime.d.ts.map +1 -1
- package/dist/runtime/session-runtime.js +14 -15
- package/dist/runtime/session-runtime.js.map +1 -1
- package/dist/session-view/portal.d.ts.map +1 -1
- package/dist/session-view/portal.js +15 -15
- package/dist/session-view/portal.js.map +1 -1
- package/dist/session-view/service.d.ts +3 -3
- package/dist/session-view/service.d.ts.map +1 -1
- package/dist/session-view/service.js +128 -28
- package/dist/session-view/service.js.map +1 -1
- package/dist/sessions/chat-session-manager.d.ts +62 -0
- package/dist/sessions/chat-session-manager.d.ts.map +1 -0
- package/dist/sessions/chat-session-manager.js +439 -0
- package/dist/sessions/chat-session-manager.js.map +1 -0
- package/dist/sessions/store.d.ts +2 -22
- package/dist/sessions/store.d.ts.map +1 -1
- package/dist/sessions/store.js +31 -158
- package/dist/sessions/store.js.map +1 -1
- package/dist/tools/index.d.ts +10 -2
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +5 -1
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/sandbox.d.ts +22 -0
- package/dist/tools/sandbox.d.ts.map +1 -0
- package/dist/tools/sandbox.js +73 -0
- package/dist/tools/sandbox.js.map +1 -0
- package/package.json +1 -1
- package/dist/adapters/slack/branch-manager.d.ts +0 -28
- package/dist/adapters/slack/branch-manager.d.ts.map +0 -1
- package/dist/adapters/slack/branch-manager.js +0 -117
- package/dist/adapters/slack/branch-manager.js.map +0 -1
- package/dist/conversation-history.d.ts +0 -16
- package/dist/conversation-history.d.ts.map +0 -1
- package/dist/conversation-history.js +0 -144
- package/dist/conversation-history.js.map +0 -1
- package/dist/login/session.d.ts.map +0 -1
- package/dist/login/session.js.map +0 -1
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
import { createManagedSessionFileAtPath, createThreadSessionFileFromRootMessage, forkThreadSessionFile, forkThreadSessionFileFromRootMessage, getChannelSessionDir, getThreadSessionFile, resolveChannelSessionFile, resolveManagedSessionFile, ThreadRootNotFoundError, tryResolveThreadSession, } from "../../sessions/store.js";
|
|
2
|
-
import { findLogMessageById } from "../../context.js";
|
|
3
|
-
import { resolveUsableTopLevelHistorySession } from "../../conversation-history.js";
|
|
4
|
-
import { parseSlackSessionKey } from "./session.js";
|
|
5
|
-
function defaultSleep(ms) {
|
|
6
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
7
|
-
}
|
|
8
|
-
function buildThreadRootSeed(message) {
|
|
9
|
-
if (message.isBot)
|
|
10
|
-
return null;
|
|
11
|
-
return {
|
|
12
|
-
text: message.text,
|
|
13
|
-
userName: message.userName,
|
|
14
|
-
user: message.user,
|
|
15
|
-
loggedAt: message.date ? new Date(message.date).getTime() : undefined,
|
|
16
|
-
};
|
|
17
|
-
}
|
|
18
|
-
async function forkThreadSessionFromRootWithRetry(sourceSessionFile, targetSessionFile, cwd, rootMessage, sleep, retryCount, retryDelayMs) {
|
|
19
|
-
const seed = buildThreadRootSeed(rootMessage);
|
|
20
|
-
if (!seed)
|
|
21
|
-
throw new ThreadRootNotFoundError(sourceSessionFile);
|
|
22
|
-
for (let attempt = 0; attempt < retryCount; attempt++) {
|
|
23
|
-
try {
|
|
24
|
-
return forkThreadSessionFileFromRootMessage(sourceSessionFile, targetSessionFile, cwd, seed);
|
|
25
|
-
}
|
|
26
|
-
catch (error) {
|
|
27
|
-
if (!(error instanceof ThreadRootNotFoundError))
|
|
28
|
-
throw error;
|
|
29
|
-
if (attempt === retryCount - 1)
|
|
30
|
-
break;
|
|
31
|
-
await sleep(retryDelayMs);
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
return createThreadSessionFileFromRootMessage(targetSessionFile, cwd, seed, sourceSessionFile);
|
|
35
|
-
}
|
|
36
|
-
function createThreadSessionFromRootOrEmpty(threadFile, cwd, threadRootMessage, parentSession) {
|
|
37
|
-
if (threadRootMessage) {
|
|
38
|
-
return createThreadSessionFileFromRootMessage(threadFile, cwd, threadRootMessage, parentSession);
|
|
39
|
-
}
|
|
40
|
-
return createManagedSessionFileAtPath(threadFile, cwd);
|
|
41
|
-
}
|
|
42
|
-
export function hasMaterializedSlackBranchSession(conversationDir, sessionKey) {
|
|
43
|
-
if (parseSlackSessionKey(sessionKey).kind !== "fork")
|
|
44
|
-
return false;
|
|
45
|
-
return tryResolveThreadSession(getThreadSessionFile(conversationDir, sessionKey)) !== null;
|
|
46
|
-
}
|
|
47
|
-
export function registerSlackForkSession(options) {
|
|
48
|
-
const { conversationDir, sessionKey } = options;
|
|
49
|
-
if (parseSlackSessionKey(sessionKey).kind !== "fork")
|
|
50
|
-
return null;
|
|
51
|
-
const threadFile = getThreadSessionFile(conversationDir, sessionKey);
|
|
52
|
-
return (tryResolveThreadSession(threadFile) ??
|
|
53
|
-
createManagedSessionFileAtPath(threadFile, options.cwd ?? conversationDir));
|
|
54
|
-
}
|
|
55
|
-
export async function waitForSlackBranchBootstrap(options) {
|
|
56
|
-
const { parentSessionKey, sessionKey, hasThreadSession, isParentRunning, sleep = defaultSleep, pollMs = 100, } = options;
|
|
57
|
-
if (parseSlackSessionKey(sessionKey).kind !== "fork")
|
|
58
|
-
return false;
|
|
59
|
-
if (sessionKey === parentSessionKey)
|
|
60
|
-
return false;
|
|
61
|
-
if (hasThreadSession())
|
|
62
|
-
return false;
|
|
63
|
-
let waited = false;
|
|
64
|
-
while (isParentRunning() && !hasThreadSession()) {
|
|
65
|
-
waited = true;
|
|
66
|
-
await sleep(pollMs);
|
|
67
|
-
}
|
|
68
|
-
return waited;
|
|
69
|
-
}
|
|
70
|
-
export async function resolveSlackSessionScope(options) {
|
|
71
|
-
const { conversationDir, sessionKey, sleep = defaultSleep, retryCount = 5, retryDelayMs = 100, } = options;
|
|
72
|
-
const cwd = options.cwd ?? conversationDir;
|
|
73
|
-
const sessionDir = getChannelSessionDir(conversationDir);
|
|
74
|
-
const sessionRef = parseSlackSessionKey(sessionKey);
|
|
75
|
-
if (sessionRef.kind === "channel") {
|
|
76
|
-
return {
|
|
77
|
-
sessionDir,
|
|
78
|
-
contextFile: resolveManagedSessionFile(sessionDir, cwd),
|
|
79
|
-
threadRootMessage: null,
|
|
80
|
-
};
|
|
81
|
-
}
|
|
82
|
-
const rootTs = sessionRef.anchorTs;
|
|
83
|
-
const threadRootLogMessage = await findLogMessageById(conversationDir, rootTs);
|
|
84
|
-
const threadRootMessage = threadRootLogMessage ? buildThreadRootSeed(threadRootLogMessage) : null;
|
|
85
|
-
const threadFile = getThreadSessionFile(conversationDir, sessionKey);
|
|
86
|
-
const existing = tryResolveThreadSession(threadFile);
|
|
87
|
-
if (existing) {
|
|
88
|
-
return { sessionDir, contextFile: existing, threadRootMessage };
|
|
89
|
-
}
|
|
90
|
-
const conversationSource = resolveUsableTopLevelHistorySession({
|
|
91
|
-
conversationDir,
|
|
92
|
-
sessionDir,
|
|
93
|
-
cwd,
|
|
94
|
-
existingSessionFile: resolveChannelSessionFile(conversationDir),
|
|
95
|
-
});
|
|
96
|
-
if (!conversationSource) {
|
|
97
|
-
return {
|
|
98
|
-
sessionDir,
|
|
99
|
-
contextFile: createThreadSessionFromRootOrEmpty(threadFile, cwd, threadRootMessage),
|
|
100
|
-
threadRootMessage,
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
try {
|
|
104
|
-
const contextFile = threadRootMessage
|
|
105
|
-
? await forkThreadSessionFromRootWithRetry(conversationSource, threadFile, cwd, threadRootLogMessage, sleep, retryCount, retryDelayMs)
|
|
106
|
-
: forkThreadSessionFile(conversationSource, threadFile, cwd);
|
|
107
|
-
return { sessionDir, contextFile, threadRootMessage };
|
|
108
|
-
}
|
|
109
|
-
catch {
|
|
110
|
-
return {
|
|
111
|
-
sessionDir,
|
|
112
|
-
contextFile: createThreadSessionFromRootOrEmpty(threadFile, cwd, threadRootMessage, conversationSource),
|
|
113
|
-
threadRootMessage,
|
|
114
|
-
};
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
//# sourceMappingURL=branch-manager.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"branch-manager.js","sourceRoot":"","sources":["../../../src/adapters/slack/branch-manager.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n createManagedSessionFileAtPath,\n createThreadSessionFileFromRootMessage,\n forkThreadSessionFile,\n forkThreadSessionFileFromRootMessage,\n getChannelSessionDir,\n getThreadSessionFile,\n resolveChannelSessionFile,\n resolveManagedSessionFile,\n type ResolvedSessionScope,\n ThreadRootNotFoundError,\n tryResolveThreadSession,\n type ThreadRootMessage,\n} from \"../../sessions/store.js\";\nimport { findLogMessageById, type ConversationLogMessage } from \"../../context.js\";\nimport { resolveUsableTopLevelHistorySession } from \"../../conversation-history.js\";\nimport { parseSlackSessionKey } from \"./session.js\";\n\nexport interface SlackBranchBootstrapWaitOptions {\n parentSessionKey: string;\n sessionKey: string;\n hasThreadSession: () => boolean;\n isParentRunning: () => boolean;\n sleep?: (ms: number) => Promise<void>;\n pollMs?: number;\n}\n\nexport type SlackResolvedSessionScope = ResolvedSessionScope;\n\nexport interface ResolveSlackSessionScopeOptions {\n conversationDir: string;\n sessionKey: string;\n cwd?: string;\n sleep?: (ms: number) => Promise<void>;\n retryCount?: number;\n retryDelayMs?: number;\n}\n\nexport interface RegisterSlackForkSessionOptions {\n conversationDir: string;\n sessionKey: string;\n cwd?: string;\n}\n\nfunction defaultSleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nfunction buildThreadRootSeed(message: ConversationLogMessage): ThreadRootMessage | null {\n if (message.isBot) return null;\n return {\n text: message.text,\n userName: message.userName,\n user: message.user,\n loggedAt: message.date ? new Date(message.date).getTime() : undefined,\n };\n}\n\nasync function forkThreadSessionFromRootWithRetry(\n sourceSessionFile: string,\n targetSessionFile: string,\n cwd: string,\n rootMessage: ConversationLogMessage,\n sleep: (ms: number) => Promise<void>,\n retryCount: number,\n retryDelayMs: number,\n): Promise<string> {\n const seed = buildThreadRootSeed(rootMessage);\n if (!seed) throw new ThreadRootNotFoundError(sourceSessionFile);\n\n for (let attempt = 0; attempt < retryCount; attempt++) {\n try {\n return forkThreadSessionFileFromRootMessage(sourceSessionFile, targetSessionFile, cwd, seed);\n } catch (error) {\n if (!(error instanceof ThreadRootNotFoundError)) throw error;\n if (attempt === retryCount - 1) break;\n await sleep(retryDelayMs);\n }\n }\n\n return createThreadSessionFileFromRootMessage(targetSessionFile, cwd, seed, sourceSessionFile);\n}\n\nfunction createThreadSessionFromRootOrEmpty(\n threadFile: string,\n cwd: string,\n threadRootMessage: ThreadRootMessage | null,\n parentSession?: string,\n): string {\n if (threadRootMessage) {\n return createThreadSessionFileFromRootMessage(\n threadFile,\n cwd,\n threadRootMessage,\n parentSession,\n );\n }\n return createManagedSessionFileAtPath(threadFile, cwd);\n}\n\nexport function hasMaterializedSlackBranchSession(\n conversationDir: string,\n sessionKey: string,\n): boolean {\n if (parseSlackSessionKey(sessionKey).kind !== \"fork\") return false;\n return tryResolveThreadSession(getThreadSessionFile(conversationDir, sessionKey)) !== null;\n}\n\nexport function registerSlackForkSession(options: RegisterSlackForkSessionOptions): string | null {\n const { conversationDir, sessionKey } = options;\n if (parseSlackSessionKey(sessionKey).kind !== \"fork\") return null;\n\n const threadFile = getThreadSessionFile(conversationDir, sessionKey);\n return (\n tryResolveThreadSession(threadFile) ??\n createManagedSessionFileAtPath(threadFile, options.cwd ?? conversationDir)\n );\n}\n\nexport async function waitForSlackBranchBootstrap(\n options: SlackBranchBootstrapWaitOptions,\n): Promise<boolean> {\n const {\n parentSessionKey,\n sessionKey,\n hasThreadSession,\n isParentRunning,\n sleep = defaultSleep,\n pollMs = 100,\n } = options;\n\n if (parseSlackSessionKey(sessionKey).kind !== \"fork\") return false;\n if (sessionKey === parentSessionKey) return false;\n if (hasThreadSession()) return false;\n\n let waited = false;\n while (isParentRunning() && !hasThreadSession()) {\n waited = true;\n await sleep(pollMs);\n }\n\n return waited;\n}\n\nexport async function resolveSlackSessionScope(\n options: ResolveSlackSessionScopeOptions,\n): Promise<SlackResolvedSessionScope> {\n const {\n conversationDir,\n sessionKey,\n sleep = defaultSleep,\n retryCount = 5,\n retryDelayMs = 100,\n } = options;\n const cwd = options.cwd ?? conversationDir;\n\n const sessionDir = getChannelSessionDir(conversationDir);\n const sessionRef = parseSlackSessionKey(sessionKey);\n if (sessionRef.kind === \"channel\") {\n return {\n sessionDir,\n contextFile: resolveManagedSessionFile(sessionDir, cwd),\n threadRootMessage: null,\n };\n }\n\n const rootTs = sessionRef.anchorTs;\n const threadRootLogMessage = await findLogMessageById(conversationDir, rootTs);\n const threadRootMessage = threadRootLogMessage ? buildThreadRootSeed(threadRootLogMessage) : null;\n const threadFile = getThreadSessionFile(conversationDir, sessionKey);\n const existing = tryResolveThreadSession(threadFile);\n if (existing) {\n return { sessionDir, contextFile: existing, threadRootMessage };\n }\n\n const conversationSource = resolveUsableTopLevelHistorySession({\n conversationDir,\n sessionDir,\n cwd,\n existingSessionFile: resolveChannelSessionFile(conversationDir),\n });\n if (!conversationSource) {\n return {\n sessionDir,\n contextFile: createThreadSessionFromRootOrEmpty(threadFile, cwd, threadRootMessage),\n threadRootMessage,\n };\n }\n\n try {\n const contextFile = threadRootMessage\n ? await forkThreadSessionFromRootWithRetry(\n conversationSource,\n threadFile,\n cwd,\n threadRootLogMessage!,\n sleep,\n retryCount,\n retryDelayMs,\n )\n : forkThreadSessionFile(conversationSource, threadFile, cwd);\n return { sessionDir, contextFile, threadRootMessage };\n } catch {\n return {\n sessionDir,\n contextFile: createThreadSessionFromRootOrEmpty(\n threadFile,\n cwd,\n threadRootMessage,\n conversationSource,\n ),\n threadRootMessage,\n };\n }\n}\n"]}
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import type { ConversationLogMessage } from "./context.js";
|
|
2
|
-
export interface MaterializeTopLevelHistoryOptions {
|
|
3
|
-
conversationDir: string;
|
|
4
|
-
sessionDir: string;
|
|
5
|
-
cwd: string;
|
|
6
|
-
recentDays?: number;
|
|
7
|
-
maxMessages?: number;
|
|
8
|
-
now?: Date;
|
|
9
|
-
}
|
|
10
|
-
export declare function resolveUsableTopLevelHistorySession(options: MaterializeTopLevelHistoryOptions & {
|
|
11
|
-
existingSessionFile: string | null;
|
|
12
|
-
}): string | null;
|
|
13
|
-
export declare function materializeTopLevelHistorySession(options: MaterializeTopLevelHistoryOptions): string | null;
|
|
14
|
-
export declare function latestTopLevelHistoryTime(options: Omit<MaterializeTopLevelHistoryOptions, "sessionDir" | "cwd">): number | null;
|
|
15
|
-
export declare function readTopLevelHistoryMessages(options: Omit<MaterializeTopLevelHistoryOptions, "sessionDir" | "cwd">): ConversationLogMessage[];
|
|
16
|
-
//# sourceMappingURL=conversation-history.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"conversation-history.d.ts","sourceRoot":"","sources":["../src/conversation-history.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAC;AAG3D,MAAM,WAAW,iCAAiC;IAChD,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,GAAG,CAAC,EAAE,IAAI,CAAC;CACZ;AAKD,wBAAgB,mCAAmC,CACjD,OAAO,EAAE,iCAAiC,GAAG;IAAE,mBAAmB,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,GAClF,MAAM,GAAG,IAAI,CAKf;AAED,wBAAgB,iCAAiC,CAC/C,OAAO,EAAE,iCAAiC,GACzC,MAAM,GAAG,IAAI,CAiCf;AAED,wBAAgB,yBAAyB,CACvC,OAAO,EAAE,IAAI,CAAC,iCAAiC,EAAE,YAAY,GAAG,KAAK,CAAC,GACrE,MAAM,GAAG,IAAI,CAMf;AAED,wBAAgB,2BAA2B,CACzC,OAAO,EAAE,IAAI,CAAC,iCAAiC,EAAE,YAAY,GAAG,KAAK,CAAC,GACrE,sBAAsB,EAAE,CAiC1B","sourcesContent":["import { randomUUID } from \"crypto\";\nimport { mkdirSync, statSync } from \"fs\";\nimport { join } from \"path\";\nimport { isRecord, parseJsonValue, readTextFileIfExists } from \"./file-guards.js\";\nimport { atomicWritePrivateFile } from \"./fs-atomic.js\";\nimport type { ConversationLogMessage } from \"./context.js\";\nimport * as log from \"./log.js\";\n\nexport interface MaterializeTopLevelHistoryOptions {\n conversationDir: string;\n sessionDir: string;\n cwd: string;\n recentDays?: number;\n maxMessages?: number;\n now?: Date;\n}\n\nconst DEFAULT_RECENT_DAYS = 14;\nconst DEFAULT_MAX_MESSAGES = 200;\n\nexport function resolveUsableTopLevelHistorySession(\n options: MaterializeTopLevelHistoryOptions & { existingSessionFile: string | null },\n): string | null {\n if (options.existingSessionFile && isSessionFreshForTopLevelHistory(options)) {\n return options.existingSessionFile;\n }\n return materializeTopLevelHistorySession(options);\n}\n\nexport function materializeTopLevelHistorySession(\n options: MaterializeTopLevelHistoryOptions,\n): string | null {\n const messages = readTopLevelHistoryMessages(options);\n if (messages.length === 0) return null;\n\n mkdirSync(options.sessionDir, { recursive: true });\n const now = options.now ?? new Date();\n const sessionId = randomUUID();\n const filename = `${now.toISOString().replace(/[:.]/g, \"-\")}_${sessionId.slice(0, 8)}_history.jsonl`;\n const sessionFile = join(options.sessionDir, filename);\n const header = {\n type: \"session\",\n version: 3,\n id: sessionId,\n timestamp: now.toISOString(),\n cwd: options.cwd,\n source: {\n kind: \"platform-history\",\n file: \"log.jsonl\",\n recentDays: options.recentDays ?? DEFAULT_RECENT_DAYS,\n },\n };\n const entries = messages.map((message) => ({\n type: \"message\",\n id: randomUUID().slice(0, 8),\n parentId: null,\n timestamp: new Date(message.date ?? now.toISOString()).toISOString(),\n message: buildHistorySessionMessage(message),\n }));\n\n const content = [header, ...entries].map((entry) => JSON.stringify(entry)).join(\"\\n\");\n atomicWritePrivateFile(sessionFile, `${content}\\n`);\n atomicWritePrivateFile(join(options.sessionDir, \"current\"), filename);\n return sessionFile;\n}\n\nexport function latestTopLevelHistoryTime(\n options: Omit<MaterializeTopLevelHistoryOptions, \"sessionDir\" | \"cwd\">,\n): number | null {\n const messages = readTopLevelHistoryMessages({ ...options, maxMessages: 1 });\n const latest = messages.at(-1);\n if (!latest?.date) return null;\n const ms = new Date(latest.date).getTime();\n return Number.isFinite(ms) ? ms : null;\n}\n\nexport function readTopLevelHistoryMessages(\n options: Omit<MaterializeTopLevelHistoryOptions, \"sessionDir\" | \"cwd\">,\n): ConversationLogMessage[] {\n const logFile = join(options.conversationDir, \"log.jsonl\");\n const raw = readTextFileIfExists(logFile);\n if (raw === undefined) return [];\n\n const nowMs = (options.now ?? new Date()).getTime();\n const sinceMs = nowMs - (options.recentDays ?? DEFAULT_RECENT_DAYS) * 24 * 60 * 60 * 1000;\n const messages: ConversationLogMessage[] = [];\n const lines = raw.trim().split(\"\\n\").filter(Boolean);\n\n for (let i = 0; i < lines.length; i++) {\n try {\n const entry = parseJsonValue(\n lines[i],\n (value): value is ConversationLogMessage => isRecord(value),\n (detail) => (detail === \"unexpected JSON shape\" ? \"expected a JSON object\" : detail),\n );\n if (entry.threadTs) continue;\n if (!entry.text?.trim()) continue;\n if (entry.date) {\n const dateMs = new Date(entry.date).getTime();\n if (Number.isFinite(dateMs) && dateMs < sinceMs) continue;\n }\n messages.push(entry);\n } catch (err) {\n log.logWarning(\n `Skipping malformed log entry at ${logFile}:${i + 1}`,\n err instanceof Error ? err.message : String(err),\n );\n }\n }\n\n return messages.slice(-(options.maxMessages ?? DEFAULT_MAX_MESSAGES));\n}\n\nfunction isSessionFreshForTopLevelHistory(\n options: MaterializeTopLevelHistoryOptions & { existingSessionFile: string | null },\n): boolean {\n if (!options.existingSessionFile) return false;\n const latestHistoryMs = latestTopLevelHistoryTime(options);\n if (latestHistoryMs === null) return true;\n\n try {\n return statSync(options.existingSessionFile).mtimeMs >= latestHistoryMs;\n } catch {\n return false;\n }\n}\n\nfunction buildHistorySessionMessage(message: ConversationLogMessage): object {\n const base = {\n role: message.isBot ? \"assistant\" : \"user\",\n content: [{ type: \"text\", text: formatHistoryMessage(message) }],\n ...(message.date ? { timestamp: new Date(message.date).getTime() } : {}),\n };\n if (!message.isBot) return base;\n\n return {\n ...base,\n api: \"platform-history\",\n provider: \"platform-history\",\n model: \"platform-history\",\n usage: zeroUsage(),\n stopReason: \"stop\",\n };\n}\n\nfunction zeroUsage(): object {\n return {\n input: 0,\n output: 0,\n cacheRead: 0,\n cacheWrite: 0,\n totalTokens: 0,\n cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },\n };\n}\n\nfunction formatHistoryMessage(message: ConversationLogMessage): string {\n const text = message.text?.trim() ?? \"\";\n if (message.isBot) return text;\n const userLabel = message.userName || message.user || \"unknown\";\n const timestamp = message.date ? formatLocalTimestamp(new Date(message.date)) : null;\n return timestamp ? `[${timestamp}] [${userLabel}]: ${text}` : `[${userLabel}]: ${text}`;\n}\n\nfunction formatLocalTimestamp(date: Date): string {\n const offset = -date.getTimezoneOffset();\n const sign = offset >= 0 ? \"+\" : \"-\";\n const abs = Math.abs(offset);\n return (\n `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ` +\n `${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}` +\n `${sign}${pad(Math.floor(abs / 60))}:${pad(abs % 60)}`\n );\n}\n\nfunction pad(n: number): string {\n return n.toString().padStart(2, \"0\");\n}\n"]}
|
|
@@ -1,144 +0,0 @@
|
|
|
1
|
-
import { randomUUID } from "crypto";
|
|
2
|
-
import { mkdirSync, statSync } from "fs";
|
|
3
|
-
import { join } from "path";
|
|
4
|
-
import { isRecord, parseJsonValue, readTextFileIfExists } from "./file-guards.js";
|
|
5
|
-
import { atomicWritePrivateFile } from "./fs-atomic.js";
|
|
6
|
-
import * as log from "./log.js";
|
|
7
|
-
const DEFAULT_RECENT_DAYS = 14;
|
|
8
|
-
const DEFAULT_MAX_MESSAGES = 200;
|
|
9
|
-
export function resolveUsableTopLevelHistorySession(options) {
|
|
10
|
-
if (options.existingSessionFile && isSessionFreshForTopLevelHistory(options)) {
|
|
11
|
-
return options.existingSessionFile;
|
|
12
|
-
}
|
|
13
|
-
return materializeTopLevelHistorySession(options);
|
|
14
|
-
}
|
|
15
|
-
export function materializeTopLevelHistorySession(options) {
|
|
16
|
-
const messages = readTopLevelHistoryMessages(options);
|
|
17
|
-
if (messages.length === 0)
|
|
18
|
-
return null;
|
|
19
|
-
mkdirSync(options.sessionDir, { recursive: true });
|
|
20
|
-
const now = options.now ?? new Date();
|
|
21
|
-
const sessionId = randomUUID();
|
|
22
|
-
const filename = `${now.toISOString().replace(/[:.]/g, "-")}_${sessionId.slice(0, 8)}_history.jsonl`;
|
|
23
|
-
const sessionFile = join(options.sessionDir, filename);
|
|
24
|
-
const header = {
|
|
25
|
-
type: "session",
|
|
26
|
-
version: 3,
|
|
27
|
-
id: sessionId,
|
|
28
|
-
timestamp: now.toISOString(),
|
|
29
|
-
cwd: options.cwd,
|
|
30
|
-
source: {
|
|
31
|
-
kind: "platform-history",
|
|
32
|
-
file: "log.jsonl",
|
|
33
|
-
recentDays: options.recentDays ?? DEFAULT_RECENT_DAYS,
|
|
34
|
-
},
|
|
35
|
-
};
|
|
36
|
-
const entries = messages.map((message) => ({
|
|
37
|
-
type: "message",
|
|
38
|
-
id: randomUUID().slice(0, 8),
|
|
39
|
-
parentId: null,
|
|
40
|
-
timestamp: new Date(message.date ?? now.toISOString()).toISOString(),
|
|
41
|
-
message: buildHistorySessionMessage(message),
|
|
42
|
-
}));
|
|
43
|
-
const content = [header, ...entries].map((entry) => JSON.stringify(entry)).join("\n");
|
|
44
|
-
atomicWritePrivateFile(sessionFile, `${content}\n`);
|
|
45
|
-
atomicWritePrivateFile(join(options.sessionDir, "current"), filename);
|
|
46
|
-
return sessionFile;
|
|
47
|
-
}
|
|
48
|
-
export function latestTopLevelHistoryTime(options) {
|
|
49
|
-
const messages = readTopLevelHistoryMessages({ ...options, maxMessages: 1 });
|
|
50
|
-
const latest = messages.at(-1);
|
|
51
|
-
if (!latest?.date)
|
|
52
|
-
return null;
|
|
53
|
-
const ms = new Date(latest.date).getTime();
|
|
54
|
-
return Number.isFinite(ms) ? ms : null;
|
|
55
|
-
}
|
|
56
|
-
export function readTopLevelHistoryMessages(options) {
|
|
57
|
-
const logFile = join(options.conversationDir, "log.jsonl");
|
|
58
|
-
const raw = readTextFileIfExists(logFile);
|
|
59
|
-
if (raw === undefined)
|
|
60
|
-
return [];
|
|
61
|
-
const nowMs = (options.now ?? new Date()).getTime();
|
|
62
|
-
const sinceMs = nowMs - (options.recentDays ?? DEFAULT_RECENT_DAYS) * 24 * 60 * 60 * 1000;
|
|
63
|
-
const messages = [];
|
|
64
|
-
const lines = raw.trim().split("\n").filter(Boolean);
|
|
65
|
-
for (let i = 0; i < lines.length; i++) {
|
|
66
|
-
try {
|
|
67
|
-
const entry = parseJsonValue(lines[i], (value) => isRecord(value), (detail) => (detail === "unexpected JSON shape" ? "expected a JSON object" : detail));
|
|
68
|
-
if (entry.threadTs)
|
|
69
|
-
continue;
|
|
70
|
-
if (!entry.text?.trim())
|
|
71
|
-
continue;
|
|
72
|
-
if (entry.date) {
|
|
73
|
-
const dateMs = new Date(entry.date).getTime();
|
|
74
|
-
if (Number.isFinite(dateMs) && dateMs < sinceMs)
|
|
75
|
-
continue;
|
|
76
|
-
}
|
|
77
|
-
messages.push(entry);
|
|
78
|
-
}
|
|
79
|
-
catch (err) {
|
|
80
|
-
log.logWarning(`Skipping malformed log entry at ${logFile}:${i + 1}`, err instanceof Error ? err.message : String(err));
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
return messages.slice(-(options.maxMessages ?? DEFAULT_MAX_MESSAGES));
|
|
84
|
-
}
|
|
85
|
-
function isSessionFreshForTopLevelHistory(options) {
|
|
86
|
-
if (!options.existingSessionFile)
|
|
87
|
-
return false;
|
|
88
|
-
const latestHistoryMs = latestTopLevelHistoryTime(options);
|
|
89
|
-
if (latestHistoryMs === null)
|
|
90
|
-
return true;
|
|
91
|
-
try {
|
|
92
|
-
return statSync(options.existingSessionFile).mtimeMs >= latestHistoryMs;
|
|
93
|
-
}
|
|
94
|
-
catch {
|
|
95
|
-
return false;
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
function buildHistorySessionMessage(message) {
|
|
99
|
-
const base = {
|
|
100
|
-
role: message.isBot ? "assistant" : "user",
|
|
101
|
-
content: [{ type: "text", text: formatHistoryMessage(message) }],
|
|
102
|
-
...(message.date ? { timestamp: new Date(message.date).getTime() } : {}),
|
|
103
|
-
};
|
|
104
|
-
if (!message.isBot)
|
|
105
|
-
return base;
|
|
106
|
-
return {
|
|
107
|
-
...base,
|
|
108
|
-
api: "platform-history",
|
|
109
|
-
provider: "platform-history",
|
|
110
|
-
model: "platform-history",
|
|
111
|
-
usage: zeroUsage(),
|
|
112
|
-
stopReason: "stop",
|
|
113
|
-
};
|
|
114
|
-
}
|
|
115
|
-
function zeroUsage() {
|
|
116
|
-
return {
|
|
117
|
-
input: 0,
|
|
118
|
-
output: 0,
|
|
119
|
-
cacheRead: 0,
|
|
120
|
-
cacheWrite: 0,
|
|
121
|
-
totalTokens: 0,
|
|
122
|
-
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
|
123
|
-
};
|
|
124
|
-
}
|
|
125
|
-
function formatHistoryMessage(message) {
|
|
126
|
-
const text = message.text?.trim() ?? "";
|
|
127
|
-
if (message.isBot)
|
|
128
|
-
return text;
|
|
129
|
-
const userLabel = message.userName || message.user || "unknown";
|
|
130
|
-
const timestamp = message.date ? formatLocalTimestamp(new Date(message.date)) : null;
|
|
131
|
-
return timestamp ? `[${timestamp}] [${userLabel}]: ${text}` : `[${userLabel}]: ${text}`;
|
|
132
|
-
}
|
|
133
|
-
function formatLocalTimestamp(date) {
|
|
134
|
-
const offset = -date.getTimezoneOffset();
|
|
135
|
-
const sign = offset >= 0 ? "+" : "-";
|
|
136
|
-
const abs = Math.abs(offset);
|
|
137
|
-
return (`${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ` +
|
|
138
|
-
`${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}` +
|
|
139
|
-
`${sign}${pad(Math.floor(abs / 60))}:${pad(abs % 60)}`);
|
|
140
|
-
}
|
|
141
|
-
function pad(n) {
|
|
142
|
-
return n.toString().padStart(2, "0");
|
|
143
|
-
}
|
|
144
|
-
//# sourceMappingURL=conversation-history.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"conversation-history.js","sourceRoot":"","sources":["../src/conversation-history.ts"],"names":[],"mappings":"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","sourcesContent":["import { randomUUID } from \"crypto\";\nimport { mkdirSync, statSync } from \"fs\";\nimport { join } from \"path\";\nimport { isRecord, parseJsonValue, readTextFileIfExists } from \"./file-guards.js\";\nimport { atomicWritePrivateFile } from \"./fs-atomic.js\";\nimport type { ConversationLogMessage } from \"./context.js\";\nimport * as log from \"./log.js\";\n\nexport interface MaterializeTopLevelHistoryOptions {\n conversationDir: string;\n sessionDir: string;\n cwd: string;\n recentDays?: number;\n maxMessages?: number;\n now?: Date;\n}\n\nconst DEFAULT_RECENT_DAYS = 14;\nconst DEFAULT_MAX_MESSAGES = 200;\n\nexport function resolveUsableTopLevelHistorySession(\n options: MaterializeTopLevelHistoryOptions & { existingSessionFile: string | null },\n): string | null {\n if (options.existingSessionFile && isSessionFreshForTopLevelHistory(options)) {\n return options.existingSessionFile;\n }\n return materializeTopLevelHistorySession(options);\n}\n\nexport function materializeTopLevelHistorySession(\n options: MaterializeTopLevelHistoryOptions,\n): string | null {\n const messages = readTopLevelHistoryMessages(options);\n if (messages.length === 0) return null;\n\n mkdirSync(options.sessionDir, { recursive: true });\n const now = options.now ?? new Date();\n const sessionId = randomUUID();\n const filename = `${now.toISOString().replace(/[:.]/g, \"-\")}_${sessionId.slice(0, 8)}_history.jsonl`;\n const sessionFile = join(options.sessionDir, filename);\n const header = {\n type: \"session\",\n version: 3,\n id: sessionId,\n timestamp: now.toISOString(),\n cwd: options.cwd,\n source: {\n kind: \"platform-history\",\n file: \"log.jsonl\",\n recentDays: options.recentDays ?? DEFAULT_RECENT_DAYS,\n },\n };\n const entries = messages.map((message) => ({\n type: \"message\",\n id: randomUUID().slice(0, 8),\n parentId: null,\n timestamp: new Date(message.date ?? now.toISOString()).toISOString(),\n message: buildHistorySessionMessage(message),\n }));\n\n const content = [header, ...entries].map((entry) => JSON.stringify(entry)).join(\"\\n\");\n atomicWritePrivateFile(sessionFile, `${content}\\n`);\n atomicWritePrivateFile(join(options.sessionDir, \"current\"), filename);\n return sessionFile;\n}\n\nexport function latestTopLevelHistoryTime(\n options: Omit<MaterializeTopLevelHistoryOptions, \"sessionDir\" | \"cwd\">,\n): number | null {\n const messages = readTopLevelHistoryMessages({ ...options, maxMessages: 1 });\n const latest = messages.at(-1);\n if (!latest?.date) return null;\n const ms = new Date(latest.date).getTime();\n return Number.isFinite(ms) ? ms : null;\n}\n\nexport function readTopLevelHistoryMessages(\n options: Omit<MaterializeTopLevelHistoryOptions, \"sessionDir\" | \"cwd\">,\n): ConversationLogMessage[] {\n const logFile = join(options.conversationDir, \"log.jsonl\");\n const raw = readTextFileIfExists(logFile);\n if (raw === undefined) return [];\n\n const nowMs = (options.now ?? new Date()).getTime();\n const sinceMs = nowMs - (options.recentDays ?? DEFAULT_RECENT_DAYS) * 24 * 60 * 60 * 1000;\n const messages: ConversationLogMessage[] = [];\n const lines = raw.trim().split(\"\\n\").filter(Boolean);\n\n for (let i = 0; i < lines.length; i++) {\n try {\n const entry = parseJsonValue(\n lines[i],\n (value): value is ConversationLogMessage => isRecord(value),\n (detail) => (detail === \"unexpected JSON shape\" ? \"expected a JSON object\" : detail),\n );\n if (entry.threadTs) continue;\n if (!entry.text?.trim()) continue;\n if (entry.date) {\n const dateMs = new Date(entry.date).getTime();\n if (Number.isFinite(dateMs) && dateMs < sinceMs) continue;\n }\n messages.push(entry);\n } catch (err) {\n log.logWarning(\n `Skipping malformed log entry at ${logFile}:${i + 1}`,\n err instanceof Error ? err.message : String(err),\n );\n }\n }\n\n return messages.slice(-(options.maxMessages ?? DEFAULT_MAX_MESSAGES));\n}\n\nfunction isSessionFreshForTopLevelHistory(\n options: MaterializeTopLevelHistoryOptions & { existingSessionFile: string | null },\n): boolean {\n if (!options.existingSessionFile) return false;\n const latestHistoryMs = latestTopLevelHistoryTime(options);\n if (latestHistoryMs === null) return true;\n\n try {\n return statSync(options.existingSessionFile).mtimeMs >= latestHistoryMs;\n } catch {\n return false;\n }\n}\n\nfunction buildHistorySessionMessage(message: ConversationLogMessage): object {\n const base = {\n role: message.isBot ? \"assistant\" : \"user\",\n content: [{ type: \"text\", text: formatHistoryMessage(message) }],\n ...(message.date ? { timestamp: new Date(message.date).getTime() } : {}),\n };\n if (!message.isBot) return base;\n\n return {\n ...base,\n api: \"platform-history\",\n provider: \"platform-history\",\n model: \"platform-history\",\n usage: zeroUsage(),\n stopReason: \"stop\",\n };\n}\n\nfunction zeroUsage(): object {\n return {\n input: 0,\n output: 0,\n cacheRead: 0,\n cacheWrite: 0,\n totalTokens: 0,\n cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },\n };\n}\n\nfunction formatHistoryMessage(message: ConversationLogMessage): string {\n const text = message.text?.trim() ?? \"\";\n if (message.isBot) return text;\n const userLabel = message.userName || message.user || \"unknown\";\n const timestamp = message.date ? formatLocalTimestamp(new Date(message.date)) : null;\n return timestamp ? `[${timestamp}] [${userLabel}]: ${text}` : `[${userLabel}]: ${text}`;\n}\n\nfunction formatLocalTimestamp(date: Date): string {\n const offset = -date.getTimezoneOffset();\n const sign = offset >= 0 ? \"+\" : \"-\";\n const abs = Math.abs(offset);\n return (\n `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ` +\n `${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}` +\n `${sign}${pad(Math.floor(abs / 60))}:${pad(abs % 60)}`\n );\n}\n\nfunction pad(n: number): string {\n return n.toString().padStart(2, \"0\");\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../src/login/session.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAElD,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,YAAY,CAAC;IACvB,cAAc,EAAE,MAAM,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,oDAAoD;IACpD,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;CACnB;AAID,qBAAa,sBAAsB;IACjC,OAAO,CAAC,MAAM,CAAgC;IAE9C;;;OAGG;IACH,MAAM,CACJ,QAAQ,EAAE,YAAY,EACtB,cAAc,EAAE,MAAM,EACtB,cAAc,EAAE,MAAM,EACtB,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,GACjB,SAAS,CAkBX;IAED,qFAAqF;IACrF,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS,CAI5C;IAED,2EAA2E;IAC3E,OAAO,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS,CAM/C;IAED,sEAAsE;IACtE,KAAK,IAAI,IAAI,CAOZ;CACF","sourcesContent":["import { randomBytes } from \"crypto\";\nimport type { PlatformName } from \"../adapter.js\";\n\nexport interface LinkToken {\n token: string;\n platform: PlatformName;\n platformUserId: string;\n vaultId: string;\n providerId: string;\n /** Conversation to notify when binding completes */\n conversationId: string;\n expiresAt: number;\n}\n\nconst TTL_MS = 15 * 60 * 1000;\n\nexport class InMemoryLinkTokenStore {\n private tokens = new Map<string, LinkToken>();\n\n /**\n * Create a link token for a platform user.\n * Invalidates any existing token for the same user before creating a new one.\n */\n create(\n platform: PlatformName,\n platformUserId: string,\n conversationId: string,\n vaultId: string,\n providerId: string,\n ): LinkToken {\n for (const [key, t] of this.tokens) {\n if (t.platform === platform && t.platformUserId === platformUserId) {\n this.tokens.delete(key);\n }\n }\n\n const token: LinkToken = {\n token: randomBytes(16).toString(\"hex\"),\n platform,\n platformUserId,\n vaultId,\n providerId,\n conversationId,\n expiresAt: Date.now() + TTL_MS,\n };\n this.tokens.set(token.token, token);\n return token;\n }\n\n /** Look up a token without consuming it. Returns undefined if missing or expired. */\n peek(rawToken: string): LinkToken | undefined {\n const entry = this.tokens.get(rawToken);\n if (!entry || Date.now() > entry.expiresAt) return undefined;\n return entry;\n }\n\n /** Consume a token (one-shot). Returns undefined if missing or expired. */\n consume(rawToken: string): LinkToken | undefined {\n const entry = this.tokens.get(rawToken);\n if (!entry) return undefined;\n this.tokens.delete(rawToken);\n if (Date.now() > entry.expiresAt) return undefined;\n return entry;\n }\n\n /** Remove expired tokens. Call periodically to bound memory usage. */\n purge(): void {\n const now = Date.now();\n for (const [key, t] of this.tokens) {\n if (now > t.expiresAt) {\n this.tokens.delete(key);\n }\n }\n }\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"session.js","sourceRoot":"","sources":["../../src/login/session.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAC;AAcrC,MAAM,MAAM,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;AAE9B,MAAM,OAAO,sBAAsB;IAAnC;QACU,WAAM,GAAG,IAAI,GAAG,EAAqB,CAAC;IAyDhD,CAAC;IAvDC;;;OAGG;IACH,MAAM,CACJ,QAAsB,EACtB,cAAsB,EACtB,cAAsB,EACtB,OAAe,EACf,UAAkB;QAElB,KAAK,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YACnC,IAAI,CAAC,CAAC,QAAQ,KAAK,QAAQ,IAAI,CAAC,CAAC,cAAc,KAAK,cAAc,EAAE,CAAC;gBACnE,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YAC1B,CAAC;QACH,CAAC;QAED,MAAM,KAAK,GAAc;YACvB,KAAK,EAAE,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC;YACtC,QAAQ;YACR,cAAc;YACd,OAAO;YACP,UAAU;YACV,cAAc;YACd,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,MAAM;SAC/B,CAAC;QACF,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;QACpC,OAAO,KAAK,CAAC;IACf,CAAC;IAED,qFAAqF;IACrF,IAAI,CAAC,QAAgB;QACnB,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QACxC,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,SAAS;YAAE,OAAO,SAAS,CAAC;QAC7D,OAAO,KAAK,CAAC;IACf,CAAC;IAED,2EAA2E;IAC3E,OAAO,CAAC,QAAgB;QACtB,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QACxC,IAAI,CAAC,KAAK;YAAE,OAAO,SAAS,CAAC;QAC7B,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QAC7B,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,SAAS;YAAE,OAAO,SAAS,CAAC;QACnD,OAAO,KAAK,CAAC;IACf,CAAC;IAED,sEAAsE;IACtE,KAAK;QACH,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,KAAK,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YACnC,IAAI,GAAG,GAAG,CAAC,CAAC,SAAS,EAAE,CAAC;gBACtB,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YAC1B,CAAC;QACH,CAAC;IACH,CAAC;CACF","sourcesContent":["import { randomBytes } from \"crypto\";\nimport type { PlatformName } from \"../adapter.js\";\n\nexport interface LinkToken {\n token: string;\n platform: PlatformName;\n platformUserId: string;\n vaultId: string;\n providerId: string;\n /** Conversation to notify when binding completes */\n conversationId: string;\n expiresAt: number;\n}\n\nconst TTL_MS = 15 * 60 * 1000;\n\nexport class InMemoryLinkTokenStore {\n private tokens = new Map<string, LinkToken>();\n\n /**\n * Create a link token for a platform user.\n * Invalidates any existing token for the same user before creating a new one.\n */\n create(\n platform: PlatformName,\n platformUserId: string,\n conversationId: string,\n vaultId: string,\n providerId: string,\n ): LinkToken {\n for (const [key, t] of this.tokens) {\n if (t.platform === platform && t.platformUserId === platformUserId) {\n this.tokens.delete(key);\n }\n }\n\n const token: LinkToken = {\n token: randomBytes(16).toString(\"hex\"),\n platform,\n platformUserId,\n vaultId,\n providerId,\n conversationId,\n expiresAt: Date.now() + TTL_MS,\n };\n this.tokens.set(token.token, token);\n return token;\n }\n\n /** Look up a token without consuming it. Returns undefined if missing or expired. */\n peek(rawToken: string): LinkToken | undefined {\n const entry = this.tokens.get(rawToken);\n if (!entry || Date.now() > entry.expiresAt) return undefined;\n return entry;\n }\n\n /** Consume a token (one-shot). Returns undefined if missing or expired. */\n consume(rawToken: string): LinkToken | undefined {\n const entry = this.tokens.get(rawToken);\n if (!entry) return undefined;\n this.tokens.delete(rawToken);\n if (Date.now() > entry.expiresAt) return undefined;\n return entry;\n }\n\n /** Remove expired tokens. Call periodically to bound memory usage. */\n purge(): void {\n const now = Date.now();\n for (const [key, t] of this.tokens) {\n if (now > t.expiresAt) {\n this.tokens.delete(key);\n }\n }\n }\n}\n"]}
|