@heylemon/lemonade 0.6.7 → 0.7.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.
Files changed (148) hide show
  1. package/dist/agents/model-fallback.js +7 -2
  2. package/dist/agents/pi-tool-definition-adapter.js +83 -10
  3. package/dist/agents/system-prompt.js +20 -1
  4. package/dist/auto-reply/reply/session.js +34 -8
  5. package/dist/build-info.json +3 -3
  6. package/dist/canvas-host/a2ui/.bundle.hash +1 -0
  7. package/dist/canvas-host/a2ui/a2ui.bundle.js +17775 -0
  8. package/dist/canvas-host/a2ui/index.html +307 -0
  9. package/dist/control-ui/assets/{index-0MsyRFLD.js → index-D5pbk0Cx.js} +261 -261
  10. package/dist/control-ui/assets/index-D5pbk0Cx.js.map +1 -0
  11. package/dist/control-ui/index.html +1 -1
  12. package/dist/gateway/auth-rate-limit.js +121 -0
  13. package/dist/gateway/auth.js +26 -8
  14. package/dist/gateway/server-http.js +7 -4
  15. package/dist/gateway/session-utils.fs.js +18 -0
  16. package/dist/gateway/skills-http.js +65 -0
  17. package/dist/security/secret-equal.js +13 -0
  18. package/dist/web/auto-reply/monitor.js +10 -2
  19. package/dist/web/login-qr.js +12 -0
  20. package/extensions/diagnostics-otel/node_modules/.bin/acorn +2 -2
  21. package/extensions/googlechat/node_modules/.bin/lemonade +2 -2
  22. package/extensions/line/node_modules/.bin/lemonade +2 -2
  23. package/extensions/matrix/node_modules/.bin/lemonade +2 -2
  24. package/extensions/matrix/node_modules/.bin/markdown-it +2 -2
  25. package/extensions/memory-core/node_modules/.bin/lemonade +2 -2
  26. package/extensions/memory-lancedb/node_modules/.bin/arrow2csv +2 -2
  27. package/extensions/memory-lancedb/node_modules/.bin/openai +2 -2
  28. package/extensions/msteams/node_modules/.bin/lemonade +2 -2
  29. package/extensions/nostr/node_modules/.bin/lemonade +2 -2
  30. package/extensions/nostr/node_modules/.bin/tsc +2 -2
  31. package/extensions/nostr/node_modules/.bin/tsserver +2 -2
  32. package/extensions/twitch/node_modules/.bin/lemonade +2 -2
  33. package/extensions/zalo/node_modules/.bin/lemonade +2 -2
  34. package/extensions/zalouser/node_modules/.bin/lemonade +2 -2
  35. package/package.json +1 -1
  36. package/skills/sherpa-onnx-tts/bin/sherpa-onnx-tts +178 -0
  37. package/skills/travel-agent-persona/SKILL.md +89 -0
  38. package/dist/control-ui/assets/index-0MsyRFLD.js.map +0 -1
  39. package/extensions/googlechat/node_modules/.bin/lemon-calendar +0 -21
  40. package/extensions/googlechat/node_modules/.bin/lemon-cron +0 -21
  41. package/extensions/googlechat/node_modules/.bin/lemon-docs +0 -21
  42. package/extensions/googlechat/node_modules/.bin/lemon-drive +0 -21
  43. package/extensions/googlechat/node_modules/.bin/lemon-gmail +0 -21
  44. package/extensions/googlechat/node_modules/.bin/lemon-jira +0 -21
  45. package/extensions/googlechat/node_modules/.bin/lemon-notion +0 -21
  46. package/extensions/googlechat/node_modules/.bin/lemon-sheets +0 -21
  47. package/extensions/googlechat/node_modules/.bin/lemon-slack +0 -21
  48. package/extensions/googlechat/node_modules/.bin/lemon-slides +0 -21
  49. package/extensions/googlechat/node_modules/.bin/lemon-twitter +0 -21
  50. package/extensions/googlechat/node_modules/.bin/lemon-youtube +0 -21
  51. package/extensions/line/node_modules/.bin/lemon-calendar +0 -21
  52. package/extensions/line/node_modules/.bin/lemon-cron +0 -21
  53. package/extensions/line/node_modules/.bin/lemon-docs +0 -21
  54. package/extensions/line/node_modules/.bin/lemon-drive +0 -21
  55. package/extensions/line/node_modules/.bin/lemon-gmail +0 -21
  56. package/extensions/line/node_modules/.bin/lemon-jira +0 -21
  57. package/extensions/line/node_modules/.bin/lemon-notion +0 -21
  58. package/extensions/line/node_modules/.bin/lemon-sheets +0 -21
  59. package/extensions/line/node_modules/.bin/lemon-slack +0 -21
  60. package/extensions/line/node_modules/.bin/lemon-slides +0 -21
  61. package/extensions/line/node_modules/.bin/lemon-twitter +0 -21
  62. package/extensions/line/node_modules/.bin/lemon-youtube +0 -21
  63. package/extensions/matrix/node_modules/.bin/lemon-calendar +0 -21
  64. package/extensions/matrix/node_modules/.bin/lemon-cron +0 -21
  65. package/extensions/matrix/node_modules/.bin/lemon-docs +0 -21
  66. package/extensions/matrix/node_modules/.bin/lemon-drive +0 -21
  67. package/extensions/matrix/node_modules/.bin/lemon-gmail +0 -21
  68. package/extensions/matrix/node_modules/.bin/lemon-jira +0 -21
  69. package/extensions/matrix/node_modules/.bin/lemon-notion +0 -21
  70. package/extensions/matrix/node_modules/.bin/lemon-sheets +0 -21
  71. package/extensions/matrix/node_modules/.bin/lemon-slack +0 -21
  72. package/extensions/matrix/node_modules/.bin/lemon-slides +0 -21
  73. package/extensions/matrix/node_modules/.bin/lemon-twitter +0 -21
  74. package/extensions/matrix/node_modules/.bin/lemon-youtube +0 -21
  75. package/extensions/memory-core/node_modules/.bin/lemon-calendar +0 -21
  76. package/extensions/memory-core/node_modules/.bin/lemon-cron +0 -21
  77. package/extensions/memory-core/node_modules/.bin/lemon-docs +0 -21
  78. package/extensions/memory-core/node_modules/.bin/lemon-drive +0 -21
  79. package/extensions/memory-core/node_modules/.bin/lemon-gmail +0 -21
  80. package/extensions/memory-core/node_modules/.bin/lemon-jira +0 -21
  81. package/extensions/memory-core/node_modules/.bin/lemon-notion +0 -21
  82. package/extensions/memory-core/node_modules/.bin/lemon-sheets +0 -21
  83. package/extensions/memory-core/node_modules/.bin/lemon-slack +0 -21
  84. package/extensions/memory-core/node_modules/.bin/lemon-slides +0 -21
  85. package/extensions/memory-core/node_modules/.bin/lemon-twitter +0 -21
  86. package/extensions/memory-core/node_modules/.bin/lemon-youtube +0 -21
  87. package/extensions/msteams/node_modules/.bin/lemon-calendar +0 -21
  88. package/extensions/msteams/node_modules/.bin/lemon-cron +0 -21
  89. package/extensions/msteams/node_modules/.bin/lemon-docs +0 -21
  90. package/extensions/msteams/node_modules/.bin/lemon-drive +0 -21
  91. package/extensions/msteams/node_modules/.bin/lemon-gmail +0 -21
  92. package/extensions/msteams/node_modules/.bin/lemon-jira +0 -21
  93. package/extensions/msteams/node_modules/.bin/lemon-notion +0 -21
  94. package/extensions/msteams/node_modules/.bin/lemon-sheets +0 -21
  95. package/extensions/msteams/node_modules/.bin/lemon-slack +0 -21
  96. package/extensions/msteams/node_modules/.bin/lemon-slides +0 -21
  97. package/extensions/msteams/node_modules/.bin/lemon-twitter +0 -21
  98. package/extensions/msteams/node_modules/.bin/lemon-youtube +0 -21
  99. package/extensions/nostr/node_modules/.bin/lemon-calendar +0 -21
  100. package/extensions/nostr/node_modules/.bin/lemon-cron +0 -21
  101. package/extensions/nostr/node_modules/.bin/lemon-docs +0 -21
  102. package/extensions/nostr/node_modules/.bin/lemon-drive +0 -21
  103. package/extensions/nostr/node_modules/.bin/lemon-gmail +0 -21
  104. package/extensions/nostr/node_modules/.bin/lemon-jira +0 -21
  105. package/extensions/nostr/node_modules/.bin/lemon-notion +0 -21
  106. package/extensions/nostr/node_modules/.bin/lemon-sheets +0 -21
  107. package/extensions/nostr/node_modules/.bin/lemon-slack +0 -21
  108. package/extensions/nostr/node_modules/.bin/lemon-slides +0 -21
  109. package/extensions/nostr/node_modules/.bin/lemon-twitter +0 -21
  110. package/extensions/nostr/node_modules/.bin/lemon-youtube +0 -21
  111. package/extensions/twitch/node_modules/.bin/lemon-calendar +0 -21
  112. package/extensions/twitch/node_modules/.bin/lemon-cron +0 -21
  113. package/extensions/twitch/node_modules/.bin/lemon-docs +0 -21
  114. package/extensions/twitch/node_modules/.bin/lemon-drive +0 -21
  115. package/extensions/twitch/node_modules/.bin/lemon-gmail +0 -21
  116. package/extensions/twitch/node_modules/.bin/lemon-jira +0 -21
  117. package/extensions/twitch/node_modules/.bin/lemon-notion +0 -21
  118. package/extensions/twitch/node_modules/.bin/lemon-sheets +0 -21
  119. package/extensions/twitch/node_modules/.bin/lemon-slack +0 -21
  120. package/extensions/twitch/node_modules/.bin/lemon-slides +0 -21
  121. package/extensions/twitch/node_modules/.bin/lemon-twitter +0 -21
  122. package/extensions/twitch/node_modules/.bin/lemon-youtube +0 -21
  123. package/extensions/zalo/node_modules/.bin/lemon-calendar +0 -21
  124. package/extensions/zalo/node_modules/.bin/lemon-cron +0 -21
  125. package/extensions/zalo/node_modules/.bin/lemon-docs +0 -21
  126. package/extensions/zalo/node_modules/.bin/lemon-drive +0 -21
  127. package/extensions/zalo/node_modules/.bin/lemon-gmail +0 -21
  128. package/extensions/zalo/node_modules/.bin/lemon-jira +0 -21
  129. package/extensions/zalo/node_modules/.bin/lemon-notion +0 -21
  130. package/extensions/zalo/node_modules/.bin/lemon-sheets +0 -21
  131. package/extensions/zalo/node_modules/.bin/lemon-slack +0 -21
  132. package/extensions/zalo/node_modules/.bin/lemon-slides +0 -21
  133. package/extensions/zalo/node_modules/.bin/lemon-twitter +0 -21
  134. package/extensions/zalo/node_modules/.bin/lemon-youtube +0 -21
  135. package/extensions/zalouser/node_modules/.bin/lemon-calendar +0 -21
  136. package/extensions/zalouser/node_modules/.bin/lemon-cron +0 -21
  137. package/extensions/zalouser/node_modules/.bin/lemon-docs +0 -21
  138. package/extensions/zalouser/node_modules/.bin/lemon-drive +0 -21
  139. package/extensions/zalouser/node_modules/.bin/lemon-gmail +0 -21
  140. package/extensions/zalouser/node_modules/.bin/lemon-jira +0 -21
  141. package/extensions/zalouser/node_modules/.bin/lemon-notion +0 -21
  142. package/extensions/zalouser/node_modules/.bin/lemon-sheets +0 -21
  143. package/extensions/zalouser/node_modules/.bin/lemon-slack +0 -21
  144. package/extensions/zalouser/node_modules/.bin/lemon-slides +0 -21
  145. package/extensions/zalouser/node_modules/.bin/lemon-twitter +0 -21
  146. package/extensions/zalouser/node_modules/.bin/lemon-youtube +0 -21
  147. /package/skills/pdf/{FORMS.md → forms.md} +0 -0
  148. /package/skills/pdf/{REFERENCE.md → reference.md} +0 -0
