langflower 0.0.7 → 0.0.9

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 (189) hide show
  1. package/package.json +1 -1
  2. package/ui-dist/{chunk-Y6VMBLSJ.js → chunk-7UMVYN3A.js} +1 -1
  3. package/ui-dist/{chunk-6DQ2XSRW.js → chunk-MDGXX73Z.js} +3 -3
  4. package/ui-dist/index.html +1 -1
  5. package/ui-dist/main-2JJGCNTD.js +313 -0
  6. package/vendor/common-nodes/dist/ai/chat-completion-stream.d.ts +2 -0
  7. package/vendor/common-nodes/dist/ai/critique/node.d.ts +246 -0
  8. package/vendor/common-nodes/dist/ai/critique/node.js +2 -2
  9. package/vendor/common-nodes/dist/ai/fake-llm/node.d.ts +246 -0
  10. package/vendor/common-nodes/dist/ai/features/chat-completion-stream.d.ts +53 -0
  11. package/vendor/common-nodes/dist/ai/features/chat-completion-stream.js +1 -0
  12. package/vendor/common-nodes/dist/ai/features/llm-loop/autokick-recovery.d.ts +13 -0
  13. package/vendor/common-nodes/dist/ai/features/llm-loop/autokick-recovery.js +17 -0
  14. package/vendor/common-nodes/dist/ai/features/llm-loop/classify-llm-failure.d.ts +2 -0
  15. package/vendor/common-nodes/dist/ai/features/llm-loop/classify-llm-failure.js +113 -0
  16. package/vendor/common-nodes/dist/ai/features/llm-loop/dead-loop-detector.d.ts +28 -0
  17. package/vendor/common-nodes/dist/ai/features/llm-loop/dead-loop-detector.js +194 -0
  18. package/vendor/common-nodes/dist/ai/features/llm-loop/live-get-tools.d.ts +9 -0
  19. package/vendor/common-nodes/dist/ai/features/llm-loop/live-get-tools.js +21 -0
  20. package/vendor/common-nodes/dist/ai/features/llm-loop/llm-loop-reducer.d.ts +2 -0
  21. package/vendor/common-nodes/dist/ai/features/llm-loop/llm-loop-reducer.js +216 -0
  22. package/vendor/common-nodes/dist/ai/features/llm-loop/llm-loop-types.d.ts +125 -0
  23. package/vendor/common-nodes/dist/ai/features/llm-loop/llm-loop-types.js +39 -0
  24. package/vendor/common-nodes/dist/ai/features/llm-loop/normalize-llm-recovery-policy.d.ts +2 -0
  25. package/vendor/common-nodes/dist/ai/features/llm-loop/normalize-llm-recovery-policy.js +59 -0
  26. package/vendor/common-nodes/dist/ai/features/llm-loop/operators/normalize-tool-result.d.ts +1 -0
  27. package/vendor/common-nodes/dist/ai/features/llm-loop/operators/normalize-tool-result.js +7 -0
  28. package/vendor/common-nodes/dist/ai/features/llm-loop/operators/observe-provider-stream.d.ts +43 -0
  29. package/vendor/common-nodes/dist/ai/features/llm-loop/operators/observe-provider-stream.js +96 -0
  30. package/vendor/common-nodes/dist/ai/features/llm-loop/run-agent-loop.d.ts +32 -0
  31. package/vendor/common-nodes/dist/ai/features/llm-loop/run-agent-loop.js +58 -0
  32. package/vendor/common-nodes/dist/ai/features/llm-loop/run-llm-loop.d.ts +80 -0
  33. package/vendor/common-nodes/dist/ai/features/llm-loop/run-llm-loop.js +681 -0
  34. package/vendor/common-nodes/dist/ai/features/llm-role-preset.d.ts +69 -0
  35. package/vendor/common-nodes/dist/ai/features/llm-role-preset.js +265 -0
  36. package/vendor/common-nodes/dist/ai/features/llm-session/llm-session-shell.d.ts +148 -0
  37. package/vendor/common-nodes/dist/ai/features/llm-session/llm-session-shell.js +198 -0
  38. package/vendor/common-nodes/dist/ai/features/llm-session/run-session-machine.d.ts +21 -0
  39. package/vendor/common-nodes/dist/ai/features/llm-session/run-session-machine.js +118 -0
  40. package/vendor/common-nodes/dist/ai/features/openai/context-length-error.d.ts +14 -0
  41. package/vendor/common-nodes/dist/ai/features/openai/context-length-error.js +63 -0
  42. package/vendor/common-nodes/dist/ai/features/openai/create-chat-completion-stream.d.ts +19 -0
  43. package/vendor/common-nodes/dist/ai/features/openai/create-chat-completion-stream.js +182 -0
  44. package/vendor/common-nodes/dist/ai/features/openai/list-provider-models.d.ts +28 -0
  45. package/vendor/common-nodes/dist/ai/features/openai/list-provider-models.js +52 -0
  46. package/vendor/common-nodes/dist/ai/features/openai/llm-context-compaction.d.ts +31 -0
  47. package/vendor/common-nodes/dist/ai/features/openai/llm-context-compaction.js +326 -0
  48. package/vendor/common-nodes/dist/ai/features/openai/normalize-compaction-params.d.ts +14 -0
  49. package/vendor/common-nodes/dist/ai/features/openai/normalize-compaction-params.js +21 -0
  50. package/vendor/common-nodes/dist/ai/features/openai/prepare-chat-completion.d.ts +30 -0
  51. package/vendor/common-nodes/dist/ai/features/openai/prepare-chat-completion.js +132 -0
  52. package/vendor/common-nodes/dist/ai/features/path-choice/control-tools.d.ts +20 -0
  53. package/vendor/common-nodes/dist/ai/features/path-choice/control-tools.js +70 -0
  54. package/vendor/common-nodes/dist/ai/features/path-choice/run-reactive-path-choice-loop.d.ts +35 -0
  55. package/vendor/common-nodes/dist/ai/features/path-choice/run-reactive-path-choice-loop.js +106 -0
  56. package/vendor/common-nodes/dist/ai/features/prompt/build-effective-system-prompt.d.ts +9 -0
  57. package/vendor/common-nodes/dist/ai/features/prompt/build-effective-system-prompt.js +21 -0
  58. package/vendor/common-nodes/dist/ai/features/prompt/normalize-max-iterations.d.ts +13 -0
  59. package/vendor/common-nodes/dist/ai/features/prompt/normalize-max-iterations.js +23 -0
  60. package/vendor/common-nodes/dist/ai/features/prompt/resolve-chat-provider-model.d.ts +10 -0
  61. package/vendor/common-nodes/dist/ai/features/prompt/resolve-chat-provider-model.js +8 -0
  62. package/vendor/common-nodes/dist/ai/features/run-host-services.d.ts +54 -0
  63. package/vendor/common-nodes/dist/ai/features/run-host-services.js +29 -0
  64. package/vendor/common-nodes/dist/ai/features/scripted-chat-completion-stream.d.ts +17 -0
  65. package/vendor/common-nodes/dist/ai/features/scripted-chat-completion-stream.js +61 -0
  66. package/vendor/common-nodes/dist/ai/features/sub-agent-protocol.d.ts +39 -0
  67. package/vendor/common-nodes/dist/ai/features/sub-agent-protocol.js +43 -0
  68. package/vendor/common-nodes/dist/ai/features/ui-schema/llm-compaction-ui-schema.d.ts +14 -0
  69. package/vendor/common-nodes/dist/ai/features/ui-schema/llm-compaction-ui-schema.js +18 -0
  70. package/vendor/common-nodes/dist/ai/features/ui-schema/llm-panel-ui-schema.d.ts +80 -0
  71. package/vendor/common-nodes/dist/ai/features/ui-schema/llm-panel-ui-schema.js +76 -0
  72. package/vendor/common-nodes/dist/ai/features/ui-schema/llm-recovery-ui-schema.d.ts +113 -0
  73. package/vendor/common-nodes/dist/ai/features/ui-schema/llm-recovery-ui-schema.js +131 -0
  74. package/vendor/common-nodes/dist/ai/features/wait-for-subagent-result.d.ts +6 -0
  75. package/vendor/common-nodes/dist/ai/features/wait-for-subagent-result.js +30 -0
  76. package/vendor/common-nodes/dist/ai/llm-loop/autokick-recovery.d.ts +13 -0
  77. package/vendor/common-nodes/dist/ai/llm-loop/autokick-recovery.js +17 -0
  78. package/vendor/common-nodes/dist/ai/llm-loop/dead-loop-detector.d.ts +28 -0
  79. package/vendor/common-nodes/dist/ai/llm-loop/dead-loop-detector.js +194 -0
  80. package/vendor/common-nodes/dist/ai/llm-loop/llm-loop-reducer.js +36 -0
  81. package/vendor/common-nodes/dist/ai/llm-loop/llm-loop-types.d.ts +35 -0
  82. package/vendor/common-nodes/dist/ai/llm-loop/llm-loop-types.js +19 -0
  83. package/vendor/common-nodes/dist/ai/llm-loop/normalize-llm-recovery-policy.js +52 -9
  84. package/vendor/common-nodes/dist/ai/llm-loop/operators/observe-provider-stream.d.ts +8 -2
  85. package/vendor/common-nodes/dist/ai/llm-loop/operators/observe-provider-stream.js +43 -13
  86. package/vendor/common-nodes/dist/ai/llm-loop/run-llm-loop.d.ts +3 -5
  87. package/vendor/common-nodes/dist/ai/llm-loop/run-llm-loop.js +116 -1
  88. package/vendor/common-nodes/dist/ai/llm-recovery-ui-schema.d.ts +82 -0
  89. package/vendor/common-nodes/dist/ai/llm-recovery-ui-schema.js +95 -0
  90. package/vendor/common-nodes/dist/ai/llm-session-shell.d.ts +4 -9
  91. package/vendor/common-nodes/dist/ai/llm-session-shell.js +2 -4
  92. package/vendor/common-nodes/dist/ai/nodes/critique/node.d.ts +588 -0
  93. package/vendor/common-nodes/dist/ai/nodes/critique/node.js +240 -0
  94. package/vendor/common-nodes/dist/ai/nodes/fake-llm/node.d.ts +571 -0
  95. package/vendor/common-nodes/dist/ai/nodes/fake-llm/node.js +208 -0
  96. package/vendor/common-nodes/dist/ai/nodes/openai-llm/node.d.ts +587 -0
  97. package/vendor/common-nodes/dist/ai/nodes/openai-llm/node.js +91 -0
  98. package/vendor/common-nodes/dist/ai/nodes/review/node.d.ts +593 -0
  99. package/vendor/common-nodes/dist/ai/nodes/review/node.js +240 -0
  100. package/vendor/common-nodes/dist/ai/nodes/sub-agent/node.d.ts +630 -0
  101. package/vendor/common-nodes/dist/ai/nodes/sub-agent/node.js +302 -0
  102. package/vendor/common-nodes/dist/ai/openai/create-chat-completion-stream.js +6 -0
  103. package/vendor/common-nodes/dist/ai/openai/prepare-chat-completion.d.ts +2 -0
  104. package/vendor/common-nodes/dist/ai/openai/prepare-chat-completion.js +18 -0
  105. package/vendor/common-nodes/dist/ai/openai-llm/node.d.ts +246 -0
  106. package/vendor/common-nodes/dist/ai/review/node.d.ts +246 -0
  107. package/vendor/common-nodes/dist/ai/review/node.js +2 -2
  108. package/vendor/common-nodes/dist/ai/sub-agent/node.d.ts +246 -0
  109. package/vendor/common-nodes/dist/ai/sub-agent/node.js +2 -2
  110. package/vendor/common-nodes/dist/catalog.js +7 -5
  111. package/vendor/common-nodes/dist/compile-custom-nodes/node.d.ts +21 -0
  112. package/vendor/common-nodes/dist/compile-custom-nodes/node.js +45 -0
  113. package/vendor/common-nodes/dist/crawl/crawl/node.js +1 -1
  114. package/vendor/common-nodes/dist/crawl/fetch-url/node.js +1 -1
  115. package/vendor/common-nodes/dist/flow/repeat/node.js +6 -1
  116. package/vendor/common-nodes/dist/langflower-tools/emit-registration-tools.d.ts +18 -0
  117. package/vendor/common-nodes/dist/langflower-tools/emit-registration-tools.js +29 -0
  118. package/vendor/common-nodes/dist/langflower-tools/node.d.ts +20 -0
  119. package/vendor/common-nodes/dist/langflower-tools/node.js +77 -0
  120. package/vendor/common-nodes/dist/text/append-file/node.js +1 -1
  121. package/vendor/common-nodes/dist/text/read-file/node.js +1 -1
  122. package/vendor/common-nodes/dist/text/write-file/node.js +1 -1
  123. package/vendor/common-nodes/dist/tools/inventory-tool-round.d.ts +2 -2
  124. package/vendor/common-nodes/package.json +10 -10
  125. package/vendor/compiler/dist/compile-project-nodes.d.ts +2 -1
  126. package/vendor/compiler/dist/compile-project-nodes.js +35 -6
  127. package/vendor/compiler/dist/resolve-host-types.d.ts +0 -5
  128. package/vendor/compiler/dist/resolve-host-types.js +0 -8
  129. package/vendor/node-sdk/dist/node-factory/define-llm-node/define-llm-node.d.ts +1 -1
  130. package/vendor/node-sdk/dist/node-factory/define-llm-node/define-llm-node.js +1 -1
  131. package/vendor/node-sdk/dist/node-factory/define-llm-node/recovery-notice.d.ts +8 -0
  132. package/vendor/node-sdk/dist/node-factory/define-llm-node/recovery-notice.js +14 -0
  133. package/vendor/runtime/dist/runtime-editor.d.ts +5 -1
  134. package/vendor/runtime/dist/runtime-editor.js +19 -0
  135. package/vendor/runtime/dist/runtime-runner.js +17 -17
  136. package/vendor/runtime/dist/runtime.d.ts +2 -1
  137. package/vendor/runtime/dist/runtime.js +1 -0
  138. package/vendor/runtime/dist/testing/workflows/workflow-events.d.ts +8 -6
  139. package/vendor/runtime/dist/testing/workflows/workflow-events.js +19 -31
  140. package/vendor/runtime/dist/types.d.ts +33 -38
  141. package/vendor/runtime/dist/types.js +2 -1
  142. package/vendor/server/dist/bridge/attach-langflower-bridge.js +3 -2
  143. package/vendor/server/dist/bridge/bridge-event-log.js +17 -102
  144. package/vendor/server/dist/bridge/build-execution-context.d.ts +5 -2
  145. package/vendor/server/dist/bridge/build-execution-context.js +45 -5
  146. package/vendor/server/dist/bridge/forward-runner-event.d.ts +1 -1
  147. package/vendor/server/dist/bridge/forward-runner-event.js +7 -12
  148. package/vendor/server/dist/bridge/get-live-wired-tools.d.ts +12 -0
  149. package/vendor/server/dist/bridge/get-live-wired-tools.js +80 -0
  150. package/vendor/server/dist/bridge/langflower-tools-rpc.d.ts +7 -0
  151. package/vendor/server/dist/bridge/langflower-tools-rpc.js +29 -0
  152. package/vendor/server/dist/bridge/settings-draft-controller.js +26 -3
  153. package/vendor/server/dist/bridge/wire-custom-palette-handlers.d.ts +2 -1
  154. package/vendor/server/dist/bridge/wire-custom-palette-handlers.js +3 -5
  155. package/vendor/server/dist/bridge/wire-runner-handlers.js +15 -8
  156. package/vendor/server/dist/checkpoint/run-checkpoint-session.d.ts +1 -1
  157. package/vendor/server/dist/checkpoint/run-checkpoint-session.js +10 -9
  158. package/vendor/server/dist/palette/compile-and-hot-swap-custom-nodes.d.ts +10 -0
  159. package/vendor/server/dist/palette/compile-and-hot-swap-custom-nodes.js +23 -0
  160. package/vendor/server/dist/workflow/apply-editor-mutation.d.ts +5 -0
  161. package/vendor/server/dist/workflow/apply-editor-mutation.js +32 -0
  162. package/vendor/server/skeleton/instructions.md +9 -5
  163. package/vendor/server/skeleton/nodes/my-nodes/README.md +4 -3
  164. package/vendor/server/skeleton/schemas/langflower-config.schema.json +11 -1
  165. package/vendor/server/skeleton/skills/langflower-helper/SKILL.md +49 -19
  166. package/vendor/server/skeleton/skills/langflower-helper/architecture.md +22 -9
  167. package/vendor/server/skeleton/skills/langflower-helper/layout.md +15 -15
  168. package/vendor/server/skeleton/skills/langflower-node-writer/SKILL.md +19 -9
  169. package/vendor/server/skeleton/skills/langflower-workflow-writer/SKILL.md +14 -10
  170. package/vendor/server/skeleton/workflows/starter.json +30 -3
  171. package/vendor/shared/dist/execution/derive-run-settle-outcome.d.ts +1 -1
  172. package/vendor/shared/dist/execution/derive-run-settle-outcome.js +4 -3
  173. package/vendor/shared/dist/execution/derive-run-settle-outcome.test.js +9 -9
  174. package/vendor/shared/dist/langflower-bus-config.d.ts +6 -14
  175. package/vendor/shared/dist/langflower-bus-config.js +4 -12
  176. package/vendor/shared/dist/langflower-config/resolve-wired-tool-options.js +6 -0
  177. package/vendor/shared/dist/langflower-config/resolve-wired-tool-options.test.js +30 -0
  178. package/vendor/shared/dist/langflower-ws-waits.d.ts +7 -14
  179. package/vendor/shared/dist/langflower-ws-waits.js +14 -14
  180. package/vendor/websocket-bridge/dist/bridge-codec.d.ts +1 -0
  181. package/vendor/websocket-bridge/dist/bridge-codec.js +11 -11
  182. package/vendor/websocket-bridge/dist/bridge-frame.js +1 -1
  183. package/vendor/websocket-bridge/dist/bridge-subjects.d.ts +1 -1
  184. package/vendor/websocket-bridge/dist/bridge-subjects.js +2 -2
  185. package/vendor/websocket-bridge/dist/bridge-types.d.ts +13 -1
  186. package/vendor/websocket-bridge/dist/create-client.js +3 -3
  187. package/vendor/websocket-bridge/dist/create-server.js +12 -6
  188. package/vendor/websocket-bridge/package.json +8 -0
  189. package/ui-dist/main-SMO3L7U5.js +0 -311
