ac-framework 1.9.6 → 1.9.8

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.
@@ -1,12 +1,21 @@
1
1
  import { buildAgentPrompt, ROLE_SYSTEM_PROMPTS } from './role-prompts.js';
2
- import { runOpenCodePrompt } from './opencode-client.js';
2
+ import { runOpenCodePromptDetailed } from './opencode-client.js';
3
3
  import { nextRole, shouldStop } from './scheduler.js';
4
4
  import { resolveRoleModel } from './model-selection.js';
5
+ import { buildMeetingSummary, createTurnRecord, updateSharedContext } from './collab-summary.js';
6
+ import {
7
+ appendRunEvent,
8
+ extractFinalSummary,
9
+ incrementRoleRetry,
10
+ roleRetryCount,
11
+ } from './run-state.js';
5
12
  import {
6
13
  addAgentMessage,
14
+ appendMeetingTurn,
7
15
  loadSessionState,
8
16
  saveSessionState,
9
17
  stopSession,
18
+ writeMeetingSummary,
10
19
  withSessionLock,
11
20
  } from './state-store.js';
12
21
 
@@ -17,11 +26,115 @@ function buildRuntimePrompt({ state, role }) {
17
26
  task: state.task,
18
27
  round: state.round,
19
28
  messages: state.messages,
29
+ sharedContext: state.run?.sharedContext || null,
20
30
  });
21
31
 
22
32
  return [roleContext, '', collaborativePrompt].join('\n');
23
33
  }
24
34
 
