@townco/agent 0.1.82 → 0.1.84

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.
Files changed (38) hide show
  1. package/dist/acp-server/adapter.js +150 -49
  2. package/dist/acp-server/http.js +56 -1
  3. package/dist/acp-server/session-storage.d.ts +44 -12
  4. package/dist/acp-server/session-storage.js +153 -59
  5. package/dist/definition/index.d.ts +2 -2
  6. package/dist/definition/index.js +1 -1
  7. package/dist/runner/agent-runner.d.ts +4 -2
  8. package/dist/runner/hooks/executor.d.ts +1 -0
  9. package/dist/runner/hooks/executor.js +18 -2
  10. package/dist/runner/hooks/predefined/compaction-tool.js +3 -2
  11. package/dist/runner/hooks/predefined/tool-response-compactor.d.ts +0 -4
  12. package/dist/runner/hooks/predefined/tool-response-compactor.js +30 -16
  13. package/dist/runner/hooks/types.d.ts +4 -5
  14. package/dist/runner/langchain/index.d.ts +1 -0
  15. package/dist/runner/langchain/index.js +156 -33
  16. package/dist/runner/langchain/tools/artifacts.d.ts +68 -0
  17. package/dist/runner/langchain/tools/artifacts.js +466 -0
  18. package/dist/runner/langchain/tools/browser.js +15 -3
  19. package/dist/runner/langchain/tools/filesystem.d.ts +8 -4
  20. package/dist/runner/langchain/tools/filesystem.js +118 -82
  21. package/dist/runner/langchain/tools/generate_image.d.ts +19 -0
  22. package/dist/runner/langchain/tools/generate_image.js +54 -14
  23. package/dist/runner/langchain/tools/subagent.js +2 -2
  24. package/dist/runner/langchain/tools/todo.js +3 -0
  25. package/dist/runner/langchain/tools/web_search.js +6 -0
  26. package/dist/runner/session-context.d.ts +40 -0
  27. package/dist/runner/session-context.js +69 -0
  28. package/dist/runner/tools.d.ts +2 -2
  29. package/dist/runner/tools.js +2 -0
  30. package/dist/scaffold/project-scaffold.js +7 -3
  31. package/dist/telemetry/setup.js +1 -1
  32. package/dist/templates/index.d.ts +1 -1
  33. package/dist/tsconfig.tsbuildinfo +1 -1
  34. package/dist/utils/context-size-calculator.d.ts +1 -10
  35. package/dist/utils/context-size-calculator.js +1 -12
  36. package/dist/utils/token-counter.js +2 -2
  37. package/package.json +10 -10
  38. package/templates/index.ts +1 -1
@@ -278,8 +278,10 @@ export class AgentAcpAdapter {
278
278
  return response;
279
279
  }
280
280
  async newSession(params) {
281
- // Generate a unique session ID for this session
282
- const sessionId = Math.random().toString(36).substring(2);
281
+ // Use sessionId from params if provided (HTTP transport injects it),
282
+ // otherwise generate a unique session ID for this session
283
+ const sessionId = params.sessionId ??
284
+ Math.random().toString(36).substring(2);
283
285
  // Extract configOverrides from _meta if provided (Town Hall comparison feature)
284
286
  const configOverrides = params._meta?.configOverrides;
285
287
  const sessionData = {
@@ -380,7 +382,7 @@ export class AgentAcpAdapter {
380
382
  : {}),
381
383
  ...block._meta,
382
384
  };
383
- // Debug: log subagent data being replayed
385
+ // Debug: log subagent data and compaction metadata being replayed
384
386
  logger.info("Replaying tool_call", {
385
387
  toolCallId: block.id,
386
388
  title: block.title,
@@ -389,6 +391,8 @@ export class AgentAcpAdapter {
389
391
  hasSubagentSessionId: !!block.subagentSessionId,
390
392
  hasSubagentMessages: !!block.subagentMessages,
391
393
  subagentMessagesCount: block.subagentMessages?.length,
394
+ blockMeta: block._meta,
395
+ replayMeta,
392
396
  });
393
397
  this.connection.sessionUpdate({
394
398
  sessionId: params.sessionId,
@@ -434,6 +438,7 @@ export class AgentAcpAdapter {
434
438
  status: block.status,
435
439
  rawOutput: block.rawOutput,
436
440
  error: block.error,
441
+ _meta: replayMeta,
437
442
  },
438
443
  });
439
444
  }
@@ -598,9 +603,8 @@ export class AgentAcpAdapter {
598
603
  contextMessages.push(entry.message);
599
604
  }
600
605
  }
