nodal-agents 0.4.3 → 0.4.4

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 (129) hide show
  1. package/README.md +8 -4
  2. package/package.json +1 -1
  3. package/runner.js +157 -51
  4. package/web/.next/BUILD_ID +1 -1
  5. package/web/.next/app-path-routes-manifest.json +1 -1
  6. package/web/.next/build-manifest.json +2 -2
  7. package/web/.next/prerender-manifest.json +3 -3
  8. package/web/.next/server/app/(dashboard)/agents/[id]/edit/page.js +3 -3
  9. package/web/.next/server/app/(dashboard)/agents/[id]/edit/page.js.nft.json +1 -1
  10. package/web/.next/server/app/(dashboard)/agents/[id]/edit/page_client-reference-manifest.js +1 -1
  11. package/web/.next/server/app/(dashboard)/agents/[id]/telegram/page.js +2 -2
  12. package/web/.next/server/app/(dashboard)/agents/[id]/telegram/page_client-reference-manifest.js +1 -1
  13. package/web/.next/server/app/(dashboard)/agents/page.js +2 -2
  14. package/web/.next/server/app/(dashboard)/agents/page_client-reference-manifest.js +1 -1
  15. package/web/.next/server/app/(dashboard)/approvals/page.js +2 -2
  16. package/web/.next/server/app/(dashboard)/approvals/page.js.nft.json +1 -1
  17. package/web/.next/server/app/(dashboard)/approvals/page_client-reference-manifest.js +1 -1
  18. package/web/.next/server/app/(dashboard)/automations/page.js +2 -2
  19. package/web/.next/server/app/(dashboard)/automations/page_client-reference-manifest.js +1 -1
  20. package/web/.next/server/app/(dashboard)/billing/page.js +2 -2
  21. package/web/.next/server/app/(dashboard)/billing/page_client-reference-manifest.js +1 -1
  22. package/web/.next/server/app/(dashboard)/chat/page.js +1 -1
  23. package/web/.next/server/app/(dashboard)/chat/page_client-reference-manifest.js +1 -1
  24. package/web/.next/server/app/(dashboard)/connectors/page.js +1 -1
  25. package/web/.next/server/app/(dashboard)/connectors/page.js.nft.json +1 -1
  26. package/web/.next/server/app/(dashboard)/connectors/page_client-reference-manifest.js +1 -1
  27. package/web/.next/server/app/(dashboard)/credentials/page.js +1 -1
  28. package/web/.next/server/app/(dashboard)/credentials/page.js.nft.json +1 -1
  29. package/web/.next/server/app/(dashboard)/credentials/page_client-reference-manifest.js +1 -1
  30. package/web/.next/server/app/(dashboard)/jobs/[id]/page.js +2 -2
  31. package/web/.next/server/app/(dashboard)/jobs/[id]/page_client-reference-manifest.js +1 -1
  32. package/web/.next/server/app/(dashboard)/jobs/page.js +2 -2
  33. package/web/.next/server/app/(dashboard)/jobs/page.js.nft.json +1 -1
  34. package/web/.next/server/app/(dashboard)/jobs/page_client-reference-manifest.js +1 -1
  35. package/web/.next/server/app/(dashboard)/llm-providers/page.js +2 -2
  36. package/web/.next/server/app/(dashboard)/llm-providers/page_client-reference-manifest.js +1 -1
  37. package/web/.next/server/app/(dashboard)/logs/page.js +2 -2
  38. package/web/.next/server/app/(dashboard)/logs/page_client-reference-manifest.js +1 -1
  39. package/web/.next/server/app/(dashboard)/mcp/page.js +1 -1
  40. package/web/.next/server/app/(dashboard)/mcp/page_client-reference-manifest.js +1 -1
  41. package/web/.next/server/app/(dashboard)/memories/page.js +2 -2
  42. package/web/.next/server/app/(dashboard)/memories/page_client-reference-manifest.js +1 -1
  43. package/web/.next/server/app/(dashboard)/page.js +3 -3
  44. package/web/.next/server/app/(dashboard)/page.js.nft.json +1 -1
  45. package/web/.next/server/app/(dashboard)/page_client-reference-manifest.js +1 -1
  46. package/web/.next/server/app/(dashboard)/settings/page.js +1 -1
  47. package/web/.next/server/app/(dashboard)/settings/page_client-reference-manifest.js +1 -1
  48. package/web/.next/server/app/(dashboard)/skills/[id]/edit/page.js +1 -1
  49. package/web/.next/server/app/(dashboard)/skills/[id]/edit/page.js.nft.json +1 -1
  50. package/web/.next/server/app/(dashboard)/skills/[id]/edit/page_client-reference-manifest.js +1 -1
  51. package/web/.next/server/app/(dashboard)/skills/new/page.js +2 -2
  52. package/web/.next/server/app/(dashboard)/skills/new/page.js.nft.json +1 -1
  53. package/web/.next/server/app/(dashboard)/skills/new/page_client-reference-manifest.js +1 -1
  54. package/web/.next/server/app/(dashboard)/skills/page.js +1 -1
  55. package/web/.next/server/app/(dashboard)/skills/page_client-reference-manifest.js +1 -1
  56. package/web/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  57. package/web/.next/server/app/_global-error.html +1 -1
  58. package/web/.next/server/app/_global-error.rsc +2 -2
  59. package/web/.next/server/app/_global-error.segments/_full.segment.rsc +2 -2
  60. package/web/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  61. package/web/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  62. package/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  63. package/web/.next/server/app/_global-error.segments/_index.segment.rsc +2 -2
  64. package/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  65. package/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  66. package/web/.next/server/app/_not-found.html +1 -1
  67. package/web/.next/server/app/_not-found.rsc +2 -2
  68. package/web/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
  69. package/web/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  70. package/web/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
  71. package/web/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
  72. package/web/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  73. package/web/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  74. package/web/.next/server/app/api/oauth/[provider]/callback/route.js +1 -1
  75. package/web/.next/server/app/login/page_client-reference-manifest.js +1 -1
  76. package/web/.next/server/app/onboarding/page_client-reference-manifest.js +1 -1
  77. package/web/.next/server/app/onboarding.html +1 -1
  78. package/web/.next/server/app/onboarding.rsc +2 -2
  79. package/web/.next/server/app/onboarding.segments/_full.segment.rsc +2 -2
  80. package/web/.next/server/app/onboarding.segments/_head.segment.rsc +1 -1
  81. package/web/.next/server/app/onboarding.segments/_index.segment.rsc +2 -2
  82. package/web/.next/server/app/onboarding.segments/_tree.segment.rsc +1 -1
  83. package/web/.next/server/app/onboarding.segments/onboarding/__PAGE__.segment.rsc +1 -1
  84. package/web/.next/server/app/onboarding.segments/onboarding.segment.rsc +1 -1
  85. package/web/.next/server/app-paths-manifest.json +1 -1
  86. package/web/.next/server/chunks/3233.js +1 -1
  87. package/web/.next/server/chunks/{8766.js → 3889.js} +1 -1
  88. package/web/.next/server/chunks/5329.js +1 -0
  89. package/web/.next/server/chunks/593.js +1 -0
  90. package/web/.next/server/chunks/7231.js +1 -0
  91. package/web/.next/server/chunks/7741.js +1 -1
  92. package/web/.next/server/middleware-build-manifest.js +1 -1
  93. package/web/.next/server/pages/404.html +1 -1
  94. package/web/.next/server/pages/500.html +1 -1
  95. package/web/.next/server/server-reference-manifest.js +1 -1
  96. package/web/.next/server/server-reference-manifest.json +1 -1
  97. package/web/.next/static/chunks/{9123-5c5ad180c831baa4.js → 9123-67530ba510c58003.js} +1 -1
  98. package/web/.next/static/chunks/app/(dashboard)/agents/[id]/edit/page-a2c267f563cd1a70.js +2 -0
  99. package/web/.next/static/chunks/app/(dashboard)/agents/[id]/telegram/page-2d7cae43f5e6952a.js +1 -0
  100. package/web/.next/static/chunks/app/(dashboard)/agents/{page-50005050a3304bee.js → page-bed833ebdd3646fb.js} +1 -1
  101. package/web/.next/static/chunks/app/(dashboard)/approvals/{page-7f4314908d1024f6.js → page-4b27762472802ce0.js} +1 -1
  102. package/web/.next/static/chunks/app/(dashboard)/automations/page-9e5d2ce807ce1c37.js +1 -0
  103. package/web/.next/static/chunks/app/(dashboard)/chat/page-8f81d7e3abb475a6.js +1 -0
  104. package/web/.next/static/chunks/app/(dashboard)/connectors/{page-a6a1d8f0a33d2faf.js → page-39b1d9560235d574.js} +1 -1
  105. package/web/.next/static/chunks/app/(dashboard)/jobs/[id]/page-112bb22a9ec43be0.js +1 -0
  106. package/web/.next/static/chunks/app/(dashboard)/jobs/page-44351170ed5f9180.js +1 -0
  107. package/web/.next/static/chunks/app/(dashboard)/{layout-769de8a52528194a.js → layout-53e69de869cba0d0.js} +1 -1
  108. package/web/.next/static/chunks/app/(dashboard)/llm-providers/page-8f6ce47ba228e38b.js +1 -0
  109. package/web/.next/static/chunks/app/(dashboard)/mcp/page-cdf057e468c6e92e.js +1 -0
  110. package/web/.next/static/chunks/app/(dashboard)/memories/{page-e201633b4bbbdf73.js → page-e706ab4aa681fa99.js} +1 -1
  111. package/web/.next/static/chunks/app/(dashboard)/{page-a42d880f7036e866.js → page-29cfae3bf701076e.js} +1 -1
  112. package/web/.next/static/chunks/app/(dashboard)/settings/page-312ae27ae71ea6d1.js +1 -0
  113. package/web/.next/static/chunks/app/(dashboard)/skills/[id]/edit/{page-ecaf3520da303237.js → page-89a8a30db5eb96e1.js} +1 -1
  114. package/web/.next/static/chunks/app/(dashboard)/skills/new/{page-cdbc2aada2be0bfc.js → page-46a424b203591560.js} +1 -1
  115. package/web/.next/static/chunks/app/(dashboard)/skills/{page-234553540bef945b.js → page-edad7bbd3230fb7a.js} +1 -1
  116. package/web/.next/server/chunks/8052.js +0 -1
  117. package/web/.next/server/chunks/8782.js +0 -1
  118. package/web/.next/server/chunks/9084.js +0 -1
  119. package/web/.next/static/chunks/app/(dashboard)/agents/[id]/edit/page-de6c8fc7cb73a3de.js +0 -2
  120. package/web/.next/static/chunks/app/(dashboard)/agents/[id]/telegram/page-6d4161f1e0b19885.js +0 -1
  121. package/web/.next/static/chunks/app/(dashboard)/automations/page-7693601b49363371.js +0 -1
  122. package/web/.next/static/chunks/app/(dashboard)/chat/page-839128f211f63728.js +0 -1
  123. package/web/.next/static/chunks/app/(dashboard)/jobs/[id]/page-4fc570c6c1e39edb.js +0 -1
  124. package/web/.next/static/chunks/app/(dashboard)/jobs/page-cf861b235dc54ced.js +0 -1
  125. package/web/.next/static/chunks/app/(dashboard)/llm-providers/page-99eab754716f9071.js +0 -1
  126. package/web/.next/static/chunks/app/(dashboard)/mcp/page-082442b4f9ac0f91.js +0 -1
  127. package/web/.next/static/chunks/app/(dashboard)/settings/page-d85cac3728506241.js +0 -1
  128. /package/web/.next/static/{n6jP_zB4kqJScKY_T2ciu → PV5qpGylrVW3jZ7zKHuhE}/_buildManifest.js +0 -0
  129. /package/web/.next/static/{n6jP_zB4kqJScKY_T2ciu → PV5qpGylrVW3jZ7zKHuhE}/_ssgManifest.js +0 -0