@@ -1,6 +1,7 @@
1
1
  import { DEFAULT_MODEL, DEFAULT_PROVIDER } from "./defaults.js";
2
2
  import { coerceToFailoverError, describeFailoverError, isFailoverError, isTimeoutError, } from "./failover-error.js";
3
3
  import { buildModelAliasIndex, modelKey, parseModelRef, resolveConfiguredModelRef, resolveModelRefFromString, } from "./model-selection.js";
4
+ import { isLikelyContextOverflowError } from "./pi-embedded-helpers.js";
4
5
  import { ensureAuthProfileStore, isProfileInCooldown, resolveAuthProfileOrder, } from "./auth-profiles.js";
5
6
  function isAbortError(err) {
6
7
  if (!err || typeof err !== "object")
@@ -179,13 +180,17 @@ export async function runWithModelFallback(params) {
179
180
  catch (err) {
180
181
  if (shouldRethrowAbort(err))
181
182
  throw err;
183
+ const errMessage = err instanceof Error ? err.message : String(err);
184
+ if (isLikelyContextOverflowError(errMessage))
185
+ throw err;
182
186
  const normalized = coerceToFailoverError(err, {
183
187
  provider: candidate.provider,
184
188
  model: candidate.model,
185
189
  }) ?? err;
186
- if (!isFailoverError(normalized))
190
+ const isKnownFailover = isFailoverError(normalized);
191
+ if (!isKnownFailover && i === candidates.length - 1)
187
192
  throw err;
188
- lastError = normalized;
193
+ lastError = isKnownFailover ? normalized : err;
189
194
  const described = describeFailoverError(normalized);
190
195
  attempts.push({
191
196
  provider: candidate.provider,
@@ -1,6 +1,10 @@
1
1
  import { logDebug, logError } from "../logger.js";
2
+ import { getGlobalHookRunner } from "../plugins/hook-runner-global.js";
2
3
  import { normalizeToolName } from "./tool-policy.js";
3
4
  import { jsonResult } from "./tools/common.js";
5
+ function isPlainObject(value) {
6
+ return typeof value === "object" && value !== null && !Array.isArray(value);
7
+ }
4
8
  function describeToolExecutionError(err) {
5
9
  if (err instanceof Error) {
6
10
  const message = err.message?.trim() ? err.message : String(err);
@@ -8,6 +12,26 @@ function describeToolExecutionError(err) {
8
12
  }
9
13
  return { message: String(err) };
10
14
  }
15
+ function normalizeToolExecutionResult(params) {
16
+ const { toolName, result } = params;
17
+ if (result && typeof result === "object") {
18
+ const record = result;
19
+ if (Array.isArray(record.content)) {
20
+ return result;
21
+ }
22
+ const details = "details" in record ? record.details : record;
23
+ const safeDetails = details ?? { status: "ok", tool: toolName };
24
+ return {
25
+ content: [{ type: "text", text: JSON.stringify(safeDetails, null, 2) }],
26
+ details: safeDetails,
27
+ };
28
+ }
29
+ const safeDetails = result ?? { status: "ok", tool: toolName };
30
+ return {
31
+ content: [{ type: "text", text: JSON.stringify(safeDetails, null, 2) }],
32
+ details: safeDetails,
33
+ };
34
+ }
11
35
  export function toToolDefinitions(tools) {
12
36
  return tools.map((tool) => {
13
37
  const name = tool.name || "tool";
@@ -19,36 +43,87 @@ export function toToolDefinitions(tools) {
19
43
  // biome-ignore lint/suspicious/noExplicitAny: TypeBox schema from pi-agent-core uses a different module instance.
20
44
  parameters: tool.parameters,
21
45
  execute: async (toolCallId, params, onUpdate, _ctx, signal) => {
22
- // KNOWN: pi-coding-agent `ToolDefinition.execute` has a different signature/order
23
- // than pi-agent-core `AgentTool.execute`. This adapter keeps our existing tools intact.
46
+ let executeParams = params;
24
47
  try {
25
- return await tool.execute(toolCallId, params, signal, onUpdate);
48
+ const hookRunner = getGlobalHookRunner();
49
+ if (hookRunner?.hasHooks("before_tool_call")) {
50
+ try {
51
+ const hookResult = await hookRunner.runBeforeToolCall({
52
+ toolName: name,
53
+ params: isPlainObject(params) ? params : {},
54
+ }, { toolName: name });
55
+ if (hookResult?.block) {
56
+ throw new Error(hookResult.blockReason || "Tool call blocked by plugin hook");
57
+ }
58
+ if (hookResult?.params && isPlainObject(hookResult.params)) {
59
+ executeParams = isPlainObject(params)
60
+ ? { ...params, ...hookResult.params }
61
+ : hookResult.params;
62
+ }
63
+ }
64
+ catch (hookErr) {
65
+ if (hookErr instanceof Error && hookErr.message.includes("blocked by plugin hook")) {
66
+ throw hookErr;
67
+ }
68
+ logDebug(`before_tool_call hook failed: tool=${normalizedName} error=${String(hookErr)}`);
69
+ }
70
+ }
71
+ const rawResult = await tool.execute(toolCallId, executeParams, signal, onUpdate);
72
+ const result = normalizeToolExecutionResult({
73
+ toolName: normalizedName,
74
+ result: rawResult,
75
+ });
76
+ if (hookRunner?.hasHooks("after_tool_call")) {
77
+ try {
78
+ await hookRunner.runAfterToolCall({
79
+ toolName: name,
80
+ params: isPlainObject(executeParams) ? executeParams : {},
81
+ result,
82
+ }, { toolName: name });
83
+ }
84
+ catch (hookErr) {
85
+ logDebug(`after_tool_call hook failed: tool=${normalizedName} error=${String(hookErr)}`);
86
+ }
87
+ }
88
+ return result;
26
89
  }
27
90
  catch (err) {
28
91
  if (signal?.aborted)
29
92
  throw err;
30
- const name = err && typeof err === "object" && "name" in err
93
+ const errName = err && typeof err === "object" && "name" in err
31
94
  ? String(err.name)
32
95
  : "";
33
- if (name === "AbortError")
96
+ if (errName === "AbortError")
34
97
  throw err;
35
98
  const described = describeToolExecutionError(err);
36
99
  if (described.stack && described.stack !== described.message) {
37
100
  logDebug(`tools: ${normalizedName} failed stack:\n${described.stack}`);
38
101
  }
39
102
  logError(`[tools] ${normalizedName} failed: ${described.message}`);
40
- return jsonResult({
103
+ const errorResult = jsonResult({
41
104
  status: "error",
42
105
  tool: normalizedName,
43
106
  error: described.message,
44
107
  });
108
+ const hookRunner = getGlobalHookRunner();
109
+ if (hookRunner?.hasHooks("after_tool_call")) {
110
+ try {
111
+ await hookRunner.runAfterToolCall({
112
+ toolName: normalizedName,
113
+ params: isPlainObject(params) ? params : {},
114
+ error: described.message,
115
+ }, { toolName: normalizedName });
116
+ }
117
+ catch (hookErr) {
118
+ logDebug(`after_tool_call hook failed: tool=${normalizedName} error=${String(hookErr)}`);
119
+ }
120
+ }
121
+ return errorResult;
45
122
  }
46
123
  },
47
124
  };
48
125
  });
49
126
  }
50
- // Convert client tools (OpenResponses hosted tools) to ToolDefinition format
51
- // These tools are intercepted to return a "pending" result instead of executing
52
127
  export function toClientToolDefinitions(tools, onClientToolCall) {
53
128
  return tools.map((tool) => {
54
129
  const func = tool.function;
@@ -58,11 +133,9 @@ export function toClientToolDefinitions(tools, onClientToolCall) {
58
133
  description: func.description ?? "",
59
134
  parameters: func.parameters,
60
135
  execute: async (toolCallId, params, _onUpdate, _ctx, _signal) => {
61
- // Notify handler that a client tool was called
62
136
  if (onClientToolCall) {
63
137
  onClientToolCall(func.name, params);
64
138
  }
65
- // Return a pending result - the client will execute this tool
66
139
  return jsonResult({
67
140
  status: "pending",
68
141
  tool: func.name,
@@ -347,7 +347,24 @@ export function buildAgentSystemPrompt(params) {
347
347
  "Never reply with a list of approaches/options when a single tool call would suffice.",
348
348
  "CRITICAL: NEVER stop after announcing what you're going to do. Do NOT say 'I'll check your LinkedIn' and then stop. Say it AND immediately do it in the same response — search, open browser, navigate, find the answer, and return it. The user should never have to ask twice. One request = one complete answer.",
349
349
  "",
350
+ "## Native macOS Apps & Optional CLIs",
351
+ "By default, use built-in macOS apps for common tasks:",
352
+ "- **Reminders** → use the macOS Reminders app via AppleScript (`osascript`). Do NOT require any CLI tool.",
353
+ "- **Calendar** → use `lemon-calendar` (Google Calendar). For local macOS Calendar, use AppleScript.",
354
+ "- **Notes** → use Apple Notes via AppleScript.",
355
+ "",
356
+ "Optional third-party app CLIs (Things, Obsidian, Bear Notes) should ONLY be installed when the user explicitly mentions those apps by name:",
357
+ '- User says "set a reminder" → use macOS Reminders app. Done.',
358
+ '- User says "add this to Things" → silently install `things-cli` via brew in the background (`brew install things-cli 2>/dev/null`), then use it. No warning, no asking.',
359
+ '- User says "save this to Obsidian" → silently install `obsidian-cli` via brew (`brew install obsidian-cli 2>/dev/null`), then use it.',
360
+ '- User says "add a note in Bear" → silently install `grizzly` via brew (`brew install grizzly 2>/dev/null`), then use it.',
361
+ "",
362
+ "Internal CLI tools (summarize, blogwatcher, gemini-cli, ffmpeg, etc.) are automatically installed when the user activates a persona/skill pack. Assume they are available — do NOT try to install them yourself or warn the user about them.",
363
+ "",
364
+ "CRITICAL: NEVER show install warnings or ask the user to install CLIs upfront. The user does NOT need to install anything for basic functionality. Only install app-specific CLIs when a specific third-party app is mentioned, and do it silently as part of completing the task.",
365
+ "",
350
366
  "## Screenshots",
367
+ "CRITICAL: When asked to take a screenshot, ALWAYS take one. NEVER say you can't. NEVER tell the user to press Cmd+Shift+3/4 themselves. NEVER try to use the browser for screenshots of native windows.",
351
368
  'Endpoint: `curl -s "http://127.0.0.1:19848/screenshot?name=SHORT_NAME"`',
352
369
  'Returns: `{"path": "/path/to/file.jpg", "mode": "window"}`',
353
370
  "",
@@ -360,6 +377,7 @@ export function buildAgentSystemPrompt(params) {
360
377
  "If user refers to a past screenshot and the path is no longer in conversation, run `ls ~/.lemonade/tmp/screenshots/` to find it by name.",
361
378
  "",
362
379
  "Usage: returned path works with `message` tool (send via WhatsApp/Slack) and `image` tool (analyze).",
380
+ "Fallback: if the screenshot endpoint is unavailable, use `screencapture -x /tmp/screenshot.png` directly.",
363
381
  "",
364
382
  "## Third-Party App Requests (Trello, Jira, LinkedIn, Asana, HubSpot, Salesforce, Todoist, etc.)",
365
383
  "",
@@ -550,7 +568,8 @@ export function buildAgentSystemPrompt(params) {
550
568
  'If a capability is unavailable, say something like "I can\'t do that right now" or suggest an alternative. Never ask the user to configure keys, edit config files, or troubleshoot infrastructure.',
551
569
  "",
552
570
  "## Cron / Reminders",
553
- "ALWAYS use the `cron` tool (action: add/update/remove/list/run/status) for managing cron jobs and reminders.",
571
+ "For reminders: use the `cron` tool by default. If the user wants a native macOS Reminder, use AppleScript with the Reminders app. If the user mentions Things specifically, silently install things-cli (`brew install things-cli 2>/dev/null`) and use it.",
572
+ "ALWAYS use the `cron` tool (action: add/update/remove/list/run/status) for managing cron jobs and scheduled tasks.",
554
573
  "NEVER use `exec` or shell commands to read/write cron files (e.g. ~/.lemonade/cron/jobs.json) directly — the gateway cron service will not detect the change.",
555
574
  "",
556
575
  "### Choosing sessionTarget and payload kind",
@@ -11,6 +11,15 @@ import { stripMentions, stripStructuralPrefixes } from "./mentions.js";
11
11
  import { formatInboundBodyWithSenderMeta } from "./inbound-sender-meta.js";
12
12
  import { normalizeInboundTextNewlines } from "./inbound-text.js";
13
13
  import { normalizeSessionDeliveryFields } from "../../utils/delivery-context.js";
14
+ import { archiveSessionTranscripts } from "../../gateway/session-utils.fs.js";
15
+ const DEFAULT_PARENT_FORK_MAX_TOKENS = 100_000;
16
+ function resolveParentForkMaxTokens(cfg) {
17
+ const configured = cfg.session?.parentForkMaxTokens;
18
+ if (typeof configured === "number" && Number.isFinite(configured) && configured >= 0) {
19
+ return Math.floor(configured);
20
+ }
21
+ return DEFAULT_PARENT_FORK_MAX_TOKENS;
22
+ }
14
23
  function forkSessionFromParent(params) {
15
24
  const parentSessionFile = resolveSessionFilePath(params.parentEntry.sessionId, params.parentEntry);
16
25
  if (!parentSessionFile || !fs.existsSync(parentSessionFile))
@@ -62,8 +71,11 @@ export async function initSessionState(params) {
62
71
  ? sessionCfg.resetTriggers
63
72
  : DEFAULT_RESET_TRIGGERS;
64
73
  const sessionScope = sessionCfg?.scope ?? "per-sender";
74
+ const parentForkMaxTokens = resolveParentForkMaxTokens(cfg);
65
75
  const storePath = resolveStorePath(sessionCfg?.store, { agentId });
66
- const sessionStore = loadSessionStore(storePath);
76
+ const sessionStore = loadSessionStore(storePath, {
77
+ skipCache: true,
78
+ });
67
79
  let sessionKey;
68
80
  let sessionEntry;
69
81
  let sessionId;
@@ -129,6 +141,15 @@ export async function initSessionState(params) {
129
141
  sessionKey = resolveSessionKey(sessionScope, sessionCtxForState, mainKey);
130
142
  const entry = sessionStore[sessionKey];
131
143
  const previousSessionEntry = resetTriggered && entry ? { ...entry } : undefined;
144
+ if (previousSessionEntry?.sessionId) {
145
+ archiveSessionTranscripts({
146
+ sessionId: previousSessionEntry.sessionId,
147
+ storePath,
148
+ sessionFile: previousSessionEntry.sessionFile,
149
+ agentId,
150
+ reason: "reset",
151
+ });
152
+ }
132
153
  const now = Date.now();
133
154
  const isThread = resolveThreadFlag({
134
155
  sessionKey,
@@ -242,13 +263,18 @@ export async function initSessionState(params) {
242
263
  parentSessionKey &&
243
264
  parentSessionKey !== sessionKey &&
244
265
  sessionStore[parentSessionKey]) {
245
- const forked = forkSessionFromParent({
246
- parentEntry: sessionStore[parentSessionKey],
247
- });
248
- if (forked) {
249
- sessionId = forked.sessionId;
250
- sessionEntry.sessionId = forked.sessionId;
251
- sessionEntry.sessionFile = forked.sessionFile;
266
+ const parentEntry = sessionStore[parentSessionKey];
267
+ const parentTokens = parentEntry.totalTokens ?? 0;
268
+ if (parentForkMaxTokens > 0 && parentTokens > parentForkMaxTokens) {
269
+ sessionEntry.forkedFromParent = true;
270
+ }
271
+ else {
272
+ const forked = forkSessionFromParent({ parentEntry });
273
+ if (forked) {
274
+ sessionId = forked.sessionId;
275
+ sessionEntry.sessionId = forked.sessionId;
276
+ sessionEntry.sessionFile = forked.sessionFile;
277
+ }
252
278
  }
253
279
  }
254
280
  if (!sessionEntry.sessionFile) {
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.6.7",
3
- "commit": "2965fd60321e809102f7cf3b40bb4a8bb6e009cb",
4
- "builtAt": "2026-02-28T10:36:45.544Z"
2
+ "version": "0.7.0",
3
+ "commit": "105eba9ae37816020018df1fa07a598f417517a7",
4
+ "builtAt": "2026-03-02T05:54:52.350Z"
5
5
  }
@@ -0,0 +1 @@
1
+ 155aed5143b97b9dd8e4548c7aa0a4bbde8088ee944fe179bb596cc327b75752