35
+ function ensureRunState(state) {
36
+ if (state.run && typeof state.run === 'object') {
37
+ return {
38
+ ...state.run,
39
+ sharedContext: state.run.sharedContext && typeof state.run.sharedContext === 'object'
40
+ ? {
41
+ decisions: Array.isArray(state.run.sharedContext.decisions) ? state.run.sharedContext.decisions : [],
42
+ openIssues: Array.isArray(state.run.sharedContext.openIssues) ? state.run.sharedContext.openIssues : [],
43
+ risks: Array.isArray(state.run.sharedContext.risks) ? state.run.sharedContext.risks : [],
44
+ actionItems: Array.isArray(state.run.sharedContext.actionItems) ? state.run.sharedContext.actionItems : [],
45
+ notes: Array.isArray(state.run.sharedContext.notes) ? state.run.sharedContext.notes : [],
46
+ }
47
+ : {
48
+ decisions: [],
49
+ openIssues: [],
50
+ risks: [],
51
+ actionItems: [],
52
+ notes: [],
53
+ },
54
+ };
55
+ }
56
+ return {
57
+ runId: null,
58
+ status: 'idle',
59
+ startedAt: null,
60
+ finishedAt: null,
61
+ currentRole: null,
62
+ retriesUsed: {},
63
+ round: state.round || 1,
64
+ events: [],
65
+ finalSummary: null,
66
+ lastError: null,
67
+ policy: {
68
+ timeoutPerRoleMs: 180000,
69
+ retryOnTimeout: 1,
70
+ fallbackOnFailure: 'abort',
71
+ maxRounds: state.maxRounds,
72
+ },
73
+ };
74
+ }
75
+
76
+ function applyRoleFailurePolicy(state, role, errorMessage) {
77
+ let run = ensureRunState(state);
78
+ const policy = run.policy || {};
79
+ const currentRetries = roleRetryCount(run, role);
80
+ const canRetry = currentRetries < (policy.retryOnTimeout ?? 0);
81
+
82
+ run = appendRunEvent(run, 'role_failed', {
83
+ role,
84
+ retry: canRetry,
85
+ error: errorMessage,
86
+ });
87
+
88
+ if (canRetry) {
89
+ run = incrementRoleRetry(run, role);
90
+ return {
91
+ ...state,
92
+ run: {
93
+ ...run,
94
+ currentRole: role,
95
+ status: 'running',
96
+ lastError: null,
97
+ },
98
+ activeAgent: null,
99
+ // retry same role by rewinding index
100
+ nextRoleIndex: state.roles.indexOf(role),
101
+ };
102
+ }
103
+
104
+ const fallback = policy.fallbackOnFailure || 'abort';
105
+ if (fallback === 'skip') {
106
+ const skipped = appendRunEvent(run, 'role_skipped', { role, error: errorMessage });
107
+ return {
108
+ ...state,
109
+ run: {
110
+ ...skipped,
111
+ currentRole: null,
112
+ status: 'running',
113
+ lastError: null,
114
+ },
115
+ activeAgent: null,
116
+ };
117
+ }
118
+
119
+ const failed = appendRunEvent(run, 'run_failed', { role, error: errorMessage });
120
+ return {
121
+ ...state,
122
+ status: 'failed',
123
+ activeAgent: null,
124
+ run: {
125
+ ...failed,
126
+ status: 'failed',
127
+ currentRole: null,
128
+ finishedAt: new Date().toISOString(),
129
+ lastError: {
130
+ code: 'ROLE_FAILURE',
131
+ message: errorMessage,
132
+ role,
133
+ },
134
+ },
135
+ };
136
+ }
137
+
25
138
  export async function runTurn(sessionId, options = {}) {
26
139
  return withSessionLock(sessionId, async () => {
27
140
  let state = await loadSessionState(sessionId);
@@ -44,7 +157,7 @@ export async function runTurn(sessionId, options = {}) {
44
157
  let content;
45
158
  try {
46
159
  const effectiveModel = resolveRoleModel(state, scheduled.role, options.model);
47
- content = await runOpenCodePrompt({
160
+ const output = await runOpenCodePromptDetailed({
48
161
  prompt,
49
162
  cwd: options.cwd || process.cwd(),
50
163
  model: effectiveModel,
@@ -52,6 +165,7 @@ export async function runTurn(sessionId, options = {}) {
52
165
  binaryPath: options.opencodeBin,
53
166
  timeoutMs: options.timeoutMs,
54
167
  });
168
+ content = output.text;
55
169
  } catch (error) {
56
170
  content = `Agent failed: ${error.message}`;
57
171
  }
@@ -109,7 +223,7 @@ export async function executeActiveTurn(sessionId, role, options = {}) {
109
223
  let content;
110
224
  try {
111
225
  const effectiveModel = resolveRoleModel(state, role, options.model);
112
- content = await runOpenCodePrompt({
226
+ const output = await runOpenCodePromptDetailed({
113
227
  prompt,
114
228
  cwd: options.cwd || process.cwd(),
115
229
  model: effectiveModel,
@@ -117,6 +231,7 @@ export async function executeActiveTurn(sessionId, role, options = {}) {
117
231
  binaryPath: options.opencodeBin,
118
232
  timeoutMs: options.timeoutMs,
119
233
  });
234
+ content = output.text;
120
235
  } catch (error) {
121
236
  content = `Agent failed: ${error.message}`;
122
237
  }
@@ -141,13 +256,33 @@ export async function runWorkerIteration(sessionId, role, options = {}) {
141
256
  if (state.status !== 'running') return state;
142
257
  if (!state.roles.includes(role)) return state;
143
258
 
259
+ const run = ensureRunState(state);
260
+ if (run.status === 'cancelled' || run.status === 'failed' || run.status === 'completed') {
261
+ return state;
262
+ }
263
+
144
264
  if (!state.activeAgent) {
145
265
  const scheduled = nextRole(state);
266
+ const startedRun = run.status === 'idle'
267
+ ? appendRunEvent({
268
+ ...run,
269
+ status: 'running',
270
+ startedAt: new Date().toISOString(),
271
+ currentRole: scheduled.role,
272
+ round: scheduled.round,
273
+ }, 'run_started', { round: scheduled.round })
274
+ : appendRunEvent({
275
+ ...run,
276
+ currentRole: scheduled.role,
277
+ round: scheduled.round,
278
+ }, 'role_scheduled', { role: scheduled.role, round: scheduled.round });
279
+
146
280
  state = await saveSessionState({
147
281
  ...state,
148
282
  activeAgent: scheduled.role,
149
283
  nextRoleIndex: scheduled.nextRoleIndex,
150
284
  round: scheduled.round,
285
+ run: startedRun,
151
286
  });
152
287
  }
153
288
 
@@ -157,28 +292,86 @@ export async function runWorkerIteration(sessionId, role, options = {}) {
157
292
 
158
293
  const prompt = buildRuntimePrompt({ state, role });
159
294
  let content;
295
+ let outputEvents = [];
296
+ let effectiveModel = null;
297
+ let failed = false;
298
+ let errorMessage = '';
160
299
  try {
161
- const effectiveModel = resolveRoleModel(state, role, options.model);
162
- content = await runOpenCodePrompt({
300
+ effectiveModel = resolveRoleModel(state, role, options.model);
301
+ state = await saveSessionState({
302
+ ...state,
303
+ run: appendRunEvent({
304
+ ...ensureRunState(state),
305
+ currentRole: role,
306
+ status: 'running',
307
+ }, 'role_started', { role, model: effectiveModel }),
308
+ });
309
+ const output = await runOpenCodePromptDetailed({
163
310
  prompt,
164
311
  cwd: options.cwd || process.cwd(),
165
312
  model: effectiveModel,
166
313
  agent: options.agent,
167
314
  binaryPath: options.opencodeBin,
168
- timeoutMs: options.timeoutMs,
315
+ timeoutMs: options.timeoutMs || ensureRunState(state).policy?.timeoutPerRoleMs || 180000,
169
316
  });
317
+ content = output.text;
318
+ outputEvents = output.events || [];
170
319
  } catch (error) {
320
+ failed = true;
321
+ errorMessage = error.message;
171
322
  content = `Agent failed: ${error.message}`;
172
323
  }
173
324
 
174
325
  state = await addAgentMessage(state, role, content);
326
+ if (failed) {
327
+ await appendMeetingTurn(sessionId, createTurnRecord({
328
+ round: state.round,
329
+ role,
330
+ model: effectiveModel,
331
+ content,
332
+ events: outputEvents,
333
+ }));
334
+ state = await saveSessionState(applyRoleFailurePolicy(state, role, errorMessage));
335
+ return state;
336
+ }
337
+
338
+ const turnRecord = createTurnRecord({
339
+ round: state.round,
340
+ role,
341
+ model: effectiveModel,
342
+ content,
343
+ events: outputEvents,
344
+ });
345
+ await appendMeetingTurn(sessionId, turnRecord);
346
+
347
+ const updatedShared = updateSharedContext(ensureRunState(state).sharedContext, turnRecord);
348
+ const succeededRun = appendRunEvent({
349
+ ...ensureRunState(state),
350
+ currentRole: null,
351
+ lastError: null,
352
+ sharedContext: updatedShared,
353
+ }, 'role_succeeded', { role, chars: content.length, events: outputEvents.length });
354
+
175
355
  state = await saveSessionState({
176
356
  ...state,
177
357
  activeAgent: null,
358
+ run: succeededRun,
178
359
  });
179
360
 
180
361
  if (shouldStop(state)) {
181
362
  state = await stopSession(state, 'completed');
363
+ const summaryMd = buildMeetingSummary(state.messages, ensureRunState(state), ensureRunState(state).sharedContext);
364
+ await writeMeetingSummary(sessionId, summaryMd);
365
+ const finalRun = appendRunEvent({
366
+ ...ensureRunState(state),
367
+ status: 'completed',
368
+ finishedAt: new Date().toISOString(),
369
+ finalSummary: extractFinalSummary(state.messages, ensureRunState(state)),
370
+ }, 'run_completed', { round: state.round });
371
+ state = await saveSessionState({
372
+ ...state,
373
+ run: finalRun,
374
+ });
182
375
  }
183
376
 
184
377
  return state;
@@ -25,11 +25,41 @@ export const ROLE_SYSTEM_PROMPTS = {
25
25
  ].join(' '),
26
26
  };
27
27
 
28
- export function buildAgentPrompt({ role, task, round, messages, maxMessages = 24 }) {
28
+ function formatSharedContext(sharedContext) {
29
+ if (!sharedContext || typeof sharedContext !== 'object') {
30
+ return 'No shared summary yet.';
31
+ }
32
+
33
+ const sections = [
34
+ ['Decisions', sharedContext.decisions],
35
+ ['Open issues', sharedContext.openIssues],
36
+ ['Risks', sharedContext.risks],
37
+ ['Action items', sharedContext.actionItems],
38
+ ['Notes', sharedContext.notes],
39
+ ];
40
+
41
+ const lines = [];
42
+ for (const [name, list] of sections) {
43
+ const items = Array.isArray(list) ? list.slice(-6) : [];
44
+ lines.push(`${name}:`);
45
+ if (items.length === 0) {
46
+ lines.push('- (none)');
47
+ } else {
48
+ for (const item of items) {
49
+ lines.push(`- ${item}`);
50
+ }
51
+ }
52
+ }
53
+
54
+ return lines.join('\n');
55
+ }
56
+
57
+ export function buildAgentPrompt({ role, task, round, messages, sharedContext = null, maxMessages = 18 }) {
29
58
  const recent = messages.slice(-maxMessages);
30
59
  const transcript = recent.length
31
60
  ? recent.map((msg, idx) => `${idx + 1}. [${msg.from}] ${msg.content}`).join('\n')
32
61
  : 'No previous messages.';
62
+ const shared = formatSharedContext(sharedContext);
33
63
 
34
64
  return [
35
65
  `ROLE: ${role}`,
@@ -37,6 +67,9 @@ export function buildAgentPrompt({ role, task, round, messages, maxMessages = 24
37
67
  '',
38
68
  `TASK: ${task}`,
39
69
  '',
70
+ 'SHARED CONTEXT SUMMARY:',
71
+ shared,
72
+ '',
40
73
  'TEAM TRANSCRIPT (latest first-order history):',
41
74
  transcript,
42
75
  '',
@@ -0,0 +1,113 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import {
3
+ DEFAULT_ROLE_RETRIES,
4
+ DEFAULT_ROLE_TIMEOUT_MS,
5
+ DEFAULT_MAX_ROUNDS,
6
+ } from './constants.js';
7
+
8
+ export function normalizeRunPolicy(policy = {}, maxRounds = DEFAULT_MAX_ROUNDS) {
9
+ const timeoutPerRoleMs = Number.isInteger(policy.timeoutPerRoleMs) && policy.timeoutPerRoleMs > 0
10
+ ? policy.timeoutPerRoleMs
11
+ : DEFAULT_ROLE_TIMEOUT_MS;
12
+ const retryOnTimeout = Number.isInteger(policy.retryOnTimeout) && policy.retryOnTimeout >= 0
13
+ ? policy.retryOnTimeout
14
+ : DEFAULT_ROLE_RETRIES;
15
+ const fallbackOnFailure = ['retry', 'skip', 'abort'].includes(policy.fallbackOnFailure)
16
+ ? policy.fallbackOnFailure
17
+ : 'abort';
18
+ const rounds = Number.isInteger(maxRounds) && maxRounds > 0 ? maxRounds : DEFAULT_MAX_ROUNDS;
19
+
20
+ return {
21
+ timeoutPerRoleMs,
22
+ retryOnTimeout,
23
+ fallbackOnFailure,
24
+ maxRounds: rounds,
25
+ };
26
+ }
27
+
28
+ export function createRunState(policy = {}, maxRounds = DEFAULT_MAX_ROUNDS) {
29
+ return {
30
+ runId: randomUUID(),
31
+ status: 'idle',
32
+ startedAt: null,
33
+ finishedAt: null,
34
+ currentRole: null,
35
+ retriesUsed: {},
36
+ round: 1,
37
+ events: [],
38
+ finalSummary: null,
39
+ sharedContext: {
40
+ decisions: [],
41
+ openIssues: [],
42
+ risks: [],
43
+ actionItems: [],
44
+ notes: [],
45
+ },
46
+ lastError: null,
47
+ policy: normalizeRunPolicy(policy, maxRounds),
48
+ };
49
+ }
50
+
51
+ export function appendRunEvent(run, type, details = {}) {
52
+ const event = {
53
+ id: randomUUID(),
54
+ type,
55
+ timestamp: new Date().toISOString(),
56
+ ...details,
57
+ };
58
+ const events = [...(run.events || []), event];
59
+ return {
60
+ ...run,
61
+ events,
62
+ };
63
+ }
64
+
65
+ export function roleRetryCount(run, role) {
66
+ return Number(run?.retriesUsed?.[role] || 0);
67
+ }
68
+
69
+ export function incrementRoleRetry(run, role) {
70
+ return {
71
+ ...run,
72
+ retriesUsed: {
73
+ ...(run.retriesUsed || {}),
74
+ [role]: roleRetryCount(run, role) + 1,
75
+ },
76
+ };
77
+ }
78
+
79
+ export function extractFinalSummary(messages = [], run = null) {
80
+ const agentMessages = messages.filter((msg) => msg?.from && msg.from !== 'user');
81
+ if (agentMessages.length === 0) return '';
82
+ const orderedRoles = ['planner', 'critic', 'coder', 'reviewer'];
83
+ const lastByRole = new Map();
84
+ for (const msg of agentMessages) lastByRole.set(msg.from, msg.content || '');
85
+
86
+ const sections = ['# SynapseGrid Final Summary', ''];
87
+ sections.push('## Per-role last contributions');
88
+ for (const role of orderedRoles) {
89
+ const content = String(lastByRole.get(role) || '').trim();
90
+ sections.push(`- ${role}: ${content ? content.slice(0, 500) : '(none)'}`);
91
+ }
92
+
93
+ const shared = run?.sharedContext;
94
+ if (shared && typeof shared === 'object') {
95
+ const writeList = (title, items) => {
96
+ sections.push('');
97
+ sections.push(`## ${title}`);
98
+ const list = Array.isArray(items) ? items.slice(-10) : [];
99
+ if (list.length === 0) {
100
+ sections.push('- (none)');
101
+ } else {
102
+ for (const item of list) sections.push(`- ${item}`);
103
+ }
104
+ };
105
+
106
+ writeList('Decisions', shared.decisions);
107
+ writeList('Open issues', shared.openIssues);
108
+ writeList('Risks', shared.risks);
109
+ writeList('Action items', shared.actionItems);
110
+ }
111
+
112
+ return sections.join('\n').trim();
113
+ }
@@ -44,6 +44,7 @@ export function runTmux(command, args, options = {}) {
44
44
 
45
45
  export async function spawnTmuxSession({ sessionName, sessionDir, sessionId }) {
46
46
  const role0 = COLLAB_ROLES[0];
47
+ const role0Log = roleLogPath(sessionDir, role0);
47
48
  await runTmux('tmux', [
48
49
  'new-session',
49
50
  '-d',
@@ -51,17 +52,18 @@ export async function spawnTmuxSession({ sessionName, sessionDir, sessionId }) {
51
52
  sessionName,
52
53
  '-n',
53
54
  role0,
54
- `bash -lc 'node "${runnerPath}" agents worker --session ${sessionId} --role ${role0} >> "${roleLogPath(sessionDir, role0)}" 2>&1'`,
55
+ `bash -lc 'node "${runnerPath}" agents worker --session ${sessionId} --role ${role0} 2>&1 | tee -a "${role0Log}"'`,
55
56
  ]);
56
57
 
57
58
  for (let idx = 1; idx < COLLAB_ROLES.length; idx += 1) {
58
59
  const role = COLLAB_ROLES[idx];
60
+ const roleLog = roleLogPath(sessionDir, role);
59
61
  await runTmux('tmux', [
60
62
  'split-window',
61
63
  '-t',
62
64
  sessionName,
63
65
  '-v',
64
- `bash -lc 'node "${runnerPath}" agents worker --session ${sessionId} --role ${role} >> "${roleLogPath(sessionDir, role)}" 2>&1'`,
66
+ `bash -lc 'node "${runnerPath}" agents worker --session ${sessionId} --role ${role} 2>&1 | tee -a "${roleLog}"'`,
65
67
  ]);
66
68
  }
67
69
 
@@ -8,6 +8,7 @@ import {
8
8
  SESSION_ROOT_DIR,
9
9
  CURRENT_SESSION_FILE,
10
10
  } from './constants.js';
11
+ import { createRunState } from './run-state.js';
11
12
  import { sanitizeRoleModels } from './model-selection.js';
12
13
 
13
14
  function sleep(ms) {
@@ -38,6 +39,22 @@ function getTranscriptPath(sessionId) {
38
39
  return join(getSessionDir(sessionId), 'transcript.jsonl');
39
40
  }
40
41
 
42
+ function getTurnsDir(sessionId) {
43
+ return join(getSessionDir(sessionId), 'turns');
44
+ }
45
+
46
+ function getMeetingLogPath(sessionId) {
47
+ return join(getSessionDir(sessionId), 'meeting-log.md');
48
+ }
49
+
50
+ function getMeetingLogJsonlPath(sessionId) {
51
+ return join(getSessionDir(sessionId), 'meeting-log.jsonl');
52
+ }
53
+
54
+ function getMeetingSummaryPath(sessionId) {
55
+ return join(getSessionDir(sessionId), 'meeting-summary.md');
56
+ }
57
+
41
58
  function initialState(task, options = {}) {
42
59
  const sessionId = randomUUID();
43
60
  const createdAt = new Date().toISOString();
@@ -57,6 +74,7 @@ function initialState(task, options = {}) {
57
74
  roleModels: sanitizeRoleModels(options.roleModels),
58
75
  opencodeBin: options.opencodeBin || null,
59
76
  tmuxSessionName: options.tmuxSessionName || null,
77
+ run: createRunState(options.runPolicy, Number.isInteger(options.maxRounds) ? options.maxRounds : DEFAULT_MAX_ROUNDS),
60
78
  messages: [
61
79
  {
62
80
  from: 'user',
@@ -91,6 +109,57 @@ export async function appendTranscript(sessionId, message) {
91
109
  await appendFile(transcriptPath, line, 'utf8');
92
110
  }
93
111
 
112
+ export async function appendMeetingTurn(sessionId, turnRecord) {
113
+ const sessionDir = getSessionDir(sessionId);
114
+ const turnsDir = getTurnsDir(sessionId);
115
+ await mkdir(sessionDir, { recursive: true });
116
+ await mkdir(turnsDir, { recursive: true });
117
+
118
+ const safeRole = String(turnRecord?.role || 'unknown').replace(/[^a-z0-9_-]/gi, '_');
119
+ const safeRound = Number.isInteger(turnRecord?.round) ? turnRecord.round : 0;
120
+ const turnFilePath = join(turnsDir, `${String(safeRound).padStart(3, '0')}-${safeRole}.json`);
121
+ await writeFile(turnFilePath, JSON.stringify(turnRecord, null, 2) + '\n', 'utf8');
122
+
123
+ const mdPath = getMeetingLogPath(sessionId);
124
+ const jsonlPath = getMeetingLogJsonlPath(sessionId);
125
+ const snippet = (turnRecord?.snippet || '').trim() || '(empty output)';
126
+ const keyPoints = Array.isArray(turnRecord?.keyPoints) ? turnRecord.keyPoints : [];
127
+
128
+ const mdBlock = [
129
+ `## Round ${safeRound} - ${safeRole}`,
130
+ `- timestamp: ${turnRecord?.timestamp || new Date().toISOString()}`,
131
+ `- model: ${turnRecord?.model || '(default)'}`,
132
+ `- events: ${turnRecord?.eventCount ?? 0}`,
133
+ '',
134
+ '### Output snippet',
135
+ snippet,
136
+ '',
137
+ '### Key points',
138
+ ...(keyPoints.length > 0 ? keyPoints.map((line) => `- ${line.replace(/^[-*]\s+/, '')}`) : ['- (none)']),
139
+ '',
140
+ ].join('\n');
141
+
142
+ if (!existsSync(mdPath)) {
143
+ const header = `# SynapseGrid Meeting Log\n\nSession: ${sessionId}\n\n`;
144
+ await writeFile(mdPath, header + mdBlock, 'utf8');
145
+ } else {
146
+ await appendFile(mdPath, mdBlock, 'utf8');
147
+ }
148
+
149
+ await appendFile(jsonlPath, JSON.stringify(turnRecord) + '\n', 'utf8');
150
+ return {
151
+ turnFilePath,
152
+ meetingLogPath: mdPath,
153
+ meetingJsonlPath: jsonlPath,
154
+ };
155
+ }
156
+
157
+ export async function writeMeetingSummary(sessionId, summaryMarkdown) {
158
+ const outputPath = getMeetingSummaryPath(sessionId);
159
+ await writeFile(outputPath, String(summaryMarkdown || '').trimEnd() + '\n', 'utf8');
160
+ return outputPath;
161
+ }
162
+
94
163
  export async function loadCurrentSessionId() {
95
164
  if (!existsSync(CURRENT_SESSION_FILE)) return null;
96
165
  const raw = await readFile(CURRENT_SESSION_FILE, 'utf8');
@@ -109,7 +178,6 @@ export async function saveSessionState(state) {
109
178
  updatedAt: new Date().toISOString(),
110
179
  };
111
180
  await writeFile(getSessionStatePath(updated.sessionId), JSON.stringify(updated, null, 2) + '\n', 'utf8');
112
- await writeCurrentSession(updated.sessionId, updated.updatedAt);
113
181
  return updated;
114
182
  }
115
183