package/README.md CHANGED
@@ -84,7 +84,7 @@ When a newer version is available, `nodal-agents up` also prints a one-line
84
84
  notice:
85
85
 
86
86
  ```
87
- ℹ v0.4.3 available — run `nodal-agents update`
87
+ ℹ v0.4.4 available — run `nodal-agents update`
88
88
  ```
89
89
 
90
90
  ### Build from source
@@ -212,7 +212,7 @@ pnpm deps:check # runs locally and in CI before every release
212
212
 
213
213
  ## Status
214
214
 
215
- **Current release:** `0.4.3` on npm `latest`. Used daily by the
215
+ **Current release:** `0.4.4` on npm `latest`. Used daily by the
216
216
  maintainer, stable enough for personal production. Pre-1.0 — breaking
217
217
  changes are still possible between minors.
218
218
 
@@ -228,9 +228,13 @@ changes are still possible between minors.
228
228
  timeout / quota mid-job the runner fails over to the next key, and fails loud
229
229
  (`all_providers_failed`) only when the whole chain is exhausted
230
230
  - **Reliability guards (generic, model-agnostic)** — per-job token budget +
231
- no-progress detector (kill runaway loops), and a no-false-success guard that
231
+ no-progress detector (kill runaway loops), a no-false-success guard that
232
232
  refuses to complete a job as "success" when a tool action failed and was never