601
- // Calculate context size - no LLM call yet, so only estimated values
602
- const context_size = calculateContextSize(contextMessages, this.agent.definition.systemPrompt ?? undefined, undefined, // No LLM-reported tokens yet
603
- this.currentToolOverheadTokens, // Include tool overhead
606
+ // Calculate context size - only estimated values
607
+ const context_size = calculateContextSize(contextMessages, this.agent.definition.systemPrompt ?? undefined, this.currentToolOverheadTokens, // Include tool overhead
604
608
  this.currentMcpOverheadTokens, // Include MCP overhead
605
609
  getModelContextWindow(this.agent.definition.model));
606
610
  const contextSnapshot = createContextSnapshot(session.messages.length - 1, // Exclude the newly added user message (it will be passed separately via prompt)
@@ -619,7 +623,7 @@ export class AgentAcpAdapter {
619
623
  }
620
624
  };
621
625
  // Declare agentResponse and turnTokenUsage outside try block so they're accessible after catch
622
- let agentResponse;
626
+ let _agentResponse;
623
627
  // Track accumulated token usage during the turn
624
628
  const turnTokenUsage = {
625
629
  inputTokens: 0,
@@ -653,8 +657,8 @@ export class AgentAcpAdapter {
653
657
  latestContextEntry: session.context.length > 0 &&
654
658
  session.context[session.context.length - 1]
655
659
  ? {
656
- messageCount: session.context[session.context.length - 1].messages.length,
657
- contextSize: session.context[session.context.length - 1].context_size,
660
+ messageCount: session.context[session.context.length - 1]?.messages.length,
661
+ contextSize: session.context[session.context.length - 1]?.context_size,
658
662
  }
659
663
  : null,
660
664
  });
@@ -662,6 +666,8 @@ export class AgentAcpAdapter {
662
666
  prompt: params.prompt,
663
667
  sessionId: params.sessionId,
664
668
  messageId,
669
+ // Pass agent directory for session-scoped file storage (only if defined)
670
+ ...(this.agentDir ? { agentDir: this.agentDir } : {}),
665
671
  // Pass resolved context messages to agent
666
672
  contextMessages,
667
673
  };
@@ -717,19 +723,8 @@ export class AgentAcpAdapter {
717
723
  if (tokenUsage.inputTokens !== undefined &&
718
724
  tokenUsage.inputTokens > 0) {
719
725
  turnTokenUsage.inputTokens = tokenUsage.inputTokens;
720
- // Update the LAST context entry with LLM-reported tokens
721
- if (!this.noSession && session.context.length > 0) {
722
- const lastContext = session.context[session.context.length - 1];
723
- if (lastContext) {
724
- lastContext.context_size.llmReportedInputTokens =
725
- tokenUsage.inputTokens;
726
- logger.debug("Updated context entry with LLM-reported tokens", {
727
- contextIndex: session.context.length - 1,
728
- llmReportedTokens: tokenUsage.inputTokens,
729
- estimatedTokens: lastContext.context_size.totalEstimated,
730
- });
731
- }
732
- }
726
+ // Note: We no longer update context entries with LLM-reported tokens
727
+ // as they can cause mismatches when updated on mid-turn snapshots
733
728
  }
734
729
  turnTokenUsage.outputTokens += tokenUsage.outputTokens ?? 0;
735
730
  turnTokenUsage.totalTokens += tokenUsage.totalTokens ?? 0;
@@ -906,19 +901,71 @@ export class AgentAcpAdapter {
906
901
  // Get the raw output
907
902
  let rawOutput = outputMsg.rawOutput || outputMsg.output;
908
903
  let truncationWarning;
904
+ // Check for compaction metadata embedded by LangChain tool wrapper
905
+ // This happens when compaction runs in the wrapper (before reaching adapter)
906
+ if (rawOutput &&
907
+ typeof rawOutput === "object" &&
908
+ "_compactionMeta" in rawOutput) {
909
+ const compactionMeta = rawOutput._compactionMeta;
910
+ // Store in _meta for UI persistence
911
+ if (!toolCallBlock._meta) {
912
+ toolCallBlock._meta = {};
913
+ }
914
+ if (compactionMeta.action) {
915
+ toolCallBlock._meta.compactionAction = compactionMeta.action;
916
+ }
917
+ if (compactionMeta.originalTokens !== undefined) {
918
+ toolCallBlock._meta.originalTokens =
919
+ compactionMeta.originalTokens;
920
+ }
921
+ if (compactionMeta.finalTokens !== undefined) {
922
+ toolCallBlock._meta.finalTokens = compactionMeta.finalTokens;
923
+ }
924
+ // Store original content only if compaction or truncation actually occurred
925
+ const wasCompacted = compactionMeta.action === "compacted" ||
926
+ compactionMeta.action === "truncated" ||
927
+ compactionMeta.action === "compacted_then_truncated";
928
+ if (compactionMeta.originalContent &&
929
+ wasCompacted &&
930
+ this.storage) {
931
+ try {
932
+ const toolName = toolCallBlock.title || "unknown";
933
+ const originalContentPath = this.storage.saveToolOriginal(params.sessionId, toolName, outputMsg.toolCallId, compactionMeta.originalContent);
934
+ toolCallBlock._meta.originalContentPath = originalContentPath;
935
+ logger.info("Saved original content to artifacts", {
936
+ toolCallId: outputMsg.toolCallId,
937
+ toolName,
938
+ path: originalContentPath,
939
+ });
940
+ }
941
+ catch (error) {
942
+ logger.warn("Failed to save original tool content", {
943
+ toolCallId: outputMsg.toolCallId,
944
+ error: error instanceof Error ? error.message : String(error),
945
+ });
946
+ }
947
+ }
948
+ logger.info("Extracted compaction metadata from tool response wrapper", {
949
+ toolCallId: outputMsg.toolCallId,
950
+ action: compactionMeta.action,
951
+ originalTokens: compactionMeta.originalTokens,
952
+ finalTokens: compactionMeta.finalTokens,
953
+ hasOriginalContent: !!compactionMeta.originalContent,
954
+ });
955
+ // Remove the metadata from rawOutput to keep it clean
956
+ const { _compactionMeta: _, ...cleanOutput } = rawOutput;
957
+ rawOutput = cleanOutput;
958
+ }
909
959
  if (rawOutput && !this.noSession) {
910
960
  // Execute tool_response hooks if configured
911
961
  const hooks = this.agent.definition.hooks ?? [];
912
962
  if (hooks.some((h) => h.type === "tool_response")) {
913
963
  const latestContext = session.context[session.context.length - 1];
914
- // Use max of estimated and LLM-reported tokens (same logic as UI)
915
- const currentContextTokens = Math.max(latestContext?.context_size.totalEstimated ?? 0, latestContext?.context_size.llmReportedInputTokens ?? 0);
916
- // Send the context size to the UI so it shows the same value we're using for hook evaluation
964
+ // Use estimated tokens for hook evaluation
965
+ const currentContextTokens = latestContext?.context_size.totalEstimated ?? 0;
966
+ // Send the context size to the UI
917
967
  if (latestContext?.context_size) {
918
- const contextSizeForUI = {
919
- ...latestContext.context_size,
920
- totalEstimated: currentContextTokens,
921
- };
968
+ const contextSizeForUI = latestContext.context_size;
922
969
  this.connection.sessionUpdate({
923
970
  sessionId: params.sessionId,
924
971
  update: {
@@ -960,6 +1007,12 @@ export class AgentAcpAdapter {
960
1007
  });
961
1008
  // Note: Notifications are now sent in real-time via the callback
962
1009
  // The hookResult.notifications array is kept for backwards compatibility
1010
+ // Capture original content BEFORE any modification (needed for storage)
1011
+ const originalContentStr = typeof rawOutput === "object" &&
1012
+ rawOutput !== null &&
1013
+ "content" in rawOutput
1014
+ ? String(rawOutput.content)
1015
+ : JSON.stringify(rawOutput);
963
1016
  // Apply modifications if hook returned them
964
1017
  if (hookResult.modifiedOutput) {
965
1018
  rawOutput = hookResult.modifiedOutput;
@@ -970,6 +1023,49 @@ export class AgentAcpAdapter {
970
1023
  });
971
1024
  }
972
1025
  truncationWarning = hookResult.truncationWarning;
1026
+ // Store hook result metadata in toolCallBlock._meta for persistence
1027
+ if (hookResult.metadata) {
1028
+ if (!toolCallBlock._meta) {
1029
+ toolCallBlock._meta = {};
1030
+ }
1031
+ // Store compaction action and stats
1032
+ if (hookResult.metadata.action) {
1033
+ toolCallBlock._meta.compactionAction = hookResult.metadata
1034
+ .action;
1035
+ }
1036
+ if (hookResult.metadata.originalTokens !== undefined) {
1037
+ toolCallBlock._meta.originalTokens = hookResult.metadata
1038
+ .originalTokens;
1039
+ }
1040
+ if (hookResult.metadata.finalTokens !== undefined) {
1041
+ toolCallBlock._meta.finalTokens = hookResult.metadata
1042
+ .finalTokens;
1043
+ }
1044
+ // Store original content if compaction occurred (action !== "none")
1045
+ if (hookResult.metadata.action &&
1046
+ hookResult.metadata.action !== "none" &&
1047
+ this.storage) {
1048
+ try {
1049
+ const toolName = toolCallBlock.title || "unknown";
1050
+ const originalContentPath = this.storage.saveToolOriginal(params.sessionId, toolName, outputMsg.toolCallId, originalContentStr);
1051
+ toolCallBlock._meta.originalContentPath =
1052
+ originalContentPath;
1053
+ logger.info("Saved original content to artifacts", {
1054
+ toolCallId: outputMsg.toolCallId,
1055
+ toolName,
1056
+ path: originalContentPath,
1057
+ });
1058
+ }
1059
+ catch (error) {
1060
+ logger.warn("Failed to save original tool content", {
1061
+ toolCallId: outputMsg.toolCallId,
1062
+ error: error instanceof Error
1063
+ ? error.message
1064
+ : String(error),
1065
+ });
1066
+ }
1067
+ }
1068
+ }
973
1069
  }
974
1070
  }
975
1071
  // Store the (potentially modified) output
@@ -983,6 +1079,22 @@ export class AgentAcpAdapter {
983
1079
  }
984
1080
  toolCallBlock._meta.truncationWarning = truncationWarning;
985
1081
  }
1082
+ // Send compaction metadata to the client if present
1083
+ // This is needed for live streaming (not just replay)
1084
+ if (toolCallBlock._meta?.compactionAction) {
1085
+ logger.info("Sending compaction metadata to client during live streaming", {
1086
+ toolCallId: outputMsg.toolCallId,
1087
+ _meta: toolCallBlock._meta,
1088
+ });
1089
+ this.connection.sessionUpdate({
1090
+ sessionId: params.sessionId,
1091
+ update: {
1092
+ sessionUpdate: "tool_call_update",
1093
+ toolCallId: outputMsg.toolCallId,
1094
+ _meta: toolCallBlock._meta,
1095
+ },
1096
+ });
1097
+ }
986
1098
  // Note: content blocks are handled by the transport for display
987
1099
  // We store the raw output here for session persistence
988
1100
  // Create mid-turn context snapshot after tool completes
@@ -1058,8 +1170,7 @@ export class AgentAcpAdapter {
1058
1170
  }
1059
1171
  }
1060
1172
  // Calculate context size - tool result is now in the message, but hasn't been sent to LLM yet
1061
- const context_size = calculateContextSize(contextMessages, this.agent.definition.systemPrompt ?? undefined, undefined, // Tool result hasn't been sent to LLM yet, so no new LLM-reported tokens
1062
- this.currentToolOverheadTokens, // Include tool overhead
1173
+ const context_size = calculateContextSize(contextMessages, this.agent.definition.systemPrompt ?? undefined, this.currentToolOverheadTokens, // Include tool overhead
1063
1174
  this.currentMcpOverheadTokens, // Include MCP overhead
1064
1175
  getModelContextWindow(this.agent.definition.model));
1065
1176
  // Create snapshot with a pointer to the partial message (not a full copy!)
@@ -1154,7 +1265,7 @@ export class AgentAcpAdapter {
1154
1265
  iterResult = await generator.next();
1155
1266
  }
1156
1267
  // Capture the return value (PromptResponse with tokenUsage)
1157
- agentResponse = iterResult.value;
1268
+ _agentResponse = iterResult.value;
1158
1269
  // Flush any remaining pending text
1159
1270
  flushPendingText();
1160
1271
  }
@@ -1213,9 +1324,8 @@ export class AgentAcpAdapter {
1213
1324
  contextMessages.push(entry.message);
1214
1325
  }
1215
1326
  }
1216
- // Calculate context size with LLM-reported tokens from this turn
1217
- const context_size = calculateContextSize(contextMessages, this.agent.definition.systemPrompt ?? undefined, turnTokenUsage.inputTokens, // Final LLM-reported tokens from this turn
1218
- this.currentToolOverheadTokens, // Include tool overhead
1327
+ // Calculate context size - only estimated values
1328
+ const context_size = calculateContextSize(contextMessages, this.agent.definition.systemPrompt ?? undefined, this.currentToolOverheadTokens, // Include tool overhead
1219
1329
  this.currentMcpOverheadTokens, // Include MCP overhead
1220
1330
  getModelContextWindow(this.agent.definition.model));
1221
1331
  const contextSnapshot = createContextSnapshot(session.messages.length, new Date().toISOString(), previousContext, context_size);
@@ -1293,28 +1403,19 @@ export class AgentAcpAdapter {
1293
1403
  context: session.context,
1294
1404
  requestParams: session.requestParams,
1295
1405
  };
1296
- // Get actual input token count from latest context entry
1406
+ // Get input token count from latest context entry
1297
1407
  const latestContext = session.context.length > 0
1298
1408
  ? session.context[session.context.length - 1]
1299
1409
  : undefined;
1300
- // Use max of estimated and LLM-reported tokens (same logic as UI)
1301
- // This is conservative - we want to trigger compaction earlier rather than later
1302
- const actualInputTokens = Math.max(latestContext?.context_size.totalEstimated ?? 0, latestContext?.context_size.llmReportedInputTokens ?? 0);
1410
+ // Use estimated tokens for hook evaluation
1411
+ const actualInputTokens = latestContext?.context_size.totalEstimated ?? 0;
1303
1412
  logger.debug("Using tokens for hook execution", {
1304
- llmReported: latestContext?.context_size.llmReportedInputTokens,
1305
1413
  estimated: latestContext?.context_size.totalEstimated,
1306
1414
  used: actualInputTokens,
1307
1415
  });
1308
- // Send the context size to the UI so it shows the same value we're using for hook evaluation
1309
- // This ensures the UI percentage matches what the hook sees
1416
+ // Send the context size to the UI
1310
1417
  if (latestContext?.context_size) {
1311
- // Create an updated context_size with the actualInputTokens we computed
1312
- // so UI can calculate the same percentage
1313
- const contextSizeForUI = {
1314
- ...latestContext.context_size,
1315
- // Override with the max value we computed (what hooks actually use)
1316
- totalEstimated: actualInputTokens,
1317
- };
1418
+ const contextSizeForUI = latestContext.context_size;
1318
1419
  this.connection.sessionUpdate({
1319
1420
  sessionId,
1320
1421
  update: {
@@ -241,7 +241,7 @@ export function makeHttpTransport(agent, agentDir, agentName) {
241
241
  }
242
242
  // Regular session update - send via PubSub
243
243
  const channel = safeChannelName("notifications", msgSessionId);
244
- const { payload, isCompressed, originalSize, compressedSize } = compressIfNeeded(rawMsg);
244
+ const { payload, compressedSize } = compressIfNeeded(rawMsg);
245
245
  if (compressedSize <= 7500) {
246
246
  const escapedPayload = payload.replace(/'/g, "''");
247
247
  try {
@@ -455,6 +455,61 @@ export function makeHttpTransport(agent, agentDir, agentName) {
455
455
  }, 500);
456
456
  }
457
457
  });
458
+ // Serve files from session artifacts folder
459
+ app.get("/sessions/:sessionId/artifacts/*", async (c) => {
460
+ if (!agentDir || !agentName) {
461
+ return c.json({ error: "Session storage not configured" }, 500);
462
+ }
463
+ const noSession = process.env.TOWN_NO_SESSION === "true";
464
+ if (noSession) {
465
+ return c.json({ error: "Sessions disabled" }, 500);
466
+ }
467
+ const sessionId = c.req.param("sessionId");
468
+ // Extract the filename from the wildcard path
469
+ const filename = c.req.path.replace(`/sessions/${sessionId}/artifacts/`, "");
470
+ if (!sessionId || !filename) {
471
+ return c.json({ error: "Session ID and filename required" }, 400);
472
+ }
473
+ // Build path to artifacts folder
474
+ const storage = new SessionStorage(agentDir, agentName);
475
+ const artifactsDir = storage.getArtifactsDir(sessionId);
476
+ const filePath = join(artifactsDir, filename);
477
+ // Security check: ensure the file is within the artifacts directory
478
+ const normalizedPath = resolve(filePath);
479
+ const normalizedArtifactsDir = resolve(artifactsDir);
480
+ if (!normalizedPath.startsWith(normalizedArtifactsDir)) {
481
+ logger.warn("Attempted to access file outside artifacts directory", {
482
+ filename,
483
+ filePath,
484
+ artifactsDir,
485
+ });
486
+ return c.json({ error: "Invalid path" }, 403);
487
+ }
488
+ try {
489
+ const file = Bun.file(filePath);
490
+ const exists = await file.exists();
491
+ if (!exists) {
492
+ logger.warn("Artifact file not found", {
493
+ sessionId,
494
+ filename,
495
+ filePath,
496
+ });
497
+ return c.json({ error: "File not found" }, 404);
498
+ }
499
+ const content = await file.text();
500
+ return c.json({ content });
501
+ }
502
+ catch (error) {
503
+ logger.error("Failed to load artifact", {
504
+ error,
505
+ sessionId,
506
+ filename,
507
+ });
508
+ return c.json({
509
+ error: error instanceof Error ? error.message : String(error),
510
+ }, 500);
511
+ }
512
+ });
458
513
  // Serve static files from agent directory (for generated images, etc.)
459
514
  if (agentDir) {
460
515
  app.get("/static/*", async (c) => {
@@ -66,11 +66,13 @@ export interface ToolCallBlock {
66
66
  startedAt?: number | undefined;
67
67
  completedAt?: number | undefined;
68
68
  _meta?: {
69
- truncationWarning?: string;
70
- compactionAction?: "compacted" | "truncated";
71
- originalTokens?: number;
72
- finalTokens?: number;
73
- };
69
+ truncationWarning?: string | undefined;
70
+ compactionAction?: "compacted" | "truncated" | undefined;
71
+ originalTokens?: number | undefined;
72
+ finalTokens?: number | undefined;
73
+ originalContentPreview?: string | undefined;
74
+ originalContentPath?: string | undefined;
75
+ } | undefined;
74
76
  /** Sub-agent HTTP port (for reference, not used in replay) */
75
77
  subagentPort?: number | undefined;
76
78
  /** Sub-agent session ID (for reference, not used in replay) */
@@ -124,7 +126,6 @@ export interface ContextEntry {
124
126
  toolInputTokens: number;
125
127
  toolResultsTokens: number;
126
128
  totalEstimated: number;
127
- llmReportedInputTokens?: number | undefined;
128
129
  modelContextWindow?: number | undefined;
129
130
  };
130
131
  }
@@ -147,7 +148,8 @@ export interface StoredSession {
147
148
  }
148
149
  /**
149
150
  * File-based session storage
150
- * Stores sessions in agents/<agent-name>/.sessions/<session_id>.json
151
+ * Stores sessions in agents/<agent-name>/.sessions/<session_id>/session.json
152
+ * (Legacy: agents/<agent-name>/.sessions/<session_id>.json)
151
153
  */
152
154
  export declare class SessionStorage {
153
155
  private sessionsDir;
@@ -159,13 +161,18 @@ export declare class SessionStorage {
159
161
  */
160
162
  constructor(agentDir: string, agentName: string);
161
163
  /**
162
- * Ensure the .sessions directory exists
164
+ * Ensure the session directory exists
163
165
  */
164
- private ensureSessionsDir;
166
+ private ensureSessionDir;
165
167
  /**
166
168
  * Get the file path for a session
167
169
  */
168
170
  private getSessionPath;
171
+ /**
172
+ * Get the legacy file path for a session (for backwards compatibility)
173
+ * Legacy format: .sessions/<session_id>.json (flat file, not directory)
174
+ */
175
+ private getLegacySessionPath;
169
176
  /**
170
177
  * Save a session to disk
171
178
  * Uses atomic write (write to temp file, then rename)
@@ -177,18 +184,19 @@ export declare class SessionStorage {
177
184
  loadSession(sessionId: string): Promise<StoredSession | null>;
178
185
  /**
179
186
  * Synchronous session loading (for internal use)
187
+ * Checks new location first, falls back to legacy location
180
188
  */
181
189
  private loadSessionSync;
182
190
  /**
183
- * Check if a session exists
191
+ * Check if a session exists (checks both new and legacy locations)
184
192
  */
185
193
  sessionExists(sessionId: string): boolean;
186
194
  /**
187
- * Delete a session
195
+ * Delete a session (deletes entire session directory or legacy file)
188
196
  */
189
197
  deleteSession(sessionId: string): Promise<boolean>;
190
198
  /**
191
- * List all session IDs
199
+ * List all session IDs (checks both new and legacy locations)
192
200
  */
193
201
  listSessions(): Promise<string[]>;
194
202
  /**
@@ -205,4 +213,28 @@ export declare class SessionStorage {
205
213
  messageCount: number;
206
214
  firstUserMessage?: string;
207
215
  }>>;
216
+ /**
217
+ * Get the directory for storing large content files for a session (artifacts folder)
218
+ */
219
+ getArtifactsDir(sessionId: string): string;
220
+ /**
221
+ * Get the file path for a tool's original content
222
+ * Follows the pattern: artifacts/tool-<ToolName>/<toolCallId>.original.txt
223
+ */
224
+ private getToolOriginalPath;
225
+ /**
226
+ * Save original tool response to a separate file
227
+ * Follows the pattern: artifacts/tool-<ToolName>/<toolCallId>.original.txt
228
+ * @param toolName - The name of the tool (e.g., "Read", "Grep")
229
+ * @returns Relative file path (for storage in _meta.originalContentPath)
230
+ */
231
+ saveToolOriginal(sessionId: string, toolName: string, toolCallId: string, content: string): string;
232
+ /**
233
+ * Load original tool response from separate file
234
+ */
235
+ loadToolOriginal(sessionId: string, toolName: string, toolCallId: string): string | null;
236
+ /**
237
+ * Check if original content exists for a tool call
238
+ */
239
+ hasToolOriginal(sessionId: string, toolName: string, toolCallId: string): boolean;
208
240
  }