@@ -1,3 +1,4 @@
1
+ export const DEFAULT_AUTOKICK_USER_MESSAGE = 'I notice you are repeating yourself. Please stop and provide a concise answer.';
1
2
  export const DEFAULT_LLM_RECOVERY_POLICY = {
2
3
  streamIdleTimeoutMs: 90_000,
3
4
  toolTimeoutMs: 60_000,
@@ -5,6 +6,22 @@ export const DEFAULT_LLM_RECOVERY_POLICY = {
5
6
  maxTransientRetries: 2,
6
7
  retryBaseDelayMs: 1_000,
7
8
  maxToolResultChars: 40_000,
9
+ autokickOnIdle: true,
10
+ deadLoopEnabled: true,
11
+ maxAutokickAttempts: 0,
12
+ autokickBackoffMs: 60_000,
13
+ autokickMaxBackoffMs: 960_000,
14
+ autokickUserMessage: DEFAULT_AUTOKICK_USER_MESSAGE,
15
+ autokickPenaltyDelta: {
16
+ frequency: 0.3,
17
+ presence: 0.3,
18
+ },
19
+ deadLoop: {
20
+ maxWindowTokens: 1_000,
21
+ consecutiveThreshold: 5,
22
+ minRepetitions: 3,
23
+ minPatternTokens: 2,
24
+ },
8
25
  };
9
26
  export const initialLlmLoopState = (messages) => ({
10
27
  phase: 'prepare',
@@ -17,4 +34,6 @@ export const initialLlmLoopState = (messages) => ({
17
34
  draft: '',
18
35
  },
19
36
  pendingToolCalls: [],
37
+ autokickAttempts: 0,
38
+ autokickKickAttempts: 0,
20
39
  });
@@ -1,4 +1,4 @@
1
- import { DEFAULT_LLM_RECOVERY_POLICY, } from './llm-loop-types.js';
1
+ import { DEFAULT_AUTOKICK_USER_MESSAGE, DEFAULT_LLM_RECOVERY_POLICY, } from './llm-loop-types.js';
2
2
  const normalizePositiveInteger = (value, fallback, max) => {
3
3
  const number = typeof value === 'number' ? value : Number(value);
4
4
  if (!Number.isFinite(number) || number < 0) {
@@ -6,11 +6,54 @@ const normalizePositiveInteger = (value, fallback, max) => {
6
6
  }
7
7
  return Math.min(max, Math.floor(number));
8
8
  };
9
- export const normalizeLlmRecoveryPolicy = (params) => ({
10
- streamIdleTimeoutMs: normalizePositiveInteger(params['streamIdleTimeoutMs'], DEFAULT_LLM_RECOVERY_POLICY.streamIdleTimeoutMs, 3_600_000),
11
- toolTimeoutMs: normalizePositiveInteger(params['toolTimeoutMs'], DEFAULT_LLM_RECOVERY_POLICY.toolTimeoutMs, 3_600_000),
12
- subagentTimeoutMs: normalizePositiveInteger(params['subagentTimeoutMs'], DEFAULT_LLM_RECOVERY_POLICY.subagentTimeoutMs, 86_400_000),
13
- maxTransientRetries: normalizePositiveInteger(params['maxTransientRetries'], DEFAULT_LLM_RECOVERY_POLICY.maxTransientRetries, 10),
14
- retryBaseDelayMs: normalizePositiveInteger(params['retryBaseDelayMs'], DEFAULT_LLM_RECOVERY_POLICY.retryBaseDelayMs, 60_000),
15
- maxToolResultChars: normalizePositiveInteger(params['maxToolResultChars'], DEFAULT_LLM_RECOVERY_POLICY.maxToolResultChars, 1_000_000),
16
- });
9
+ const normalizeBoolean = (value, fallback) => typeof value === 'boolean' ? value : fallback;
10
+ const normalizePenalty = (value, fallback) => {
11
+ const number = typeof value === 'number' ? value : Number(value);
12
+ if (!Number.isFinite(number)) {
13
+ return fallback;
14
+ }
15
+ return Math.min(2, Math.max(-2, number));
16
+ };
17
+ const readRecord = (value) => value !== null && typeof value === 'object' && !Array.isArray(value)
18
+ ? value
19
+ : undefined;
20
+ const nestedDeadLoop = (params) => readRecord(params['deadLoop']) ?? {};
21
+ const nestedPenalty = (params) => readRecord(params['autokickPenaltyDelta']) ?? {};
22
+ export const normalizeLlmRecoveryPolicy = (params) => {
23
+ const deadLoop = nestedDeadLoop(params);
24
+ const penalty = nestedPenalty(params);
25
+ const autokickBackoffMs = Math.max(1_000, normalizePositiveInteger(params['autokickBackoffMs'], DEFAULT_LLM_RECOVERY_POLICY.autokickBackoffMs, 3_600_000));
26
+ const autokickMaxBackoffMs = Math.max(autokickBackoffMs, normalizePositiveInteger(params['autokickMaxBackoffMs'], DEFAULT_LLM_RECOVERY_POLICY.autokickMaxBackoffMs, 3_600_000));
27
+ const kick = typeof params['autokickUserMessage'] === 'string' &&
28
+ params['autokickUserMessage'].trim().length > 0
29
+ ? params['autokickUserMessage'].trim()
30
+ : DEFAULT_AUTOKICK_USER_MESSAGE;
31
+ return {
32
+ streamIdleTimeoutMs: normalizePositiveInteger(params['streamIdleTimeoutMs'], DEFAULT_LLM_RECOVERY_POLICY.streamIdleTimeoutMs, 3_600_000),
33
+ toolTimeoutMs: normalizePositiveInteger(params['toolTimeoutMs'], DEFAULT_LLM_RECOVERY_POLICY.toolTimeoutMs, 3_600_000),
34
+ subagentTimeoutMs: normalizePositiveInteger(params['subagentTimeoutMs'], DEFAULT_LLM_RECOVERY_POLICY.subagentTimeoutMs, 86_400_000),
35
+ maxTransientRetries: normalizePositiveInteger(params['maxTransientRetries'], DEFAULT_LLM_RECOVERY_POLICY.maxTransientRetries, 10),
36
+ retryBaseDelayMs: normalizePositiveInteger(params['retryBaseDelayMs'], DEFAULT_LLM_RECOVERY_POLICY.retryBaseDelayMs, 60_000),
37
+ maxToolResultChars: normalizePositiveInteger(params['maxToolResultChars'], DEFAULT_LLM_RECOVERY_POLICY.maxToolResultChars, 1_000_000),
38
+ autokickOnIdle: normalizeBoolean(params['autokickOnIdle'], DEFAULT_LLM_RECOVERY_POLICY.autokickOnIdle),
39
+ deadLoopEnabled: normalizeBoolean(params['deadLoopEnabled'], DEFAULT_LLM_RECOVERY_POLICY.deadLoopEnabled),
40
+ maxAutokickAttempts: normalizePositiveInteger(params['maxAutokickAttempts'], DEFAULT_LLM_RECOVERY_POLICY.maxAutokickAttempts, 1_000),
41
+ autokickBackoffMs,
42
+ autokickMaxBackoffMs,
43
+ autokickUserMessage: kick,
44
+ autokickPenaltyDelta: {
45
+ frequency: normalizePenalty(penalty['frequency'] ?? params['autokickPenaltyFrequency'], DEFAULT_LLM_RECOVERY_POLICY.autokickPenaltyDelta.frequency),
46
+ presence: normalizePenalty(penalty['presence'] ?? params['autokickPenaltyPresence'], DEFAULT_LLM_RECOVERY_POLICY.autokickPenaltyDelta.presence),
47
+ },
48
+ deadLoop: {
49
+ maxWindowTokens: Math.min(8_000, Math.max(10, normalizePositiveInteger(deadLoop['maxWindowTokens'] ??
50
+ params['deadLoopMaxWindowTokens'], DEFAULT_LLM_RECOVERY_POLICY.deadLoop.maxWindowTokens, 8_000))),
51
+ consecutiveThreshold: Math.max(1, normalizePositiveInteger(deadLoop['consecutiveThreshold'] ??
52
+ params['deadLoopConsecutiveThreshold'], DEFAULT_LLM_RECOVERY_POLICY.deadLoop.consecutiveThreshold, 100)),
53
+ minRepetitions: Math.max(1, normalizePositiveInteger(deadLoop['minRepetitions'] ??
54
+ params['deadLoopMinRepetitions'], DEFAULT_LLM_RECOVERY_POLICY.deadLoop.minRepetitions, 20)),
55
+ minPatternTokens: Math.max(1, normalizePositiveInteger(deadLoop['minPatternTokens'] ??
56
+ params['deadLoopMinPatternTokens'], DEFAULT_LLM_RECOVERY_POLICY.deadLoop.minPatternTokens, 100)),
57
+ },
58
+ };
59
+ };
@@ -1,6 +1,7 @@
1
1
  import type { SteerControlPayload } from '@langflower/node-sdk/llm';
2
2
  import { type Observable } from 'rxjs';
3
3
  import type { ChatCompletionFinishReason, ChatCompletionStreamChunk, ChatCompletionToolCall } from '../../chat-completion-stream.js';
4
+ import { type DeadLoopChannel, type DeadLoopDetectorOptions } from '../dead-loop-detector.js';
4
5
  import type { LlmFailure } from '../llm-loop-types.js';
5
6
  export type ProviderStreamFact = {
6
7
  readonly kind: 'provider.reasoning';
@@ -18,6 +19,10 @@ export type ProviderStreamFact = {
18
19
  } | {
19
20
  readonly kind: 'provider.idle';
20
21
  readonly idleMs: number;
22
+ } | {
23
+ readonly kind: 'provider.dead-loop';
24
+ readonly channel: DeadLoopChannel;
25
+ readonly reason: 'consecutive' | 'cyclic';
21
26
  } | {
22
27
  readonly kind: 'provider.failed';
23
28
  readonly failure: LlmFailure;
@@ -28,10 +33,11 @@ export type ObserveProviderStreamOptions = {
28
33
  readonly cancel$: Observable<void>;
29
34
  readonly idleTimeoutMs: number;
30
35
  readonly onAbort?: () => void;
36
+ readonly deadLoop?: DeadLoopDetectorOptions | false;
31
37
  };
32
38
  /**
33
39
  * Convert the provider AsyncIterable into typed, cancellable RxJS facts.
34
- * Pause and idle are terminal for one provider attempt, not terminal for the
35
- * surrounding LLM loop.
40
+ * Pause, idle, and dead-loop are terminal for one provider attempt, not
41
+ * terminal for the surrounding LLM loop.
36
42
  */
37
43
  export declare const observeProviderStream: (options: ObserveProviderStreamOptions) => Observable<ProviderStreamFact>;
@@ -1,6 +1,7 @@
1
1
  import { isSteerControlPause } from '@langflower/node-sdk/llm';
2
- import { catchError, defer, endWith, filter, finalize, from, map, merge, of, switchMap, take, takeUntil, takeWhile, tap, timeout, } from 'rxjs';
2
+ import { catchError, concatMap, defer, endWith, filter, finalize, from, map, merge, of, switchMap, take, takeUntil, takeWhile, tap, timeout, } from 'rxjs';
3
3
  import { classifyLlmFailure } from '../classify-llm-failure.js';
4
+ import { createDeadLoopDetector, } from '../dead-loop-detector.js';
4
5
  const toProviderStreamFact = (chunk) => {
5
6
  switch (chunk.kind) {
6
7
  case 'reasoning':
@@ -23,32 +24,62 @@ const toProviderStreamFact = (chunk) => {
23
24
  const isTerminalProviderFact = (fact) => fact.kind === 'provider.done' ||
24
25
  fact.kind === 'provider.paused' ||
25
26
  fact.kind === 'provider.idle' ||
27
+ fact.kind === 'provider.dead-loop' ||
26
28
  fact.kind === 'provider.failed';
27
- const withIdleTimeout = (source$, idleTimeoutMs) => {
29
+ const withIdleTimeout = (source$, idleTimeoutMs, onIdle) => {
28
30
  if (idleTimeoutMs <= 0) {
29
31
  return source$;
30
32
  }
31
33
  return source$.pipe(timeout({
32
34
  first: idleTimeoutMs,
33
35
  each: idleTimeoutMs,
34
- with: () => of({
35
- kind: 'provider.idle',
36
- idleMs: idleTimeoutMs,
37
- }),
36
+ with: () => {
37
+ onIdle();
38
+ return of({
39
+ kind: 'provider.idle',
40
+ idleMs: idleTimeoutMs,
41
+ });
42
+ },
38
43
  }));
39
44
  };
45
+ const guardDeadLoop = (fact, detectors, abort) => {
46
+ if (detectors === undefined ||
47
+ (fact.kind !== 'provider.reasoning' && fact.kind !== 'provider.draft')) {
48
+ return of(fact);
49
+ }
50
+ const channel = fact.kind === 'provider.reasoning' ? 'reasoning' : 'draft';
51
+ const detected = detectors[channel].push(fact.text);
52
+ if (detected.ok) {
53
+ return of(fact);
54
+ }
55
+ abort();
56
+ return of(fact, {
57
+ kind: 'provider.dead-loop',
58
+ channel: detected.error.channel,
59
+ reason: detected.error.reason,
60
+ });
61
+ };
40
62
  /**
41
63
  * Convert the provider AsyncIterable into typed, cancellable RxJS facts.
42
- * Pause and idle are terminal for one provider attempt, not terminal for the
43
- * surrounding LLM loop.
64
+ * Pause, idle, and dead-loop are terminal for one provider attempt, not
65
+ * terminal for the surrounding LLM loop.
44
66
  */
45
67
  export const observeProviderStream = (options) => defer(() => {
46
68
  const abort = new AbortController();
47
- const pause$ = options.pause$.pipe(filter(isSteerControlPause), take(1), tap(() => {
69
+ const stopProvider = () => {
48
70
  abort.abort();
49
71
  options.onAbort?.();
72
+ };
73
+ const detectors = options.deadLoop === false
74
+ ? undefined
75
+ : {
76
+ reasoning: createDeadLoopDetector('reasoning', options.deadLoop ?? {}),
77
+ draft: createDeadLoopDetector('draft', options.deadLoop ?? {}),
78
+ };
79
+ const pause$ = options.pause$.pipe(filter(isSteerControlPause), take(1), tap(() => {
80
+ stopProvider();
50
81
  }), map(() => ({ kind: 'provider.paused' })));
51
- const chunks$ = withIdleTimeout(from(options.createStream(abort.signal)).pipe(switchMap((stream) => from(stream)), map(toProviderStreamFact), catchError((error) => of({
82
+ const chunks$ = withIdleTimeout(from(options.createStream(abort.signal)).pipe(switchMap((stream) => from(stream)), concatMap((chunk) => guardDeadLoop(toProviderStreamFact(chunk), detectors, stopProvider)), catchError((error) => of({
52
83
  kind: 'provider.failed',
53
84
  failure: classifyLlmFailure(error),
54
85
  })), endWith({
@@ -58,9 +89,8 @@ export const observeProviderStream = (options) => defer(() => {
58
89
  message: 'Provider stream completed without a done chunk.',
59
90
  recoverable: true,
60
91
  },
61
- })), options.idleTimeoutMs);
92
+ })), options.idleTimeoutMs, stopProvider);
62
93
  return merge(chunks$, pause$).pipe(takeWhile((fact) => !isTerminalProviderFact(fact), true), takeUntil(options.cancel$), finalize(() => {
63
- abort.abort();
64
- options.onAbort?.();
94
+ stopProvider();
65
95
  }));
66
96
  });
@@ -1,5 +1,5 @@
1
1
  import type { ToolHandle } from '@langflower/node-sdk';
2
- import { type SteerControlPayload } from '@langflower/node-sdk/llm';
2
+ import { type LlmRecoveryNotice, type SteerControlPayload } from '@langflower/node-sdk/llm';
3
3
  import type { Harness } from '@langflower/tools/create-project-harness';
4
4
  import type { ToolHandlerContext } from '@langflower/tools/domain-tool-configs';
5
5
  import type { PermissionAskRequest } from '@langflower/tools/permission';
@@ -17,11 +17,9 @@ export type SharedLlmLoopChunk = {
17
17
  } | {
18
18
  readonly kind: 'toolLog';
19
19
  readonly text: string;
20
- } | {
20
+ } | ({
21
21
  readonly kind: 'recoveryNotice';
22
- readonly code: 'retry' | 'suspended';
23
- readonly text: string;
24
- } | {
22
+ } & LlmRecoveryNotice) | {
25
23
  readonly kind: 'historySync';
26
24
  readonly messages: readonly ChatCompletionMessage[];
27
25
  } | {
@@ -4,6 +4,7 @@ import { invokeInventoryTool, previewToolLogText, resolveSpawnPayload, SPAWN_SUB
4
4
  import { prepareChatCompletion } from '../openai/prepare-chat-completion.js';
5
5
  import { compactMessagesWithSummary, estimateRequestTokens, resolveForceTargetTokens, } from '../openai/llm-context-compaction.js';
6
6
  import { classifyLlmFailure } from './classify-llm-failure.js';
7
+ import { autokickBackoffMs, autokickPenalties } from './autokick-recovery.js';
7
8
  import { reduceLlmLoop } from './llm-loop-reducer.js';
8
9
  import { initialLlmLoopState, } from './llm-loop-types.js';
9
10
  import { observeProviderStream, } from './operators/observe-provider-stream.js';
@@ -17,6 +18,10 @@ const failureText = (failure) => {
17
18
  const status = failure.status === undefined ? '' : ` HTTP ${failure.status}`;
18
19
  return `Provider failure${status}: ${failure.message}`;
19
20
  };
21
+ const joinsAutokickWait = (kind) => kind === 'rate-limit' ||
22
+ kind === 'provider-unavailable' ||
23
+ kind === 'network';
24
+ const retryReasonFromFailure = (kind) => joinsAutokickWait(kind) ? kind : undefined;
20
25
  const isIncompleteToolCallJson = (calls) => {
21
26
  for (const call of calls) {
22
27
  const args = call.arguments.trim();
@@ -39,6 +44,61 @@ const isOutputTruncation = (fact) => {
39
44
  const calls = fact.toolCalls ?? [];
40
45
  return calls.length > 0 && isIncompleteToolCallJson(calls);
41
46
  };
47
+ const canScheduleAutokick = (state, recovery) => recovery.autokickOnIdle &&
48
+ (recovery.maxAutokickAttempts === 0 ||
49
+ state.autokickAttempts < recovery.maxAutokickAttempts);
50
+ const autokickPackets = (state, options, args) => {
51
+ if (!canScheduleAutokick(state, options.recovery)) {
52
+ return concat(of(emit({
53
+ kind: 'recoveryNotice',
54
+ code: 'suspended',
55
+ text: args.suspendedText,
56
+ })), of(transition(state)));
57
+ }
58
+ const attempt = state.autokickAttempts + 1;
59
+ const backoffMs = args.reason === 'dead-loop'
60
+ ? options.recovery.retryBaseDelayMs
61
+ : Math.max(autokickBackoffMs(attempt, options.recovery.autokickBackoffMs, options.recovery.autokickMaxBackoffMs), args.retryAfterMs ?? 0);
62
+ const nowMs = Date.now();
63
+ const interrupt$ = (options.steerControl$ ?? NEVER).pipe(filter((payload) => isSteerControlPause(payload) || payload.kind === 'steer'), take(1), map((payload) => ({ kind: 'interrupt', payload })));
64
+ return concat(of(emit({
65
+ kind: 'recoveryNotice',
66
+ code: 'retry',
67
+ text: `⚠ ${args.retryDetail}. Retrying ${attempt} in ${backoffMs}ms.`,
68
+ attempt,
69
+ reason: args.reason,
70
+ backoffMs,
71
+ nextAttemptAt: nowMs + backoffMs,
72
+ ...(state.lastAutokickAt !== undefined
73
+ ? { lastAttemptAt: state.lastAutokickAt }
74
+ : {}),
75
+ })), race(of({ kind: 'elapsed' }).pipe(delay(backoffMs)), interrupt$).pipe(switchMap((event) => {
76
+ if (event.kind === 'interrupt') {
77
+ if (isSteerControlPause(event.payload)) {
78
+ return concat(of(emit({
79
+ kind: 'toolLog',
80
+ text: 'Paused. Send Steer feedback or Resume to continue.',
81
+ })), of(transition(reduceLlmLoop(state, {
82
+ type: 'stream.paused',
83
+ }))));
84
+ }
85
+ const text = event.payload.kind === 'steer'
86
+ ? event.payload.text
87
+ : undefined;
88
+ return of(transition(reduceLlmLoop(state, {
89
+ type: 'steer.received',
90
+ ...(text !== undefined ? { text } : {}),
91
+ })));
92
+ }
93
+ return of(transition(reduceLlmLoop(state, {
94
+ type: 'autokick.scheduled',
95
+ atMs: Date.now(),
96
+ ...(args.kickUserMessage !== undefined
97
+ ? { kickUserMessage: args.kickUserMessage }
98
+ : {}),
99
+ })));
100
+ })));
101
+ };
42
102
  const recoveryPackets = (state, failure, options) => {
43
103
  if (!failure.recoverable) {
44
104
  return concat(of(emit({
@@ -65,12 +125,31 @@ const recoveryPackets = (state, failure, options) => {
65
125
  const delayMs = failure.retryAfterMs ??
66
126
  options.recovery.retryBaseDelayMs * 2 ** (attempt - 1);
67
127
  const next = reduceLlmLoop(state, { type: 'retry.scheduled' });
128
+ const reason = retryReasonFromFailure(failure.kind);
68
129
  return concat(of(emit({
69
130
  kind: 'recoveryNotice',
70
131
  code: 'retry',
71
132
  text: `⚠ ${failureText(failure)}. Retrying ${attempt}/${options.recovery.maxTransientRetries} in ${delayMs}ms.`,
133
+ attempt,
134
+ backoffMs: delayMs,
135
+ nextAttemptAt: Date.now() + delayMs,
136
+ ...(reason !== undefined ? { reason } : {}),
137
+ ...(state.lastAutokickAt !== undefined
138
+ ? { lastAttemptAt: state.lastAutokickAt }
139
+ : {}),
72
140
  })), of(transition(next)).pipe(delay(delayMs)));
73
141
  }
142
+ if (joinsAutokickWait(failure.kind) &&
143
+ canScheduleAutokick(state, options.recovery)) {
144
+ return autokickPackets(state, options, {
145
+ retryDetail: failureText(failure),
146
+ suspendedText: `⚠ ${failureText(failure)}. Retry budget exhausted; paused for Steer or Resume.`,
147
+ reason: failure.kind,
148
+ ...(failure.retryAfterMs !== undefined
149
+ ? { retryAfterMs: failure.retryAfterMs }
150
+ : {}),
151
+ });
152
+ }
74
153
  return concat(of(emit({
75
154
  kind: 'recoveryNotice',
76
155
  code: 'suspended',
@@ -165,6 +244,12 @@ const providerFactAction = (fact) => {
165
244
  return { type: 'stream.paused' };
166
245
  case 'provider.idle':
167
246
  return { type: 'stream.idle', idleMs: fact.idleMs };
247
+ case 'provider.dead-loop':
248
+ return {
249
+ type: 'stream.dead-loop',
250
+ channel: fact.channel,
251
+ reason: fact.reason,
252
+ };
168
253
  case 'provider.failed':
169
254
  return undefined;
170
255
  }
@@ -198,14 +283,32 @@ const providerFactPackets = (frame, options) => {
198
283
  };
199
284
  const hasPartial = frame.state.partial.reasoning.length > 0 ||
200
285
  frame.state.partial.draft.length > 0;
286
+ const suspendedText = hasPartial
287
+ ? `⚠ ${failure.message} Paused to avoid repeating partial work.`
288
+ : `⚠ ${failure.message}. Retry budget exhausted; paused for Steer or Resume.`;
289
+ if (options.recovery.autokickOnIdle) {
290
+ return autokickPackets(frame.state, options, {
291
+ retryDetail: failure.message,
292
+ suspendedText,
293
+ reason: 'idle',
294
+ kickUserMessage: options.recovery.autokickUserMessage,
295
+ });
296
+ }
201
297
  return hasPartial
202
298
  ? concat(of(emit({
203
299
  kind: 'recoveryNotice',
204
300
  code: 'suspended',
205
- text: `⚠ ${failure.message} Paused to avoid repeating partial work.`,
301
+ text: suspendedText,
206
302
  })), of(transition(frame.state)))
207
303
  : recoveryPackets(frame.state, failure, options);
208
304
  }
305
+ case 'provider.dead-loop':
306
+ return autokickPackets(frame.state, options, {
307
+ retryDetail: 'Provider output entered a repetition loop',
308
+ suspendedText: '⚠ Provider output entered a repetition loop. Paused to avoid repeating partial work.',
309
+ reason: 'dead-loop',
310
+ kickUserMessage: options.recovery.autokickUserMessage,
311
+ });
209
312
  case 'provider.failed':
210
313
  return recoveryPackets(frame.state, fact.failure, options);
211
314
  case 'provider.done': {
@@ -292,6 +395,9 @@ const prepareAndStream = (state, options, cancel$, cancelSignal) => {
292
395
  const attempt = new AbortController();
293
396
  const handleCancel = () => attempt.abort();
294
397
  cancelSignal.addEventListener('abort', handleCancel, { once: true });
398
+ const penalties = state.autokickKickAttempts > 0
399
+ ? autokickPenalties(state.autokickKickAttempts, options.recovery.autokickPenaltyDelta)
400
+ : undefined;
295
401
  const preparationSource$ = from(prepareChatCompletion({
296
402
  factory: options.factory,
297
403
  providerId: options.providerId,
@@ -300,6 +406,12 @@ const prepareAndStream = (state, options, cancel$, cancelSignal) => {
300
406
  tools: options.chatTools,
301
407
  signal: attempt.signal,
302
408
  compaction: options.compaction,
409
+ ...(penalties === undefined
410
+ ? {}
411
+ : {
412
+ frequency_penalty: penalties.frequency,
413
+ presence_penalty: penalties.presence,
414
+ }),
303
415
  })).pipe(map((prepared) => ({ kind: 'prepared', prepared })), catchError((error) => of({ kind: 'failed', error })));
304
416
  const preparation$ = options.recovery.streamIdleTimeoutMs > 0
305
417
  ? preparationSource$.pipe(timeout({
@@ -360,6 +472,9 @@ const prepareAndStream = (state, options, cancel$, cancelSignal) => {
360
472
  cancel$,
361
473
  idleTimeoutMs: options.recovery.streamIdleTimeoutMs,
362
474
  onAbort: () => attempt.abort(),
475
+ deadLoop: options.recovery.deadLoopEnabled
476
+ ? options.recovery.deadLoop
477
+ : false,
363
478
  }).pipe(scan((frame, fact) => {
364
479
  const action = providerFactAction(fact);
365
480
  return {
@@ -28,4 +28,86 @@ export declare const llmRecoveryUiSchema: readonly [{
28
28
  readonly min: 0;
29
29
  readonly max: 32;
30
30
  readonly step: 1;
31
+ }, {
32
+ readonly field: "autokickOnIdle";
33
+ readonly type: "boolean";
34
+ readonly label: "Autokick on idle and dead-loop";
35
+ readonly default: boolean;
36
+ }, {
37
+ readonly field: "deadLoopEnabled";
38
+ readonly type: "boolean";
39
+ readonly label: "Detect repetition dead loops";
40
+ readonly default: boolean;
41
+ }, {
42
+ readonly field: "maxAutokickAttempts";
43
+ readonly type: "number";
44
+ readonly label: "Max autokick reconnects (0 = unlimited)";
45
+ readonly default: number;
46
+ readonly min: 0;
47
+ readonly max: 1000;
48
+ readonly step: 1;
49
+ }, {
50
+ readonly field: "autokickBackoffMs";
51
+ readonly type: "number";
52
+ readonly label: "Autokick backoff base (ms)";
53
+ readonly default: number;
54
+ readonly min: 1000;
55
+ readonly step: 1000;
56
+ }, {
57
+ readonly field: "autokickMaxBackoffMs";
58
+ readonly type: "number";
59
+ readonly label: "Autokick backoff clamp (ms)";
60
+ readonly default: number;
61
+ readonly min: 1000;
62
+ readonly step: 1000;
63
+ }, {
64
+ readonly field: "autokickUserMessage";
65
+ readonly type: "string";
66
+ readonly label: "Autokick kick message";
67
+ readonly default: string;
68
+ }, {
69
+ readonly field: "autokickPenaltyFrequency";
70
+ readonly type: "number";
71
+ readonly label: "Autokick frequency_penalty delta";
72
+ readonly default: number;
73
+ readonly min: -2;
74
+ readonly max: 2;
75
+ readonly step: 0.1;
76
+ }, {
77
+ readonly field: "autokickPenaltyPresence";
78
+ readonly type: "number";
79
+ readonly label: "Autokick presence_penalty delta";
80
+ readonly default: number;
81
+ readonly min: -2;
82
+ readonly max: 2;
83
+ readonly step: 0.1;
84
+ }, {
85
+ readonly field: "deadLoopMaxWindowTokens";
86
+ readonly type: "number";
87
+ readonly label: "Dead-loop window (tokens)";
88
+ readonly default: number;
89
+ readonly min: 10;
90
+ readonly max: 8000;
91
+ readonly step: 1;
92
+ }, {
93
+ readonly field: "deadLoopConsecutiveThreshold";
94
+ readonly type: "number";
95
+ readonly label: "Dead-loop consecutive identical deltas";
96
+ readonly default: number;
97
+ readonly min: 1;
98
+ readonly step: 1;
99
+ }, {
100
+ readonly field: "deadLoopMinRepetitions";
101
+ readonly type: "number";
102
+ readonly label: "Dead-loop cyclic repetitions";
103
+ readonly default: number;
104
+ readonly min: 1;
105
+ readonly step: 1;
106
+ }, {
107
+ readonly field: "deadLoopMinPatternTokens";
108
+ readonly type: "number";
109
+ readonly label: "Dead-loop min pattern tokens";
110
+ readonly default: number;
111
+ readonly min: 1;
112
+ readonly step: 1;
31
113
  }];
@@ -1,3 +1,4 @@
1
+ import { DEFAULT_LLM_RECOVERY_POLICY } from './llm-loop/llm-loop-types.js';
1
2
  /** Shared Inspector fields for provider/tool recovery policy. */
2
3
  export const llmRecoveryUiSchema = [
3
4
  {
@@ -33,4 +34,98 @@ export const llmRecoveryUiSchema = [
33
34
  max: 32,
34
35
  step: 1,
35
36
  },
37
+ {
38
+ field: 'autokickOnIdle',
39
+ type: 'boolean',
40
+ label: 'Autokick on idle and dead-loop',
41
+ default: DEFAULT_LLM_RECOVERY_POLICY.autokickOnIdle,
42
+ },
43
+ {
44
+ field: 'deadLoopEnabled',
45
+ type: 'boolean',
46
+ label: 'Detect repetition dead loops',
47
+ default: DEFAULT_LLM_RECOVERY_POLICY.deadLoopEnabled,
48
+ },
49
+ {
50
+ field: 'maxAutokickAttempts',
51
+ type: 'number',
52
+ label: 'Max autokick reconnects (0 = unlimited)',
53
+ default: DEFAULT_LLM_RECOVERY_POLICY.maxAutokickAttempts,
54
+ min: 0,
55
+ max: 1000,
56
+ step: 1,
57
+ },
58
+ {
59
+ field: 'autokickBackoffMs',
60
+ type: 'number',
61
+ label: 'Autokick backoff base (ms)',
62
+ default: DEFAULT_LLM_RECOVERY_POLICY.autokickBackoffMs,
63
+ min: 1000,
64
+ step: 1000,
65
+ },
66
+ {
67
+ field: 'autokickMaxBackoffMs',
68
+ type: 'number',
69
+ label: 'Autokick backoff clamp (ms)',
70
+ default: DEFAULT_LLM_RECOVERY_POLICY.autokickMaxBackoffMs,
71
+ min: 1000,
72
+ step: 1000,
73
+ },
74
+ {
75
+ field: 'autokickUserMessage',
76
+ type: 'string',
77
+ label: 'Autokick kick message',
78
+ default: DEFAULT_LLM_RECOVERY_POLICY.autokickUserMessage,
79
+ },
80
+ {
81
+ field: 'autokickPenaltyFrequency',
82
+ type: 'number',
83
+ label: 'Autokick frequency_penalty delta',
84
+ default: DEFAULT_LLM_RECOVERY_POLICY.autokickPenaltyDelta.frequency,
85
+ min: -2,
86
+ max: 2,
87
+ step: 0.1,
88
+ },
89
+ {
90
+ field: 'autokickPenaltyPresence',
91
+ type: 'number',
92
+ label: 'Autokick presence_penalty delta',
93
+ default: DEFAULT_LLM_RECOVERY_POLICY.autokickPenaltyDelta.presence,
94
+ min: -2,
95
+ max: 2,
96
+ step: 0.1,
97
+ },
98
+ {
99
+ field: 'deadLoopMaxWindowTokens',
100
+ type: 'number',
101
+ label: 'Dead-loop window (tokens)',
102
+ default: DEFAULT_LLM_RECOVERY_POLICY.deadLoop.maxWindowTokens,
103
+ min: 10,
104
+ max: 8000,
105
+ step: 1,
106
+ },
107
+ {
108
+ field: 'deadLoopConsecutiveThreshold',
109
+ type: 'number',
110
+ label: 'Dead-loop consecutive identical deltas',
111
+ default: DEFAULT_LLM_RECOVERY_POLICY.deadLoop.consecutiveThreshold,
112
+ min: 1,
113
+ step: 1,
114
+ },
115
+ {
116
+ field: 'deadLoopMinRepetitions',
117
+ type: 'number',
118
+ label: 'Dead-loop cyclic repetitions',
119
+ default: DEFAULT_LLM_RECOVERY_POLICY.deadLoop.minRepetitions,
120
+ min: 1,
121
+ step: 1,
122
+ },
123
+ {
124
+ field: 'deadLoopMinPatternTokens',
125
+ type: 'number',
126
+ label: 'Dead-loop min pattern tokens',
127
+ default: DEFAULT_LLM_RECOVERY_POLICY.deadLoop.minPatternTokens,
128
+ min: 1,
129
+ step: 1,
130
+ },
36
131
  ];
@@ -2,7 +2,7 @@ import type { ToolHandle, configureOutput, makeInput } from '@langflower/node-sd
2
2
  import type { ToolHandlerContext } from '@langflower/tools/domain-tool-configs';
3
3
  import type { PermissionAskRequest } from '@langflower/tools/permission';
4
4
  import type { ChatCompletionMessage } from './chat-completion-stream.js';
5
- import type { LlmInventoryInputs, SteerControlPayload } from '@langflower/node-sdk/llm';
5
+ import { type LlmInventoryInputs, type LlmRecoveryNotice, type SteerControlPayload } from '@langflower/node-sdk/llm';
6
6
  import type { PortMeta } from '@langflower/node-sdk';
7
7
  import type { combineStatefulObservables, StatefulObservable } from '@rx-evo/stateful-observable';
8
8
  import { type Observable, type OperatorFunction } from 'rxjs';
@@ -67,11 +67,9 @@ type StandardLlmAgentChunk = {
67
67
  } | {
68
68
  readonly kind: 'toolLog';
69
69
  readonly text: string;
70
- } | {
70
+ } | ({
71
71
  readonly kind: 'recoveryNotice';
72
- readonly code: 'retry' | 'suspended';
73
- readonly text: string;
74
- } | {
72
+ } & LlmRecoveryNotice) | {
75
73
  readonly kind: 'response';
76
74
  readonly text: string;
77
75
  } | {
@@ -142,10 +140,7 @@ export declare const bindLlmAgentSession: <Context extends LlmAgentInventoryCont
142
140
  outputs: StatefulObservable<string, unknown, import("@langflower/node-sdk").OutputParams & PortMeta>[];
143
141
  inventoryOutputs: {
144
142
  toolLog$: StatefulObservable<string, [unknown, unknown, unknown, unknown, unknown, unknown], undefined>;
145
- recovery$: StatefulObservable<{
146
- code: "suspended" | "retry";
147
- text: string;
148
- }, [unknown, unknown, unknown, unknown, unknown, unknown], undefined>;
143
+ recovery$: StatefulObservable<LlmRecoveryNotice, [unknown, unknown, unknown, unknown, unknown, unknown], undefined>;
149
144
  subagent$: StatefulObservable<SubAgentSpawnPayload, [unknown, unknown, unknown, unknown, unknown, unknown], undefined>;
150
145
  };
151
146
  };