233
- resolved (fail loud, never fake it)
233
+ resolved (fail loud, never fake it), and context compaction that evicts stale
234
+ tool output before the model's context window overflows
235
+ - **Diagnosable failures** — every failed job persists its full transcript and
236
+ the real upstream provider error (not an opaque "provider returned error"), so
237
+ you can see exactly what the agent did and why it stopped
234
238
  - Persistent memory (sanitation, dedup, importance ranking, auto-injection,
235
239
  feedback loop)
236
240
  - Session-thread continuity on chat channels (Telegram today)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodal-agents",
3
- "version": "0.4.3",
3
+ "version": "0.4.4",
4
4
  "description": "Local-first AI agent platform with a web dashboard — install in one command.",
5
5
  "license": "MIT",
6
6
  "repository": {
package/runner.js CHANGED
@@ -1106,7 +1106,10 @@ var init_connector_catalog = __esm({
1106
1106
  function findModelCatalogEntry(provider, modelId) {
1107
1107
  return MODEL_CATALOG[provider]?.find((e) => e.modelId === modelId);
1108
1108
  }
1109
- var MODEL_CATALOG;
1109
+ function modelContextWindow(provider, modelId) {
1110
+ return findModelCatalogEntry(provider, modelId)?.contextWindow ?? DEFAULT_CONTEXT_WINDOW;
1111
+ }
1112
+ var MODEL_CATALOG, DEFAULT_CONTEXT_WINDOW;
1110
1113
  var init_model_catalog = __esm({
1111
1114
  "../../packages/shared/src/model-catalog.ts"() {
1112
1115
  "use strict";
@@ -1115,51 +1118,64 @@ var init_model_catalog = __esm({
1115
1118
  {
1116
1119
  modelId: "claude-opus-4-8",
1117
1120
  label: "Claude Opus 4.8",
1118
- capabilities: { tools: true, forcedToolChoice: true }
1121
+ capabilities: { tools: true, forcedToolChoice: true },
1122
+ contextWindow: 2e5
1119
1123
  },
1120
1124
  {
1121
1125
  modelId: "claude-sonnet-4-6",
1122
1126
  label: "Claude Sonnet 4.6",
1123
- capabilities: { tools: true, forcedToolChoice: true }
1127
+ capabilities: { tools: true, forcedToolChoice: true },
1128
+ contextWindow: 2e5
1124
1129
  },
1125
1130
  {
1126
1131
  modelId: "claude-haiku-4-5-20251001",
1127
1132
  label: "Claude Haiku 4.5",
1128
- capabilities: { tools: true, forcedToolChoice: true }
1133
+ capabilities: { tools: true, forcedToolChoice: true },
1134
+ contextWindow: 2e5
1129
1135
  }
1130
1136
  ],
1131
1137
  openai: [
1132
- { modelId: "gpt-5", label: "GPT-5", capabilities: { tools: true, forcedToolChoice: true } },
1138
+ {
1139
+ modelId: "gpt-5",
1140
+ label: "GPT-5",
1141
+ capabilities: { tools: true, forcedToolChoice: true },
1142
+ contextWindow: 4e5
1143
+ },
1133
1144
  {
1134
1145
  modelId: "gpt-5-mini",
1135
1146
  label: "GPT-5 mini",
1136
- capabilities: { tools: true, forcedToolChoice: true }
1147
+ capabilities: { tools: true, forcedToolChoice: true },
1148
+ contextWindow: 4e5
1137
1149
  }
1138
1150
  ],
1139
1151
  google: [
1140
1152
  {
1141
1153
  modelId: "gemini-2.0-flash",
1142
1154
  label: "Gemini 2.0 Flash",
1143
- capabilities: { tools: true, forcedToolChoice: true }
1155
+ capabilities: { tools: true, forcedToolChoice: true },
1156
+ contextWindow: 1048576
1144
1157
  },
1145
1158
  {
1146
1159
  modelId: "gemini-2.5-pro",
1147
1160
  label: "Gemini 2.5 Pro",
1148
- capabilities: { tools: true, forcedToolChoice: true }
1161
+ capabilities: { tools: true, forcedToolChoice: true },
1162
+ contextWindow: 1048576
1149
1163
  }
1150
1164
  ],
1151
1165
  groq: [
1152
1166
  {
1153
1167
  modelId: "llama-3.3-70b-versatile",
1154
1168
  label: "Llama 3.3 70B",
1155
- capabilities: { tools: true, forcedToolChoice: true }
1169
+ capabilities: { tools: true, forcedToolChoice: true },
1170
+ contextWindow: 131072
1156
1171
  }
1157
1172
  ],
1158
1173
  mistral: [
1159
1174
  {
1160
1175
  modelId: "mistral-large-latest",
1161
1176
  label: "Mistral Large",
1162
- capabilities: { tools: true, forcedToolChoice: true }
1177
+ capabilities: { tools: true, forcedToolChoice: true },
1178
+ contextWindow: 131072
1163
1179
  }
1164
1180
  ],
1165
1181
  // OpenRouter models are namespaced by sub-vendor (anthropic/, deepseek/, …).
@@ -1172,69 +1188,82 @@ var init_model_catalog = __esm({
1172
1188
  {
1173
1189
  modelId: "anthropic/claude-haiku-4.5",
1174
1190
  label: "Claude Haiku 4.5",
1175
- capabilities: { tools: true, forcedToolChoice: true }
1191
+ capabilities: { tools: true, forcedToolChoice: true },
1192
+ contextWindow: 2e5
1176
1193
  },
1177
1194
  {
1178
1195
  modelId: "anthropic/claude-opus-4.7",
1179
1196
  label: "Claude Opus 4.7",
1180
- capabilities: { tools: true, forcedToolChoice: true }
1197
+ capabilities: { tools: true, forcedToolChoice: true },
1198
+ contextWindow: 1e6
1181
1199
  },
1182
1200
  {
1183
1201
  modelId: "anthropic/claude-opus-4.7-fast",
1184
1202
  label: "Claude Opus 4.7 (fast)",
1185
- capabilities: { tools: true, forcedToolChoice: true }
1203
+ capabilities: { tools: true, forcedToolChoice: true },
1204
+ contextWindow: 1e6
1186
1205
  },
1187
1206
  {
1188
1207
  modelId: "anthropic/claude-opus-4.8",
1189
1208
  label: "Claude Opus 4.8",
1190
- capabilities: { tools: true, forcedToolChoice: true }
1209
+ capabilities: { tools: true, forcedToolChoice: true },
1210
+ contextWindow: 1e6
1191
1211
  },
1192
1212
  {
1193
1213
  modelId: "anthropic/claude-opus-4.8-fast",
1194
1214
  label: "Claude Opus 4.8 (fast)",
1195
- capabilities: { tools: true, forcedToolChoice: true }
1215
+ capabilities: { tools: true, forcedToolChoice: true },
1216
+ contextWindow: 1e6
1196
1217
  },
1197
1218
  {
1198
1219
  modelId: "anthropic/claude-sonnet-4.6",
1199
1220
  label: "Claude Sonnet 4.6",
1200
- capabilities: { tools: true, forcedToolChoice: true }
1221
+ capabilities: { tools: true, forcedToolChoice: true },
1222
+ contextWindow: 1e6
1201
1223
  },
1202
1224
  // DeepSeek
1203
1225
  {
1204
1226
  modelId: "deepseek/deepseek-v3.2",
1205
1227
  label: "DeepSeek V3.2",
1206
- capabilities: { tools: true, forcedToolChoice: true }
1228
+ capabilities: { tools: true, forcedToolChoice: true },
1229
+ contextWindow: 131072
1207
1230
  },
1208
1231
  {
1209
1232
  modelId: "deepseek/deepseek-v4-flash",
1210
1233
  label: "DeepSeek V4 Flash",
1211
- capabilities: { tools: true, forcedToolChoice: true }
1234
+ capabilities: { tools: true, forcedToolChoice: true },
1235
+ contextWindow: 1048576
1212
1236
  },
1213
1237
  {
1214
1238
  modelId: "deepseek/deepseek-v4-pro",
1215
1239
  label: "DeepSeek V4 Pro",
1216
- capabilities: { tools: true, forcedToolChoice: true }
1240
+ capabilities: { tools: true, forcedToolChoice: true },
1241
+ contextWindow: 1048576
1217
1242
  },
1218
1243
  // Google
1219
1244
  {
1220
1245
  modelId: "google/gemini-3.1-flash-lite-preview",
1221
1246
  label: "Gemini 3.1 Flash Lite (preview)",
1222
- capabilities: { tools: true, forcedToolChoice: true }
1247
+ capabilities: { tools: true, forcedToolChoice: true },
1248
+ contextWindow: 1048576
1223
1249
  },
1224
1250
  {
1225
1251
  modelId: "google/gemini-3.1-pro-preview",
1226
1252
  label: "Gemini 3.1 Pro (preview)",
1227
- capabilities: { tools: true, forcedToolChoice: true }
1253
+ capabilities: { tools: true, forcedToolChoice: true },
1254
+ contextWindow: 1048576
1228
1255
  },
1229
1256
  {
1230
1257
  modelId: "google/gemini-3.5-flash",
1231
1258
  label: "Gemini 3.5 Flash",
1232
- capabilities: { tools: true, forcedToolChoice: true }
1259
+ capabilities: { tools: true, forcedToolChoice: true },
1260
+ contextWindow: 1048576
1233
1261
  },
1234
1262
  {
1235
1263
  modelId: "google/gemma-4-31b-it",
1236
1264
  label: "Gemma 4 31B-IT",
1237
- capabilities: { tools: true, forcedToolChoice: true }
1265
+ capabilities: { tools: true, forcedToolChoice: true },
1266
+ contextWindow: 262144
1238
1267
  },
1239
1268
  // MiniMax
1240
1269
  {
@@ -1244,10 +1273,12 @@ var init_model_catalog = __esm({
1244
1273
  // tool_choice ('required') → we send 'auto' (forcedToolChoice:false).
1245
1274
  // reasoning:true makes the provider return reasoning_details so the runner
1246
1275
  // can round-trip them across tool-call turns.
1247
- capabilities: { tools: true, forcedToolChoice: false, reasoning: true }
1276
+ capabilities: { tools: true, forcedToolChoice: false, reasoning: true },
1277
+ contextWindow: 1048576
1248
1278
  }
1249
1279
  ]
1250
1280
  };
1281
+ DEFAULT_CONTEXT_WINDOW = 128e3;
1251
1282
  }
1252
1283
  });
1253
1284
 
@@ -27445,13 +27476,17 @@ async function completeJob(db, jobId, result, toolsUsed = [], stats, messages) {
27445
27476
  }
27446
27477
  }).where(eq4(agentJobs.id, jobId));
27447
27478
  }
27448
- async function failJob(db, jobId, errorCode, stats) {
27479
+ async function failJob(db, jobId, errorCode, stats, messages) {
27449
27480
  const now = /* @__PURE__ */ new Date();
27450
27481
  await db.update(agentJobs).set({
27451
27482
  status: "failed",
27452
27483
  error: errorCode,
27453
27484
  completedAt: now,
27454
27485
  updatedAt: now,
27486
+ // Persist the transcript on failure (mirrors completeJob) so a failed job
27487
+ // is DIAGNOSABLE, not opaque. The resume/guard failure paths used to lose
27488
+ // it entirely. Omitted ⇒ the stored messages are left untouched.
27489
+ ...messages ? { messages } : {},
27455
27490
  ...stats && {
27456
27491
  inputTokens: stats.inputTokens,
27457
27492
  outputTokens: stats.outputTokens,
@@ -27673,6 +27708,40 @@ function truncateForContext(value) {
27673
27708
 
27674
27709
  [... truncated: ${dropped} chars dropped (total ${value.length}) ...]`;
27675
27710
  }
27711
+ var EVICTED_TOOL_RESULT_MARKER = "[earlier tool result elided to fit the context window \u2014 re-run the tool if you need this data again]";
27712
+ function compactOldToolResults(messages, keepRecentToolMessages) {
27713
+ const toolMsgIdx = [];
27714
+ messages.forEach((m, i) => {
27715
+ if (m.role === "tool") toolMsgIdx.push(i);
27716
+ });
27717
+ const evictCount = toolMsgIdx.length - keepRecentToolMessages;
27718
+ if (evictCount <= 0) return { messages: [...messages], evicted: 0 };
27719
+ const evictIdx = new Set(toolMsgIdx.slice(0, evictCount));
27720
+ let evicted = 0;
27721
+ const out = messages.map((m, i) => {
27722
+ if (!evictIdx.has(i) || m.role !== "tool" || !Array.isArray(m.content)) return m;
27723
+ const content = m.content.map((p) => {
27724
+ if (p.type !== "tool-result") return p;
27725
+ if (p.output.type === "text" && p.output.value === EVICTED_TOOL_RESULT_MARKER) return p;
27726
+ evicted += 1;
27727
+ return { ...p, output: { type: "text", value: EVICTED_TOOL_RESULT_MARKER } };
27728
+ });
27729
+ return { ...m, content };
27730
+ });
27731
+ return { messages: out, evicted };
27732
+ }
27733
+ function describeLlmError(err) {
27734
+ if (!(err instanceof Error)) return "unknown_error";
27735
+ const e = err;
27736
+ const body = typeof e.responseBody === "string" && e.responseBody.trim().length > 0 ? e.responseBody : "";
27737
+ const dataStr = !body && e.data != null ? JSON.stringify(e.data) : "";
27738
+ const detail = body || dataStr;
27739
+ if (detail) {
27740
+ const status = typeof e.statusCode === "number" ? `${e.statusCode} ` : "";
27741
+ return `${status}${err.message}: ${detail}`.slice(0, 400);
27742
+ }
27743
+ return err.message.slice(0, 200);
27744
+ }
27676
27745
  function stableStringify(value) {
27677
27746
  return JSON.stringify(value, (_key, val) => {
27678
27747
  if (val && typeof val === "object" && !Array.isArray(val)) {
@@ -27711,6 +27780,7 @@ async function executeJob(jobId, deps, _runnerEnv) {
27711
27780
  });
27712
27781
  return { status: "failed", error: "chain_limit_exceeded" };
27713
27782
  }
27783
+ let messages = Array.isArray(job.messages) ? job.messages : [];
27714
27784
  let inputTokens = job.inputTokens ?? 0;
27715
27785
  let outputTokens = job.outputTokens ?? 0;
27716
27786
  let turn = job.turn ?? 0;
@@ -27723,13 +27793,13 @@ async function executeJob(jobId, deps, _runnerEnv) {
27723
27793
  });
27724
27794
  await setJobStatus(db, jobId, "processing");
27725
27795
  if (!job.agentId) {
27726
- await failJob(db, jobId, "agent_not_found", runStats());
27796
+ await failJob(db, jobId, "agent_not_found", runStats(), messages);
27727
27797
  return { status: "failed", error: "agent_not_found" };
27728
27798
  }
27729
27799
  const agentRows = await db.select().from(agents).where(eq4(agents.id, job.agentId)).limit(1);
27730
27800
  const agentRow = agentRows[0];
27731
27801
  if (!agentRow || !agentRow.active) {
27732
- await failJob(db, jobId, "agent_not_found", runStats());
27802
+ await failJob(db, jobId, "agent_not_found", runStats(), messages);
27733
27803
  return { status: "failed", error: "agent_not_found" };
27734
27804
  }
27735
27805
  const agent = {
@@ -27747,7 +27817,7 @@ async function executeJob(jobId, deps, _runnerEnv) {
27747
27817
  const wsRows = await db.select({ label: agentWorkspaces.label, path: agentWorkspaces.path }).from(agentWorkspaces).where(eq4(agentWorkspaces.agentId, agentRow.id)).orderBy(agentWorkspaces.position, agentWorkspaces.label);
27748
27818
  const agentWorkspacesList = wsRows;
27749
27819
  if (!agentRow.llmKeyId) {
27750
- await failJob(db, jobId, "agent_no_llm_configured", runStats());
27820
+ await failJob(db, jobId, "agent_no_llm_configured", runStats(), messages);
27751
27821
  return { status: "failed", error: "agent_no_llm_configured" };
27752
27822
  }
27753
27823
  {
@@ -27762,7 +27832,7 @@ async function executeJob(jobId, deps, _runnerEnv) {
27762
27832
  );
27763
27833
  if (!resolved.ok) {
27764
27834
  const code = resolved.reason === "agent_no_llm_configured" ? "agent_no_llm_configured" : `llm_key_invalid:${resolved.detail}`;
27765
- await failJob(db, jobId, code, runStats());
27835
+ await failJob(db, jobId, code, runStats(), messages);
27766
27836
  return { status: "failed", error: code };
27767
27837
  }
27768
27838
  llmClient = resolved.client;
@@ -27797,7 +27867,7 @@ async function executeJob(jobId, deps, _runnerEnv) {
27797
27867
  let toolDefs;
27798
27868
  const memoryBuiltins = ALWAYS_ON_TOOLS.filter((n) => n !== "return_result").map((n) => registry.get(n)).filter((t) => t !== void 0);
27799
27869
  const capabilityTools = [];
27800
- if (agentRow.telegramBotToken) {
27870
+ if (agentRow.telegramBotToken && job.chatId) {
27801
27871
  capabilityTools.push(createTelegramSendMessageTool());
27802
27872
  }
27803
27873
  const mcpClosers = [];
@@ -27950,7 +28020,7 @@ async function executeJob(jobId, deps, _runnerEnv) {
27950
28020
  }
27951
28021
  } catch (err) {
27952
28022
  const errorCode = err instanceof Error ? err.message : "whitelist_computation_failed";
27953
- await failJob(db, jobId, errorCode, runStats());
28023
+ await failJob(db, jobId, errorCode, runStats(), messages);
27954
28024
  return { status: "failed", error: errorCode };
27955
28025
  }
27956
28026
  const ruleRows = await db.select().from(approvalRules).where(eq4(approvalRules.entityId, job.entityId ?? ""));
@@ -27969,7 +28039,6 @@ async function executeJob(jobId, deps, _runnerEnv) {
27969
28039
  const mcpToolNames = await listWorkspaceMcpToolNames(db, job.entityId ?? "");
27970
28040
  authoringToolsSuffix = mcpToolNames.length > 0 ? ` When a skill needs an MCP tool, reference one of these EXACT names available in this workspace (built-in and connector tools are bare snake_case and need no namespace): ${mcpToolNames.join(", ")}.` : " This workspace has no MCP servers connected yet \u2014 a skill should only reference built-in tools (bare snake_case) or none.";
27971
28041
  }
27972
- let messages = Array.isArray(job.messages) ? job.messages : [];
27973
28042
  if (messages.length === 0) {
27974
28043
  messages = [{ role: "user", content: job.task }];
27975
28044
  }
@@ -28111,6 +28180,18 @@ async function executeJob(jobId, deps, _runnerEnv) {
28111
28180
  const n = raw ? Number(raw) : NaN;
28112
28181
  return Number.isFinite(n) && n > 0 ? n : DEFAULT_LIMITS.maxTotalTokensPerJob;
28113
28182
  })();
28183
+ const compactionThreshold = (() => {
28184
+ const ctxWindow = modelContextWindow(llmClient.config.provider, llmClient.config.model);
28185
+ const fracRaw = Number(process.env["LLM_COMPACTION_FRACTION"]);
28186
+ const frac = Number.isFinite(fracRaw) && fracRaw > 0 && fracRaw < 1 ? fracRaw : 0.7;
28187
+ const absRaw = Number(process.env["LLM_COMPACTION_TOKENS"]);
28188
+ const abs = Number.isFinite(absRaw) && absRaw > 0 ? absRaw : 12e4;
28189
+ return Math.min(Math.floor(frac * ctxWindow), abs);
28190
+ })();
28191
+ const compactionKeepRecentToolMsgs = (() => {
28192
+ const raw = Number(process.env["LLM_COMPACTION_KEEP_TURNS"]);
28193
+ return Number.isFinite(raw) && raw >= 1 ? Math.floor(raw) : 3;
28194
+ })();
28114
28195
  const recentTurnSignatures = [];
28115
28196
  const maxNoProgressRepeats = (() => {
28116
28197
  const raw = process.env["MAX_NO_PROGRESS_REPEATS"];
@@ -28131,7 +28212,7 @@ async function executeJob(jobId, deps, _runnerEnv) {
28131
28212
  return { status: "cancelled" };
28132
28213
  }
28133
28214
  if (turn > DEFAULT_LIMITS.maxTurns) {
28134
- await failJob(db, jobId, "turn_limit_exceeded", runStats());
28215
+ await failJob(db, jobId, "turn_limit_exceeded", runStats(), messages);
28135
28216
  return { status: "failed", error: "turn_limit_exceeded" };
28136
28217
  }
28137
28218
  validateMessageStructure(messages);
@@ -28160,9 +28241,21 @@ async function executeJob(jobId, deps, _runnerEnv) {
28160
28241
  outputTokens += Number.isFinite(completionT) ? completionT : 0;
28161
28242
  if (inputTokens + outputTokens > maxTotalTokensPerJob) {
28162
28243
  trace("token_budget_exceeded", { turn, inputTokens, outputTokens, maxTotalTokensPerJob });
28163
- await failJob(db, jobId, "token_budget_exceeded", runStats());
28244
+ await failJob(db, jobId, "token_budget_exceeded", runStats(), messages);
28164
28245
  return { status: "failed", error: "token_budget_exceeded" };
28165
28246
  }
28247
+ if (promptT > compactionThreshold) {
28248
+ const ev = compactOldToolResults(messages, compactionKeepRecentToolMsgs);
28249
+ if (ev.evicted > 0) {
28250
+ messages = ev.messages;
28251
+ trace("context_compacted", {
28252
+ turn,
28253
+ promptTokens: promptT,
28254
+ threshold: compactionThreshold,
28255
+ evictedToolResults: ev.evicted
28256
+ });
28257
+ }
28258
+ }
28166
28259
  const rawToolCalls = response.toolCalls ?? [];
28167
28260
  trace("llm_call_done", {
28168
28261
  turn,
@@ -28174,7 +28267,7 @@ async function executeJob(jobId, deps, _runnerEnv) {
28174
28267
  consecutiveDeliveryOnlyTurns = isDeliveryOnlyTurn ? consecutiveDeliveryOnlyTurns + 1 : 0;
28175
28268
  if (consecutiveDeliveryOnlyTurns > DEFAULT_LIMITS.maxConsecutiveDeliveryTurns) {
28176
28269
  trace("delivery_spam_guard", { turn, consecutiveDeliveryOnlyTurns });
28177
- await failJob(db, jobId, "delivery_spam_guard", runStats());
28270
+ await failJob(db, jobId, "delivery_spam_guard", runStats(), messages);
28178
28271
  return { status: "failed", error: "delivery_spam_guard" };
28179
28272
  }
28180
28273
  const reasoningParts = response.reasoning ?? [];
@@ -28211,7 +28304,7 @@ async function executeJob(jobId, deps, _runnerEnv) {
28211
28304
  continue;
28212
28305
  }
28213
28306
  trace("telegram_not_delivered", { turn, via: "text_branch" });
28214
- await failJob(db, jobId, "telegram_not_delivered", runStats());
28307
+ await failJob(db, jobId, "telegram_not_delivered", runStats(), messages);
28215
28308
  return { status: "failed", error: "telegram_not_delivered" };
28216
28309
  }
28217
28310
  await completeJob(db, jobId, textContent, toolsUsed, runStats(), messages);
@@ -28223,7 +28316,8 @@ async function executeJob(jobId, deps, _runnerEnv) {
28223
28316
  messages = messages.slice(0, -1);
28224
28317
  continue;
28225
28318
  }
28226
- await failJob(db, jobId, "no_tool_calls_no_text", runStats());
28319
+ messages = messages.slice(0, -1);
28320
+ await failJob(db, jobId, "no_tool_calls_no_text", runStats(), messages);
28227
28321
  return { status: "failed", error: "no_tool_calls_no_text" };
28228
28322
  }
28229
28323
  const rawCallBlocks = rawToolCalls.map((tc) => ({
@@ -28258,7 +28352,13 @@ async function executeJob(jobId, deps, _runnerEnv) {
28258
28352
  toolsUsed = [.../* @__PURE__ */ new Set([...toolsUsed, call.name])];
28259
28353
  const toolDef = toolMap.get(call.name);
28260
28354
  if (!toolDef) {
28261
- await failJob(db, jobId, `whitelist_violation:${call.name}`, runStats());
28355
+ await failJob(
28356
+ db,
28357
+ jobId,
28358
+ `whitelist_violation:${call.name}`,
28359
+ runStats(),
28360
+ messages
28361
+ );
28262
28362
  return { status: "failed", error: `whitelist_violation:${call.name}` };
28263
28363
  }
28264
28364
  if (call.name.startsWith("assign_")) {
@@ -28493,7 +28593,7 @@ async function executeJob(jobId, deps, _runnerEnv) {
28493
28593
  continue;
28494
28594
  }
28495
28595
  trace("unresolved_tool_failure", { turn, stuck });
28496
- await failJob(db, jobId, "unresolved_tool_failure", runStats());
28596
+ await failJob(db, jobId, "unresolved_tool_failure", runStats(), messages);
28497
28597
  return { status: "failed", error: "unresolved_tool_failure" };
28498
28598
  }
28499
28599
  const taskRows = await db.select({ id: agentTasks.id }).from(agentTasks).where(eq4(agentTasks.rootJobId, jobId));
@@ -28518,7 +28618,7 @@ async function executeJob(jobId, deps, _runnerEnv) {
28518
28618
  continue;
28519
28619
  }
28520
28620
  trace("telegram_not_delivered", { turn, via: "return_result_branch" });
28521
- await failJob(db, jobId, "telegram_not_delivered", runStats());
28621
+ await failJob(db, jobId, "telegram_not_delivered", runStats(), messages);
28522
28622
  return { status: "failed", error: "telegram_not_delivered" };
28523
28623
  }
28524
28624
  const finalResult = "";
@@ -28568,7 +28668,7 @@ async function executeJob(jobId, deps, _runnerEnv) {
28568
28668
  }
28569
28669
  if (recentTurnSignatures.length === maxNoProgressRepeats && recentTurnSignatures.every((s) => s === turnSignature)) {
28570
28670
  trace("no_progress_detected", { turn, repeats: recentTurnSignatures.length });
28571
- await failJob(db, jobId, "no_progress_detected", runStats());
28671
+ await failJob(db, jobId, "no_progress_detected", runStats(), messages);
28572
28672
  return { status: "failed", error: "no_progress_detected" };
28573
28673
  }
28574
28674
  }
@@ -28584,30 +28684,36 @@ async function executeJob(jobId, deps, _runnerEnv) {
28584
28684
  } catch (err) {
28585
28685
  trace("catch", {
28586
28686
  errName: err instanceof Error ? err.name : "unknown",
28587
- errMsg: err instanceof Error ? err.message.slice(0, 200) : String(err)
28687
+ errMsg: describeLlmError(err)
28588
28688
  });
28589
28689
  if (err instanceof ToolCallLimitExceededError) {
28590
- await failJob(db, jobId, err.code, runStats());
28690
+ await failJob(db, jobId, err.code, runStats(), messages);
28591
28691
  return { status: "failed", error: err.code };
28592
28692
  }
28593
28693
  if (err instanceof ChainLimitExceededError) {
28594
- await failJob(db, jobId, err.code, runStats());
28694
+ await failJob(db, jobId, err.code, runStats(), messages);
28595
28695
  return { status: "failed", error: err.code };
28596
28696
  }
28597
28697
  if (err instanceof DelegationDepthExceededError) {
28598
- await failJob(db, jobId, err.code, runStats());
28698
+ await failJob(db, jobId, err.code, runStats(), messages);
28599
28699
  return { status: "failed", error: err.code };
28600
28700
  }
28601
28701
  if (err instanceof QuotaExhaustedError) {
28602
- await failJob(db, jobId, "quota_exhausted", runStats());
28702
+ await failJob(db, jobId, "quota_exhausted", runStats(), messages);
28603
28703
  return { status: "failed", error: "quota_exhausted" };
28604
28704
  }
28605
28705
  if (err instanceof AllProvidersFailedError) {
28606
- await failJob(db, jobId, err.code, runStats());
28706
+ await failJob(db, jobId, err.code, runStats(), messages);
28607
28707
  return { status: "failed", error: err.code };
28608
28708
  }
28609
28709
  if (err instanceof MessageStructureError) {
28610
- await failJob(db, jobId, `message_structure_invalid:${err.code}`, runStats());
28710
+ await failJob(
28711
+ db,
28712
+ jobId,
28713
+ `message_structure_invalid:${err.code}`,
28714
+ runStats(),
28715
+ messages
28716
+ );
28611
28717
  return { status: "failed", error: `message_structure_invalid:${err.code}` };
28612
28718
  }
28613
28719
  if (err instanceof Error) {
@@ -28617,12 +28723,12 @@ async function executeJob(jobId, deps, _runnerEnv) {
28617
28723
  if (unavailableMatch) {
28618
28724
  const toolName = unavailableMatch[1] ?? "unknown_tool";
28619
28725
  const code = `whitelist_violation:${toolName}`;
28620
- await failJob(db, jobId, code, runStats());
28726
+ await failJob(db, jobId, code, runStats(), messages);
28621
28727
  return { status: "failed", error: code };
28622
28728
  }
28623
28729
  }
28624
- const errorCode = err instanceof Error ? err.message.slice(0, 200) : "unknown_error";
28625
- await failJob(db, jobId, errorCode, runStats());
28730
+ const errorCode = describeLlmError(err);
28731
+ await failJob(db, jobId, errorCode, runStats(), messages);
28626
28732
  return { status: "failed", error: errorCode };
28627
28733
  } finally {
28628
28734
  for (const close of mcpClosers) {
@@ -1 +1 @@
1
- n6jP_zB4kqJScKY_T2ciu
1
+ PV5qpGylrVW3jZ7zKHuhE
@@ -13,8 +13,8 @@
13
13
  "/(dashboard)/agents/[id]/edit/page": "/agents/[id]/edit",
14
14
  "/(dashboard)/agents/[id]/telegram/page": "/agents/[id]/telegram",
15
15
  "/(dashboard)/agents/page": "/agents",
16
- "/(dashboard)/approvals/page": "/approvals",
17
16
  "/(dashboard)/automations/page": "/automations",
17
+ "/(dashboard)/approvals/page": "/approvals",
18
18
  "/(dashboard)/chat/page": "/chat",
19
19
  "/(dashboard)/connectors/page": "/connectors",
20
20
  "/(dashboard)/credentials/page": "/credentials",
@@ -4,8 +4,8 @@
4
4
  ],
5
5
  "devFiles": [],
6
6
  "lowPriorityFiles": [
7
- "static/n6jP_zB4kqJScKY_T2ciu/_buildManifest.js",
8
- "static/n6jP_zB4kqJScKY_T2ciu/_ssgManifest.js"
7
+ "static/PV5qpGylrVW3jZ7zKHuhE/_buildManifest.js",
8
+ "static/PV5qpGylrVW3jZ7zKHuhE/_ssgManifest.js"
9
9
  ],
10
10
  "rootMainFiles": [
11
11
  "static/chunks/webpack-3741bf0a7636d65e.js",
@@ -78,8 +78,8 @@
78
78
  "dynamicRoutes": {},
79
79
  "notFoundRoutes": [],
80
80
  "preview": {
81
- "previewModeId": "98616a9865e16bddc6a1143b769ee98e",
82
- "previewModeSigningKey": "ee3ee89f46a6815ae05eb06949ae794c6962a52e9a6f58e3dfbcf1c6614ff841",
83
- "previewModeEncryptionKey": "c4c6b207c2e3329b64d6c383809a95a2f5f1f3ecd6c68005de252da1d057c7bb"
81
+ "previewModeId": "ab66490d94469ea63e5777d5e5465b39",
82
+ "previewModeSigningKey": "3b725fc347ffafb5d6ab5906e8f5f39cb4523c75a6a3769d4e6779e014736265",
83
+ "previewModeEncryptionKey": "356915c85cb0a6b84c6f46dc1bceb092c3ed009fbca03946585cea052e5c754c"
84
84
  }
85
85
  }