agent-office-cli 0.0.1

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.
@@ -0,0 +1,210 @@
1
+ const fs = require("node:fs");
2
+ const os = require("node:os");
3
+ const path = require("node:path");
4
+
5
+ const CODEX_SESSIONS_ROOT = path.join(os.homedir(), ".codex", "sessions");
6
+
7
+ function readJsonLinesHead(filePath, maxLines = 8) {
8
+ try {
9
+ const fd = fs.openSync(filePath, "r");
10
+ const buffer = Buffer.alloc(1048576);
11
+ const length = fs.readSync(fd, buffer, 0, buffer.length, 0);
12
+ fs.closeSync(fd);
13
+ const text = buffer.toString("utf8", 0, length);
14
+ return text
15
+ .split("\n")
16
+ .filter(Boolean)
17
+ .slice(0, maxLines)
18
+ .map((line) => {
19
+ try {
20
+ return JSON.parse(line);
21
+ } catch {
22
+ return null;
23
+ }
24
+ })
25
+ .filter(Boolean);
26
+ } catch {
27
+ return [];
28
+ }
29
+ }
30
+
31
+ function readTranscriptTail(filePath, bytes = 1048576) {
32
+ try {
33
+ const stats = fs.statSync(filePath);
34
+ const start = Math.max(0, stats.size - bytes);
35
+ const length = stats.size - start;
36
+ const fd = fs.openSync(filePath, "r");
37
+ const buffer = Buffer.alloc(length);
38
+ fs.readSync(fd, buffer, 0, length, start);
39
+ fs.closeSync(fd);
40
+ return buffer.toString("utf8");
41
+ } catch {
42
+ return "";
43
+ }
44
+ }
45
+
46
+ function extractRecentEntries(filePath, count = 1000) {
47
+ const text = readTranscriptTail(filePath);
48
+ if (!text) {
49
+ return [];
50
+ }
51
+
52
+ return text
53
+ .split("\n")
54
+ .filter(Boolean)
55
+ .slice(-count)
56
+ .map((line) => {
57
+ try {
58
+ return JSON.parse(line);
59
+ } catch {
60
+ return null;
61
+ }
62
+ })
63
+ .filter(Boolean);
64
+ }
65
+
66
+ function listSessionFiles(root = CODEX_SESSIONS_ROOT) {
67
+ const files = [];
68
+
69
+ function walk(dirPath) {
70
+ let entries;
71
+ try {
72
+ entries = fs.readdirSync(dirPath, { withFileTypes: true });
73
+ } catch {
74
+ return;
75
+ }
76
+
77
+ for (const entry of entries) {
78
+ const nextPath = path.join(dirPath, entry.name);
79
+ if (entry.isDirectory()) {
80
+ walk(nextPath);
81
+ continue;
82
+ }
83
+ if (!entry.isFile() || !entry.name.endsWith(".jsonl")) {
84
+ continue;
85
+ }
86
+ try {
87
+ const stats = fs.statSync(nextPath);
88
+ files.push({ path: nextPath, mtimeMs: stats.mtimeMs });
89
+ } catch {
90
+ // Ignore disappearing files from concurrent Codex writes.
91
+ }
92
+ }
93
+ }
94
+
95
+ walk(root);
96
+ files.sort((left, right) => right.mtimeMs - left.mtimeMs);
97
+ return files;
98
+ }
99
+
100
+ function readSessionMeta(filePath) {
101
+ const entries = readJsonLinesHead(filePath);
102
+ const sessionMeta = entries.find((entry) => entry.type === "session_meta");
103
+ return sessionMeta ? sessionMeta.payload || null : null;
104
+ }
105
+
106
+ function summarizeCodexSession(filePath) {
107
+ const entries = extractRecentEntries(filePath);
108
+ for (let index = entries.length - 1; index >= 0; index -= 1) {
109
+ const entry = entries[index];
110
+ const payload = entry.payload || {};
111
+
112
+ if (entry.type === "event_msg") {
113
+ if (payload.type === "task_started") {
114
+ return {
115
+ state: "working",
116
+ lastLifecycle: "task_started",
117
+ lastTimestamp: entry.timestamp || null,
118
+ lastTurnId: payload.turn_id || null,
119
+ lastAgentMessage: null
120
+ };
121
+ }
122
+
123
+ if (payload.type === "task_complete") {
124
+ return {
125
+ state: "idle",
126
+ lastLifecycle: "task_complete",
127
+ lastTimestamp: entry.timestamp || null,
128
+ lastTurnId: payload.turn_id || null,
129
+ lastAgentMessage: payload.last_agent_message || null
130
+ };
131
+ }
132
+
133
+ if (payload.type === "turn_aborted") {
134
+ return {
135
+ state: "idle",
136
+ lastLifecycle: "turn_aborted",
137
+ lastTimestamp: entry.timestamp || null,
138
+ lastTurnId: payload.turn_id || null,
139
+ lastAgentMessage: null
140
+ };
141
+ }
142
+ }
143
+ }
144
+
145
+ return {
146
+ state: null,
147
+ lastLifecycle: null,
148
+ lastTimestamp: null,
149
+ lastTurnId: null,
150
+ lastAgentMessage: null
151
+ };
152
+ }
153
+
154
+ function findManagedCodexSessionFile(session, allSessions = []) {
155
+ const linkedPath = session.meta && session.meta.codexSessionPath;
156
+ if (linkedPath && fs.existsSync(linkedPath)) {
157
+ return {
158
+ path: linkedPath,
159
+ sessionMeta: readSessionMeta(linkedPath)
160
+ };
161
+ }
162
+
163
+ const assignedPaths = new Set(
164
+ allSessions
165
+ .filter((entry) => entry.sessionId !== session.sessionId)
166
+ .map((entry) => entry.meta && entry.meta.codexSessionPath)
167
+ .filter(Boolean)
168
+ );
169
+
170
+ const sessionStartMs = Date.parse((session.meta && session.meta.managedStartedAt) || session.createdAt || session.updatedAt || 0);
171
+ const candidates = listSessionFiles()
172
+ .filter((entry) => !assignedPaths.has(entry.path))
173
+ .slice(0, 80)
174
+ .map((entry) => ({
175
+ ...entry,
176
+ sessionMeta: readSessionMeta(entry.path)
177
+ }))
178
+ .filter((entry) => entry.sessionMeta && entry.sessionMeta.cwd === session.cwd)
179
+ .map((entry) => {
180
+ const metaTimestampMs = Date.parse(entry.sessionMeta.timestamp || 0);
181
+ const effectiveTimeMs = Number.isFinite(metaTimestampMs) && metaTimestampMs > 0 ? metaTimestampMs : entry.mtimeMs;
182
+ const deltaMs = Math.abs(effectiveTimeMs - sessionStartMs);
183
+ const score =
184
+ (entry.mtimeMs >= sessionStartMs - 15000 ? 60 : 0) +
185
+ Math.max(0, 40 - Math.floor(deltaMs / 30000));
186
+ return {
187
+ ...entry,
188
+ score,
189
+ deltaMs,
190
+ effectiveTimeMs
191
+ };
192
+ })
193
+ .filter((entry) => entry.score > 0)
194
+ .sort((left, right) => right.score - left.score || right.effectiveTimeMs - left.effectiveTimeMs);
195
+
196
+ if (candidates.length === 0) {
197
+ return null;
198
+ }
199
+
200
+ return {
201
+ path: candidates[0].path,
202
+ sessionMeta: candidates[0].sessionMeta
203
+ };
204
+ }
205
+
206
+ module.exports = {
207
+ CODEX_SESSIONS_ROOT,
208
+ findManagedCodexSessionFile,
209
+ summarizeCodexSession
210
+ };
@@ -0,0 +1,91 @@
1
+ const { GenericProvider } = require("./generic");
2
+ const { findManagedCodexSessionFile, summarizeCodexSession } = require("./codex-transcript");
3
+
4
+ class CodexProvider extends GenericProvider {
5
+ constructor() {
6
+ super("codex");
7
+ }
8
+
9
+ createSession(payload) {
10
+ return {
11
+ ...super.createSession(payload),
12
+ meta: {
13
+ managedStartedAt: new Date().toISOString(),
14
+ codexSessionPath: null,
15
+ codexSessionId: null,
16
+ codexTranscriptCursor: null,
17
+ codexLastLifecycle: null,
18
+ ...(payload.meta || {})
19
+ }
20
+ };
21
+ }
22
+
23
+ classifyOutput(chunk) {
24
+ const text = String(chunk).toLowerCase();
25
+ if (text.includes("approval") || text.includes("press enter") || text.includes("confirm")) {
26
+ return "approval";
27
+ }
28
+ if (text.includes("error") || text.includes("failed") || text.includes("panic")) {
29
+ return "attention";
30
+ }
31
+ return null;
32
+ }
33
+
34
+ reconcileSession(session, context = {}) {
35
+ if (session.status === "exited") {
36
+ return null;
37
+ }
38
+
39
+ const matched = findManagedCodexSessionFile(session, context.sessions || []);
40
+ if (!matched || !matched.path) {
41
+ return null;
42
+ }
43
+
44
+ const summary = summarizeCodexSession(matched.path);
45
+ const nextMeta = {
46
+ codexSessionPath: matched.path,
47
+ codexSessionId: matched.sessionMeta && matched.sessionMeta.id,
48
+ codexTranscriptCursor: summary.lastTimestamp || null,
49
+ codexLastLifecycle: summary.lastLifecycle || null
50
+ };
51
+
52
+ const previousPath = session.meta && session.meta.codexSessionPath;
53
+ const previousState = session.displayState;
54
+ const previousCursor = session.meta && session.meta.codexTranscriptCursor;
55
+ const previousLifecycle = session.meta && session.meta.codexLastLifecycle;
56
+ const lifecycleAdvanced = Boolean(summary.lastTimestamp && summary.lastTimestamp !== previousCursor);
57
+ const metaChanged =
58
+ previousPath !== nextMeta.codexSessionPath ||
59
+ (session.meta && session.meta.codexSessionId) !== nextMeta.codexSessionId ||
60
+ previousCursor !== nextMeta.codexTranscriptCursor ||
61
+ previousLifecycle !== nextMeta.codexLastLifecycle;
62
+ const stateChanged = Boolean(summary.state && summary.state !== previousState);
63
+
64
+ if (!metaChanged && !stateChanged && !lifecycleAdvanced) {
65
+ return null;
66
+ }
67
+
68
+ return {
69
+ session: metaChanged
70
+ ? {
71
+ meta: nextMeta
72
+ }
73
+ : null,
74
+ state: summary.state,
75
+ patch: summary.state ? { status: "running" } : null,
76
+ eventName: lifecycleAdvanced && summary.lastLifecycle ? `codex_${summary.lastLifecycle}` : null,
77
+ meta: lifecycleAdvanced
78
+ ? {
79
+ codexSessionPath: matched.path,
80
+ codexSessionId: matched.sessionMeta && matched.sessionMeta.id,
81
+ turnId: summary.lastTurnId || null,
82
+ lastAgentMessage: summary.lastAgentMessage || null
83
+ }
84
+ : null
85
+ };
86
+ }
87
+ }
88
+
89
+ module.exports = {
90
+ CodexProvider
91
+ };
@@ -0,0 +1,40 @@
1
+ const { BaseProvider } = require("./base");
2
+
3
+ const ATTENTION_PATTERNS = [
4
+ "traceback",
5
+ "fatal",
6
+ "command not found",
7
+ "permission denied",
8
+ "access denied",
9
+ "exception"
10
+ ];
11
+
12
+ const WAITING_PATTERNS = [
13
+ "approve",
14
+ "approval",
15
+ "waiting for input",
16
+ "confirm",
17
+ "press enter",
18
+ "continue?"
19
+ ];
20
+
21
+ class GenericProvider extends BaseProvider {
22
+ constructor(name = "generic") {
23
+ super(name);
24
+ }
25
+
26
+ classifyOutput(chunk) {
27
+ const text = String(chunk).toLowerCase();
28
+ if (WAITING_PATTERNS.some((pattern) => text.includes(pattern))) {
29
+ return "approval";
30
+ }
31
+ if (ATTENTION_PATTERNS.some((pattern) => text.includes(pattern))) {
32
+ return "attention";
33
+ }
34
+ return "working";
35
+ }
36
+ }
37
+
38
+ module.exports = {
39
+ GenericProvider
40
+ };
@@ -0,0 +1,17 @@
1
+ const { ClaudeProvider } = require("./claude");
2
+ const { CodexProvider } = require("./codex");
3
+ const { GenericProvider } = require("./generic");
4
+
5
+ const providers = {
6
+ claude: new ClaudeProvider(),
7
+ codex: new CodexProvider(),
8
+ generic: new GenericProvider("generic")
9
+ };
10
+
11
+ function getProvider(name) {
12
+ return providers[name] || new GenericProvider(name || "generic");
13
+ }
14
+
15
+ module.exports = {
16
+ getProvider
17
+ };
@@ -0,0 +1,98 @@
1
+ const CONTRACT_VERSION = 1;
2
+
3
+ function sessionLifecycle(session) {
4
+ const status = session.status || "registered";
5
+ const displayState = session.displayState || session.state || "idle";
6
+ const displayZone = session.displayZone || "working-zone";
7
+
8
+ return {
9
+ status,
10
+ state: session.state || displayState,
11
+ displayState,
12
+ displayZone,
13
+ visibleInOffice: !["completed", "exited"].includes(status)
14
+ };
15
+ }
16
+
17
+ function toPublicSession(session) {
18
+ if (!session) {
19
+ return null;
20
+ }
21
+
22
+ const lifecycle = sessionLifecycle(session);
23
+ const meta = { ...(session.meta || {}) };
24
+ const runtime = {
25
+ pid: session.pid || null,
26
+ host: session.host || null,
27
+ transport: session.transport,
28
+ hasTerminal: ["pty", "tmux"].includes(session.transport),
29
+ tmuxSession: meta.tmuxSession || null,
30
+ attachCommand: meta.localAttachCommand || null
31
+ };
32
+
33
+ return {
34
+ contractVersion: CONTRACT_VERSION,
35
+ sessionId: session.sessionId,
36
+ provider: session.provider,
37
+ title: session.title,
38
+ command: session.command,
39
+ cwd: session.cwd,
40
+ mode: session.mode,
41
+ transport: session.transport,
42
+ state: lifecycle.state,
43
+ displayState: lifecycle.displayState,
44
+ displayZone: lifecycle.displayZone,
45
+ status: lifecycle.status,
46
+ visibleInOffice: lifecycle.visibleInOffice,
47
+ lifecycle,
48
+ timestamps: {
49
+ createdAt: session.createdAt,
50
+ updatedAt: session.updatedAt,
51
+ lastOutputAt: session.lastOutputAt || null
52
+ },
53
+ runtime,
54
+ createdAt: session.createdAt,
55
+ updatedAt: session.updatedAt,
56
+ lastOutputAt: session.lastOutputAt || null,
57
+ pid: runtime.pid,
58
+ host: runtime.host,
59
+ meta,
60
+ logs: [...(session.logs || [])],
61
+ events: [...(session.events || [])]
62
+ };
63
+ }
64
+
65
+ function toSessionSummary(session) {
66
+ const publicSession = toPublicSession(session);
67
+ if (!publicSession) {
68
+ return null;
69
+ }
70
+
71
+ return {
72
+ contractVersion: CONTRACT_VERSION,
73
+ sessionId: publicSession.sessionId,
74
+ provider: publicSession.provider,
75
+ title: publicSession.title,
76
+ mode: publicSession.mode,
77
+ transport: publicSession.transport,
78
+ state: publicSession.state,
79
+ displayState: publicSession.displayState,
80
+ displayZone: publicSession.displayZone,
81
+ status: publicSession.status,
82
+ visibleInOffice: publicSession.visibleInOffice,
83
+ lifecycle: publicSession.lifecycle,
84
+ timestamps: publicSession.timestamps,
85
+ runtime: {
86
+ host: publicSession.runtime.host,
87
+ hasTerminal: publicSession.runtime.hasTerminal
88
+ },
89
+ createdAt: publicSession.createdAt,
90
+ updatedAt: publicSession.updatedAt
91
+ };
92
+ }
93
+
94
+ module.exports = {
95
+ CONTRACT_VERSION,
96
+ toPublicSession,
97
+ toSessionSummary
98
+ };
@@ -0,0 +1,23 @@
1
+ const DISPLAY_STATES = {
2
+ IDLE: "idle",
3
+ WORKING: "working",
4
+ APPROVAL: "approval",
5
+ ATTENTION: "attention"
6
+ };
7
+
8
+ const DISPLAY_ZONES = {
9
+ idle: "idle-zone",
10
+ working: "working-zone",
11
+ approval: "approval-zone",
12
+ attention: "attention-zone"
13
+ };
14
+
15
+ function displayZoneFor(state) {
16
+ return DISPLAY_ZONES[state] || DISPLAY_ZONES.working;
17
+ }
18
+
19
+ module.exports = {
20
+ DISPLAY_STATES,
21
+ DISPLAY_ZONES,
22
+ displayZoneFor
23
+ };
@@ -0,0 +1,232 @@
1
+ const { EventEmitter } = require("node:events");
2
+ const os = require("node:os");
3
+ const crypto = require("node:crypto");
4
+ const { LOG_LIMIT } = require("../config");
5
+ const { displayZoneFor } = require("../state");
6
+ const { toPublicSession, toSessionSummary } = require("../session-contract");
7
+
8
+ function isoNow() {
9
+ return new Date().toISOString();
10
+ }
11
+
12
+ function cloneSession(session) {
13
+ return toPublicSession(session);
14
+ }
15
+
16
+ function applyState(session, nextState) {
17
+ session.state = nextState;
18
+ session.displayState = nextState;
19
+ session.displayZone = displayZoneFor(nextState);
20
+ }
21
+
22
+ function createSessionStore() {
23
+ const sessions = new Map();
24
+ const emitter = new EventEmitter();
25
+
26
+ function buildSession(payload) {
27
+ const sessionId = payload.sessionId || `sess_${crypto.randomBytes(5).toString("hex")}`;
28
+ const state = payload.state || "idle";
29
+ const createdAt = payload.createdAt || isoNow();
30
+ const updatedAt = payload.updatedAt || createdAt;
31
+
32
+ return {
33
+ sessionId,
34
+ provider: payload.provider || "generic",
35
+ title: payload.title || `${payload.provider || "worker"} session`,
36
+ command: payload.command || "",
37
+ cwd: payload.cwd || process.cwd(),
38
+ mode: payload.mode || "managed",
39
+ transport: payload.transport || "pty",
40
+ state,
41
+ displayState: payload.displayState || state,
42
+ displayZone: payload.displayZone || displayZoneFor(state),
43
+ status: payload.status || "registered",
44
+ createdAt,
45
+ updatedAt,
46
+ lastOutputAt: payload.lastOutputAt || null,
47
+ pid: payload.pid || null,
48
+ host: payload.host || os.hostname(),
49
+ meta: { ...(payload.meta || {}) },
50
+ logs: [...(payload.logs || [])],
51
+ events: [...(payload.events || [])]
52
+ };
53
+ }
54
+
55
+ function emitUpdate(sessionId) {
56
+ emitter.emit("session:update", getSession(sessionId));
57
+ }
58
+
59
+ function upsertSession(payload) {
60
+ const sessionId = payload.sessionId || `sess_${crypto.randomBytes(5).toString("hex")}`;
61
+ const existing = sessions.get(sessionId);
62
+
63
+ if (!existing) {
64
+ const created = buildSession({ ...payload, sessionId });
65
+ sessions.set(sessionId, created);
66
+ emitUpdate(sessionId);
67
+ return created;
68
+ }
69
+
70
+ Object.assign(existing, {
71
+ provider: payload.provider || existing.provider,
72
+ title: payload.title || existing.title,
73
+ command: payload.command || existing.command,
74
+ cwd: payload.cwd || existing.cwd,
75
+ mode: payload.mode || existing.mode,
76
+ transport: payload.transport || existing.transport,
77
+ pid: payload.pid === undefined ? existing.pid : payload.pid,
78
+ host: payload.host || existing.host,
79
+ meta: payload.meta ? { ...existing.meta, ...payload.meta } : existing.meta,
80
+ updatedAt: payload.updatedAt || isoNow()
81
+ });
82
+
83
+ if (payload.lastOutputAt !== undefined) {
84
+ existing.lastOutputAt = payload.lastOutputAt;
85
+ }
86
+ if (payload.state) {
87
+ applyState(existing, payload.state);
88
+ }
89
+ if (payload.displayState && !payload.state) {
90
+ existing.displayState = payload.displayState;
91
+ }
92
+ if (payload.displayZone && !payload.state) {
93
+ existing.displayZone = payload.displayZone;
94
+ }
95
+ if (payload.status) {
96
+ existing.status = payload.status;
97
+ }
98
+
99
+ emitUpdate(sessionId);
100
+ return existing;
101
+ }
102
+
103
+ function setSessionState(sessionId, nextState, patch = {}) {
104
+ const session = sessions.get(sessionId);
105
+ if (!session) {
106
+ return null;
107
+ }
108
+
109
+ applyState(session, nextState);
110
+ session.updatedAt = isoNow();
111
+ Object.assign(session, patch);
112
+ if (patch.displayState) {
113
+ session.displayState = patch.displayState;
114
+ }
115
+ if (patch.displayZone) {
116
+ session.displayZone = patch.displayZone;
117
+ }
118
+ emitUpdate(sessionId);
119
+ return session;
120
+ }
121
+
122
+ function addEvent(sessionId, eventName, patch = {}) {
123
+ const session = sessions.get(sessionId);
124
+ if (!session) {
125
+ return null;
126
+ }
127
+
128
+ session.events.push({
129
+ name: eventName,
130
+ state: session.state,
131
+ timestamp: isoNow(),
132
+ meta: patch.meta || {}
133
+ });
134
+ session.events = session.events.slice(-80);
135
+ session.updatedAt = isoNow();
136
+ emitUpdate(sessionId);
137
+ return session;
138
+ }
139
+
140
+ function appendOutput(sessionId, chunk) {
141
+ const session = sessions.get(sessionId);
142
+ if (!session) {
143
+ return null;
144
+ }
145
+
146
+ const lines = String(chunk).replace(/\r/g, "").split("\n").filter(Boolean);
147
+ session.logs.push(...lines);
148
+ session.logs = session.logs.slice(-LOG_LIMIT);
149
+ session.lastOutputAt = isoNow();
150
+ session.updatedAt = session.lastOutputAt;
151
+ // Terminal output does not change session state — skip session:update broadcast.
152
+ // Terminal data is forwarded directly by pty-manager via broadcastTerminal.
153
+ return session;
154
+ }
155
+
156
+ function markExit(sessionId, patch = {}) {
157
+ const session = sessions.get(sessionId);
158
+ if (!session) {
159
+ return null;
160
+ }
161
+
162
+ session.pid = null;
163
+ session.updatedAt = isoNow();
164
+ Object.assign(session, patch);
165
+ if (patch.state) {
166
+ applyState(session, patch.state);
167
+ } else {
168
+ if (patch.displayState) {
169
+ session.displayState = patch.displayState;
170
+ }
171
+ if (patch.displayZone) {
172
+ session.displayZone = patch.displayZone;
173
+ }
174
+ }
175
+ emitUpdate(sessionId);
176
+ return session;
177
+ }
178
+
179
+ function getSession(sessionId) {
180
+ const session = sessions.get(sessionId);
181
+ return session ? cloneSession(session) : null;
182
+ }
183
+
184
+ function getSessionSummary(sessionId) {
185
+ const session = sessions.get(sessionId);
186
+ return session ? toSessionSummary(session) : null;
187
+ }
188
+
189
+ function listSessions() {
190
+ return [...sessions.values()]
191
+ .sort((left, right) => right.updatedAt.localeCompare(left.updatedAt))
192
+ .map(cloneSession);
193
+ }
194
+
195
+ function listSessionSummaries() {
196
+ return [...sessions.values()]
197
+ .filter((session) => !["completed", "exited"].includes(session.status))
198
+ .sort((left, right) => right.updatedAt.localeCompare(left.updatedAt))
199
+ .map((session) => toSessionSummary(session));
200
+ }
201
+
202
+ function removeSession(sessionId) {
203
+ const session = sessions.get(sessionId);
204
+ if (!session) {
205
+ return false;
206
+ }
207
+ sessions.delete(sessionId);
208
+ emitter.emit("session:remove", {
209
+ sessionId,
210
+ session: cloneSession(session)
211
+ });
212
+ return true;
213
+ }
214
+
215
+ return {
216
+ emitter,
217
+ upsertSession,
218
+ setSessionState,
219
+ addEvent,
220
+ appendOutput,
221
+ markExit,
222
+ getSession,
223
+ getSessionSummary,
224
+ listSessions,
225
+ listSessionSummaries,
226
+ removeSession
227
+ };
228
+ }
229
+
230
+ module.exports = {
231
+ createSessionStore
232
+ };