nolo-cli 0.1.8 → 0.1.10

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 (239) hide show
  1. package/README.md +32 -0
  2. package/agentRuntimeCommands.ts +3 -3
  3. package/ai/agent/_executeModel.ts +118 -0
  4. package/ai/agent/agentSlice.ts +525 -0
  5. package/ai/agent/appWorkingMemory.ts +126 -0
  6. package/ai/agent/avatarUtils.ts +24 -0
  7. package/ai/agent/buildEditingContext.ts +373 -0
  8. package/ai/agent/buildSystemPrompt.ts +532 -0
  9. package/ai/agent/cleanAgentMessages.ts +140 -0
  10. package/ai/agent/cliChatClient.ts +119 -0
  11. package/ai/agent/cliExecutor.ts +733 -0
  12. package/ai/agent/cliPrompt.ts +10 -0
  13. package/ai/agent/contextCompiler.ts +107 -0
  14. package/ai/agent/contextLayerContract.ts +44 -0
  15. package/ai/agent/createAgentSchema.ts +234 -0
  16. package/ai/agent/executeToolCall.ts +58 -0
  17. package/ai/agent/fetchAgentContexts.ts +42 -0
  18. package/ai/agent/generatePrompt.ts +3 -0
  19. package/ai/agent/getFullChatContextKeys.ts +168 -0
  20. package/ai/agent/hooks/fetchPublicAgents.ts +133 -0
  21. package/ai/agent/hooks/useAgentConfig.ts +61 -0
  22. package/ai/agent/hooks/useAgentDialog.ts +35 -0
  23. package/ai/agent/hooks/useAgentFormValidation.ts +202 -0
  24. package/ai/agent/hooks/usePublicAgents.ts +473 -0
  25. package/ai/agent/machineRunPermissions.ts +95 -0
  26. package/ai/agent/persistMessageWithFixedId.ts +37 -0
  27. package/ai/agent/planSlice.ts +259 -0
  28. package/ai/agent/referenceUtils.ts +229 -0
  29. package/ai/agent/runAgentBackground.ts +238 -0
  30. package/ai/agent/runAgentClientLoop.ts +138 -0
  31. package/ai/agent/runtimeGuidance.ts +97 -0
  32. package/ai/agent/runtimeServerBase.ts +37 -0
  33. package/ai/agent/server/fetchPublicAgents.ts +128 -0
  34. package/ai/agent/startParallelAgentStreams.ts +424 -0
  35. package/ai/agent/startupProtocol.ts +53 -0
  36. package/ai/agent/streamAgentChatTurn.ts +1278 -0
  37. package/ai/agent/streamAgentChatTurnUtils.ts +738 -0
  38. package/ai/agent/types.ts +71 -0
  39. package/ai/agent/utils/imageOutput.ts +33 -0
  40. package/ai/agent/utils/sortUtils.ts +250 -0
  41. package/ai/agent/web/referencePickerUtils.ts +146 -0
  42. package/ai/ai.locale.ts +1079 -0
  43. package/ai/chat/accumulateToolCallChunks.ts +95 -0
  44. package/ai/chat/fetchUtils.native.ts +276 -0
  45. package/ai/chat/fetchUtils.ts +153 -0
  46. package/ai/chat/parseApiError.ts +64 -0
  47. package/ai/chat/parseMultilineSSE.ts +95 -0
  48. package/ai/chat/sendOpenAICompletionsRequest.native.ts +682 -0
  49. package/ai/chat/sendOpenAICompletionsRequest.ts +703 -0
  50. package/ai/chat/sendOpenAIResponseRequest.ts +491 -0
  51. package/ai/chat/shouldUseServerProxy.ts +18 -0
  52. package/ai/chat/sseClient.native.ts +91 -0
  53. package/ai/chat/sseClient.ts +67 -0
  54. package/ai/chat/streamReader.native.ts +31 -0
  55. package/ai/chat/streamReader.ts +62 -0
  56. package/ai/chat/updateTotalUsage.ts +72 -0
  57. package/ai/context/buildReferenceContext.ts +437 -0
  58. package/ai/context/calculateContextUsage.ts +133 -0
  59. package/ai/context/retention.ts +165 -0
  60. package/ai/context/tokenUtils.ts +78 -0
  61. package/ai/index.ts +1 -0
  62. package/ai/llm/calculateGeminiImageTokens.ts +57 -0
  63. package/ai/llm/deepinfra.ts +28 -0
  64. package/ai/llm/fireworks.ts +50 -0
  65. package/ai/llm/generateRequestBody.ts +165 -0
  66. package/ai/llm/getModelContextWindow.ts +84 -0
  67. package/ai/llm/getNoloKey.ts +31 -0
  68. package/ai/llm/getPricing.ts +199 -0
  69. package/ai/llm/hooks/useModelPricing.ts +75 -0
  70. package/ai/llm/imagePricing.ts +40 -0
  71. package/ai/llm/isResponseAPIModel.ts +13 -0
  72. package/ai/llm/mimo.ts +71 -0
  73. package/ai/llm/mistral.ts +22 -0
  74. package/ai/llm/modelAvatar.ts +427 -0
  75. package/ai/llm/models.ts +45 -0
  76. package/ai/llm/openrouterModels.ts +269 -0
  77. package/ai/llm/providers.ts +306 -0
  78. package/ai/llm/reasoningModels.ts +28 -0
  79. package/ai/llm/types.ts +59 -0
  80. package/ai/llm/usageRequestOptions.ts +59 -0
  81. package/ai/memory/capture.ts +148 -0
  82. package/ai/memory/consolidate.ts +104 -0
  83. package/ai/memory/delete.ts +147 -0
  84. package/ai/memory/overlay.ts +84 -0
  85. package/ai/memory/query.ts +38 -0
  86. package/ai/memory/queryShared.ts +160 -0
  87. package/ai/memory/rank.ts +105 -0
  88. package/ai/memory/recentRelationshipRecap.ts +249 -0
  89. package/ai/memory/remember.ts +167 -0
  90. package/ai/memory/runtime.ts +76 -0
  91. package/ai/memory/store.ts +20 -0
  92. package/ai/memory/storeShared.ts +76 -0
  93. package/ai/memory/types.ts +46 -0
  94. package/ai/memory/understanding.ts +349 -0
  95. package/ai/memory/understandingGreeting.ts +264 -0
  96. package/ai/messages/type.ts +20 -0
  97. package/ai/policy/personalizationDialog.ts +333 -0
  98. package/ai/policy/runtimePolicy.ts +440 -0
  99. package/ai/policy/selfUpdateFields.ts +48 -0
  100. package/ai/policy/types.ts +64 -0
  101. package/ai/skills/referenceRuntime.ts +274 -0
  102. package/ai/skills/skillDiagnostics.ts +251 -0
  103. package/ai/skills/skillDocBuilder.ts +139 -0
  104. package/ai/skills/skillDocProtocol.ts +434 -0
  105. package/ai/skills/skillReferenceSummary.ts +63 -0
  106. package/ai/skills/skillSummaryMarker.ts +26 -0
  107. package/ai/token/calculatePrice.ts +544 -0
  108. package/ai/token/db.ts +98 -0
  109. package/ai/token/externalToolCost.ts +330 -0
  110. package/ai/token/hooks/useRecords.ts +65 -0
  111. package/ai/token/missingUsageEstimate.ts +42 -0
  112. package/ai/token/modelUsageQuery.ts +252 -0
  113. package/ai/token/normalizeUsage.ts +84 -0
  114. package/ai/token/openaiImageGenerationUsage.ts +56 -0
  115. package/ai/token/prepareTokenUsageData.ts +88 -0
  116. package/ai/token/query.ts +88 -0
  117. package/ai/token/queryUserTokens.ts +59 -0
  118. package/ai/token/resolveBillingTarget.ts +52 -0
  119. package/ai/token/saveTokenRecord.ts +53 -0
  120. package/ai/token/serverDialogProjection.ts +78 -0
  121. package/ai/token/serverTokenWriter.ts +143 -0
  122. package/ai/token/stats.ts +21 -0
  123. package/ai/token/tokenThunks.ts +24 -0
  124. package/ai/token/types.ts +93 -0
  125. package/ai/tools/agent/agentTools.ts +176 -0
  126. package/ai/tools/agent/agentUpdateShared.ts +311 -0
  127. package/ai/tools/agent/callAgentTool.ts +139 -0
  128. package/ai/tools/agent/createAgentTool.ts +512 -0
  129. package/ai/tools/agent/createDialogTool.ts +69 -0
  130. package/ai/tools/agent/createSkillAgentTool.ts +62 -0
  131. package/ai/tools/agent/parallelBudget.ts +221 -0
  132. package/ai/tools/agent/presets/appBuilderPreset.ts +145 -0
  133. package/ai/tools/agent/runLlmTool.ts +96 -0
  134. package/ai/tools/agent/runStreamingAgentTool.ts +73 -0
  135. package/ai/tools/agent/skillAgentArgs.ts +106 -0
  136. package/ai/tools/agent/skillAgentPreset.ts +89 -0
  137. package/ai/tools/agent/streamParallelAgentsTool.ts +122 -0
  138. package/ai/tools/agent/updateAgentTool.ts +96 -0
  139. package/ai/tools/agent/updateSelfTool.ts +113 -0
  140. package/ai/tools/amazonProductScraperTool.ts +86 -0
  141. package/ai/tools/apifyActorClient.ts +45 -0
  142. package/ai/tools/appEditGuard.ts +372 -0
  143. package/ai/tools/appReadSnapshot.ts +153 -0
  144. package/ai/tools/appTools.ts +1549 -0
  145. package/ai/tools/applyEditTool.ts +256 -0
  146. package/ai/tools/applyLineEditsTool.ts +312 -0
  147. package/ai/tools/browserTools/click.ts +33 -0
  148. package/ai/tools/browserTools/closeSession.ts +29 -0
  149. package/ai/tools/browserTools/common.ts +27 -0
  150. package/ai/tools/browserTools/openSession.ts +48 -0
  151. package/ai/tools/browserTools/readContent.ts +38 -0
  152. package/ai/tools/browserTools/selectOption.ts +46 -0
  153. package/ai/tools/browserTools/typeText.ts +42 -0
  154. package/ai/tools/category/createCategoryTool.ts +66 -0
  155. package/ai/tools/category/queryContentsByCategoryTool.ts +69 -0
  156. package/ai/tools/category/updateContentCategoryTool.ts +75 -0
  157. package/ai/tools/cfBrowserTools.ts +319 -0
  158. package/ai/tools/cfSpeechToTextTool.ts +49 -0
  159. package/ai/tools/checkEnvTool.ts +65 -0
  160. package/ai/tools/cloudflareCrawlTool.ts +289 -0
  161. package/ai/tools/codeSearchTool.ts +111 -0
  162. package/ai/tools/codeTools.ts +101 -0
  163. package/ai/tools/createDocTool.ts +132 -0
  164. package/ai/tools/createPlanTool.ts +999 -0
  165. package/ai/tools/createSkillDocTool.ts +155 -0
  166. package/ai/tools/createWorkflowTool.ts +154 -0
  167. package/ai/tools/deepseekOcrTool.ts +34 -0
  168. package/ai/tools/delayTool.ts +31 -0
  169. package/ai/tools/deleteSpacesTool.ts +325 -0
  170. package/ai/tools/deleteSpacesToolModel.ts +159 -0
  171. package/ai/tools/devReloadUtils.ts +29 -0
  172. package/ai/tools/dialogMessageSearch.ts +137 -0
  173. package/ai/tools/doctorSkillTool.ts +72 -0
  174. package/ai/tools/ecommerceScraperTool.ts +86 -0
  175. package/ai/tools/emailTools.ts +549 -0
  176. package/ai/tools/evalSkillTool.ts +92 -0
  177. package/ai/tools/exaSearchTool.ts +64 -0
  178. package/ai/tools/execBashTool.ts +379 -0
  179. package/ai/tools/executeSqlTool.ts +192 -0
  180. package/ai/tools/fetchWebpageSupport.ts +309 -0
  181. package/ai/tools/fetchWebpageTool.ts +84 -0
  182. package/ai/tools/geminiImagePreviewTool.ts +361 -0
  183. package/ai/tools/generateDocxTool.ts +215 -0
  184. package/ai/tools/googleSearchScraperTool.ts +106 -0
  185. package/ai/tools/importDataTool.ts +133 -0
  186. package/ai/tools/importSkillTool.ts +162 -0
  187. package/ai/tools/index.ts +1858 -0
  188. package/ai/tools/listFilesTool.ts +82 -0
  189. package/ai/tools/listUserSpacesTool.ts +113 -0
  190. package/ai/tools/modelUsageTools.ts +142 -0
  191. package/ai/tools/olmOcrTool.ts +34 -0
  192. package/ai/tools/openaiImageTool.ts +218 -0
  193. package/ai/tools/paddleOcrTool.ts +34 -0
  194. package/ai/tools/prepareTools.ts +23 -0
  195. package/ai/tools/readDocTool.ts +84 -0
  196. package/ai/tools/readFileTool.ts +211 -0
  197. package/ai/tools/readTool.ts +163 -0
  198. package/ai/tools/readXPostTool.ts +233 -0
  199. package/ai/tools/rememberMemoryTool.ts +84 -0
  200. package/ai/tools/remotionVideoTool.ts +151 -0
  201. package/ai/tools/searchDialogMessagesTool.ts +222 -0
  202. package/ai/tools/searchRepoTool.ts +115 -0
  203. package/ai/tools/searchWorkspaceTool.ts +259 -0
  204. package/ai/tools/skillFollowup.ts +86 -0
  205. package/ai/tools/surfWeatherTool.ts +169 -0
  206. package/ai/tools/table/addTableRowTool.ts +217 -0
  207. package/ai/tools/table/createTableTool.ts +315 -0
  208. package/ai/tools/table/rowTools.ts +366 -0
  209. package/ai/tools/table/schemaTools.ts +244 -0
  210. package/ai/tools/table/shareTableTool.ts +148 -0
  211. package/ai/tools/table/toolShared.ts +129 -0
  212. package/ai/tools/toolApiClient.ts +198 -0
  213. package/ai/tools/toolNameAliases.ts +57 -0
  214. package/ai/tools/toolResultError.ts +42 -0
  215. package/ai/tools/toolRunSlice.ts +303 -0
  216. package/ai/tools/toolSchemaCompatibility.ts +53 -0
  217. package/ai/tools/toolVisibility.ts +4 -0
  218. package/ai/tools/types.ts +20 -0
  219. package/ai/tools/uiAskChoiceTool.ts +104 -0
  220. package/ai/tools/updateContentTitleTool.ts +84 -0
  221. package/ai/tools/updateDocTool.ts +105 -0
  222. package/ai/tools/updateUserPreferenceProfileTool.ts +145 -0
  223. package/ai/tools/whisperTool.ts +77 -0
  224. package/ai/tools/writeFileTool.ts +210 -0
  225. package/ai/tools/youtubeScraperTool.ts +116 -0
  226. package/ai/tools/ziweiChartTool.ts +678 -0
  227. package/ai/types.ts +55 -0
  228. package/ai/workflow/workflowExecutor.ts +323 -0
  229. package/ai/workflow/workflowSlice.ts +73 -0
  230. package/ai/workflow/workflowTypes.ts +106 -0
  231. package/client/compactDialog.ts +222 -0
  232. package/connector-experimental/capabilities.ts +73 -0
  233. package/connector-experimental/codexBinary.ts +41 -0
  234. package/connector-experimental/heartbeatLoop.ts +22 -0
  235. package/connector-experimental/index.ts +5 -0
  236. package/connector-experimental/machineInfo.ts +46 -0
  237. package/connector-experimental/protocol.ts +54 -0
  238. package/machineCommands.ts +4 -4
  239. package/package.json +22 -6
package/ai/types.ts ADDED
@@ -0,0 +1,55 @@
1
+ // packages/ai/types.ts
2
+
3
+ export type ModeType =
4
+ | "text"
5
+ | "image"
6
+ | "stream"
7
+ | "audio"
8
+ | "speech"
9
+ | "surf"
10
+ | "vision";
11
+
12
+ export interface PromptFormData {
13
+ name: string;
14
+ content: string;
15
+ category?: string;
16
+ tags?: string[];
17
+ }
18
+
19
+ export interface Contexts {
20
+ // High priority: context from the user's current input for this request.
21
+ currentInputContext?: string | null;
22
+
23
+ // Medium priority: references from past conversation messages.
24
+ historyContext?: string | null;
25
+
26
+ // Specific rules and processes for the bot/agent.
27
+ botInstructionsContext?: string | null;
28
+
29
+ // General knowledge base documents for lookup.
30
+ botKnowledgeContext?: string | null;
31
+
32
+ // 🔹 新增:用户级通用提示词
33
+ userGlobalPrompt?: string;
34
+
35
+ /** 当前正在编辑的对象描述(表格 / 页面 / 文章等),由 buildEditingContextSummary 构造 */
36
+ editingContext?: string | null;
37
+
38
+ /** 当前对话里最近一次 app 相关工具调用提炼出的工作记忆,不依赖右侧 editing target */
39
+ appWorkingMemory?: string | null;
40
+
41
+ /** 当前所在的 Space 信息(Agent 所处的工作台),包含标题、描述等 */
42
+ spaceContext?: string | null;
43
+
44
+ /** 用户偏好与自动化边界(tone / capture / read policy) */
45
+ userPolicyContext?: string | null;
46
+
47
+ /** 对话增量摘要:已压缩的历史消息概要 */
48
+ dialogSummary?: string | null;
49
+
50
+ /** 主动工作摘要:阶段性沉淀,不代表原始消息已被裁剪 */
51
+ proactiveSummary?: string | null;
52
+
53
+ /** 压缩消息中提取的引用 Key */
54
+ referenceKeys?: string[];
55
+ }
@@ -0,0 +1,323 @@
1
+ /**
2
+ * Workflow Executor
3
+ *
4
+ * Deterministic execution engine — zero LLM orchestration tokens.
5
+ * Each step runs directly without asking LLM "what to do next".
6
+ * Only "llm" type steps call the model (single call, no tool loop).
7
+ */
8
+
9
+ import { createAsyncThunk } from "@reduxjs/toolkit";
10
+ import type { RootState } from "app/store";
11
+ import { runLlm } from "ai/agent/agentSlice";
12
+ import { toolExecutors } from "ai/tools";
13
+ import {
14
+ setWorkflow,
15
+ updateStep,
16
+ incrementStepsExecuted,
17
+ incrementFailedSteps,
18
+ } from "./workflowSlice";
19
+ import type {
20
+ WorkflowDefinition,
21
+ WorkflowStep,
22
+ WorkflowToolStep,
23
+ WorkflowLlmStep,
24
+ WorkflowParallelStep,
25
+ WorkflowStepState,
26
+ WorkflowResult,
27
+ } from "./workflowTypes";
28
+
29
+ // --- Template Resolution ---
30
+
31
+ /**
32
+ * Resolves {{steps.<id>.result}} and {{steps.<id>.result[N]}} templates.
33
+ * Walks nested objects/arrays recursively.
34
+ */
35
+ function resolveTemplates(
36
+ value: any,
37
+ results: Record<string, any>
38
+ ): any {
39
+ if (typeof value === "string") {
40
+ return value.replace(
41
+ /\{\{steps\.([^.}\s]+)\.result(\[\d+\])?\}\}/g,
42
+ (match, stepId, indexPart) => {
43
+ const stepResult = results[stepId];
44
+ if (stepResult === undefined) {
45
+ console.warn(`Workflow: cannot resolve {{steps.${stepId}.result}} — step not completed yet`);
46
+ return match;
47
+ }
48
+ let resolved = stepResult;
49
+ if (indexPart) {
50
+ const idx = parseInt(indexPart.slice(1, -1), 10);
51
+ if (Array.isArray(resolved) && idx < resolved.length) {
52
+ resolved = resolved[idx];
53
+ } else {
54
+ console.warn(`Workflow: index ${idx} out of range for steps.${stepId}.result`);
55
+ return match;
56
+ }
57
+ }
58
+ return typeof resolved === "object" ? JSON.stringify(resolved) : String(resolved);
59
+ }
60
+ );
61
+ }
62
+ if (Array.isArray(value)) {
63
+ return value.map((v) => resolveTemplates(v, results));
64
+ }
65
+ if (value !== null && typeof value === "object") {
66
+ return Object.fromEntries(
67
+ Object.entries(value).map(([k, v]) => [k, resolveTemplates(v, results)])
68
+ );
69
+ }
70
+ return value;
71
+ }
72
+
73
+ // --- Condition Evaluation (safe allowlist interpreter) ---
74
+
75
+ /**
76
+ * Validates that an expression contains ONLY:
77
+ * - steps.<identifier> property paths
78
+ * - comparison operators: === !== > < >= <=
79
+ * - logical operators: && || !
80
+ * - string literals, number literals, boolean/null keywords
81
+ * - parentheses and whitespace
82
+ *
83
+ * Strategy: substitute every safe token with a placeholder, then assert
84
+ * nothing else remains. This allowlist approach is far more robust than a
85
+ * blocklist, which can be bypassed with Unicode escapes or bracket notation.
86
+ */
87
+ function isSafeConditionExpression(expr: string): boolean {
88
+ let s = expr.trim();
89
+ if (!s) return false;
90
+
91
+ // 1. Replace double/single-quoted string literals (no newlines inside)
92
+ s = s.replace(/"[^"\n\\]*(?:\\.[^"\n\\]*)*"/g, "0");
93
+ s = s.replace(/'[^'\n\\]*(?:\\.[^'\n\\]*)*/g, "0");
94
+
95
+ // 2. Replace number literals (int / float)
96
+ s = s.replace(/\b\d+(?:\.\d+)?\b/g, "0");
97
+
98
+ // 3. Replace allowed keywords
99
+ s = s.replace(/\b(true|false|null|undefined)\b/g, "0");
100
+
101
+ // 4. Replace steps.<dotted-path> (e.g. steps.fetchData.output)
102
+ s = s.replace(/\bsteps\.[a-zA-Z_][a-zA-Z0-9_.]*\b/g, "0");
103
+
104
+ // 5. What's left should only be operators, parens, whitespace
105
+ // Allowed characters: 0 (placeholder) space ( ) ! & | = < >
106
+ return /^[0\s()!&|=<>]+$/.test(s);
107
+ }
108
+
109
+ /**
110
+ * Safely resolves a dotted property path like "steps.fetchData.output"
111
+ * against the given results object. Returns undefined for any unknown path.
112
+ */
113
+ function resolvePath(path: string, results: Record<string, any>): unknown {
114
+ // path must be "steps.<segment>[.<segment>...]"
115
+ const parts = path.split(".");
116
+ if (parts[0] !== "steps" || parts.length < 2) return undefined;
117
+ let cur: any = results;
118
+ for (let i = 1; i < parts.length; i++) {
119
+ if (cur === null || cur === undefined || typeof cur !== "object") return undefined;
120
+ cur = cur[parts[i]];
121
+ }
122
+ return cur;
123
+ }
124
+
125
+ /**
126
+ * Evaluates a condition expression against step results.
127
+ *
128
+ * Uses an allowlist validator to guarantee the expression contains nothing
129
+ * but property lookups, comparison/logical operators, and literals before
130
+ * delegating to the JS engine. This eliminates the code-execution surface
131
+ * that a keyword blocklist cannot reliably close.
132
+ */
133
+ function evaluateCondition(
134
+ expression: string,
135
+ results: Record<string, any>
136
+ ): boolean {
137
+ if (!isSafeConditionExpression(expression)) {
138
+ console.warn(
139
+ `Workflow: condition expression rejected by allowlist: "${expression}"`
140
+ );
141
+ return false;
142
+ }
143
+ try {
144
+ // At this point the expression has been validated to contain only safe
145
+ // tokens. We substitute all steps.* paths with their runtime values so
146
+ // the final eval string never needs to reference external globals.
147
+ const resolved = expression.replace(
148
+ /\bsteps\.[a-zA-Z_][a-zA-Z0-9_.]*\b/g,
149
+ (match) => JSON.stringify(resolvePath(match, results))
150
+ );
151
+ // eslint-disable-next-line no-new-func
152
+ const fn = new Function(`"use strict"; return !!(${resolved})`);
153
+ return fn();
154
+ } catch (e) {
155
+ console.warn(`Workflow: condition eval failed: "${expression}"`, e);
156
+ return false;
157
+ }
158
+ }
159
+
160
+
161
+
162
+ /**
163
+ * Executes `fn` up to `maxRetries + 1` times total.
164
+ * Throws the last error if all attempts fail.
165
+ */
166
+ async function executeWithRetry<T>(
167
+ fn: () => Promise<T>,
168
+ maxRetries: number
169
+ ): Promise<T> {
170
+ let lastError: any;
171
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
172
+ try {
173
+ return await fn();
174
+ } catch (err) {
175
+ lastError = err;
176
+ }
177
+ }
178
+ throw lastError;
179
+ }
180
+
181
+ // --- Single Step Executors ---
182
+
183
+ async function executeToolStep(
184
+ step: WorkflowToolStep,
185
+ results: Record<string, any>,
186
+ thunkApi: any
187
+ ): Promise<any> {
188
+ const executor = toolExecutors[step.tool];
189
+ if (!executor) throw new Error(`Workflow: unknown tool "${step.tool}"`);
190
+ const resolvedArgs = resolveTemplates(step.args, results);
191
+ const result = await executor(resolvedArgs, thunkApi);
192
+ return result?.rawData ?? result;
193
+ }
194
+
195
+ async function executeLlmStep(
196
+ step: WorkflowLlmStep,
197
+ results: Record<string, any>,
198
+ thunkApi: any
199
+ ): Promise<any> {
200
+ const { dispatch } = thunkApi;
201
+ const resolvedPrompt = resolveTemplates(step.prompt, results);
202
+ const result = await dispatch(
203
+ runLlm({
204
+ content: resolvedPrompt,
205
+ isStreaming: false,
206
+ ...(step.model && { modelOverride: step.model }),
207
+ })
208
+ ).unwrap();
209
+ return result;
210
+ }
211
+
212
+ async function executeParallelStep(
213
+ step: WorkflowParallelStep,
214
+ results: Record<string, any>,
215
+ thunkApi: any
216
+ ): Promise<Record<string, any>> {
217
+ const subResults = await Promise.all(
218
+ step.steps.map(async (sub) => {
219
+ const subResult =
220
+ sub.type === "tool"
221
+ ? await executeToolStep(sub as WorkflowToolStep, results, thunkApi)
222
+ : await executeLlmStep(sub as WorkflowLlmStep, results, thunkApi);
223
+ return [sub.id, subResult] as [string, any];
224
+ })
225
+ );
226
+ return Object.fromEntries(subResults);
227
+ }
228
+
229
+ // --- Main Executor Thunk ---
230
+
231
+ export const runWorkflow = createAsyncThunk<
232
+ WorkflowResult,
233
+ { definition: WorkflowDefinition; dialogKey?: string },
234
+ { state: RootState }
235
+ >("workflow/run", async ({ definition }, thunkApi) => {
236
+ const { dispatch } = thunkApi;
237
+
238
+ // Initialize Redux state for UI visibility
239
+ const initialStepStates: WorkflowStepState[] = definition.steps.map((s) => ({
240
+ id: s.id,
241
+ title: s.title,
242
+ type: s.type,
243
+ status: "pending",
244
+ }));
245
+ dispatch(setWorkflow({ title: definition.title, steps: initialStepStates }));
246
+
247
+ const results: Record<string, any> = {};
248
+ // Set of step IDs to skip (controlled by condition steps)
249
+ const skippedIds = new Set<string>();
250
+
251
+ for (const step of definition.steps) {
252
+ if (skippedIds.has(step.id)) {
253
+ dispatch(updateStep({ id: step.id, updates: { status: "skipped" } }));
254
+ continue;
255
+ }
256
+
257
+ dispatch(updateStep({ id: step.id, updates: { status: "in-progress" } }));
258
+
259
+ try {
260
+ let result: any;
261
+
262
+ if (step.type === "tool" || step.type === "llm") {
263
+ const retryable = step as WorkflowToolStep | WorkflowLlmStep;
264
+ const policy = retryable.onError ?? "stop";
265
+ const maxRetries =
266
+ policy === "retry" ? ((retryable as WorkflowToolStep).retryCount ?? 1) : 0;
267
+
268
+ result = await executeWithRetry(
269
+ () =>
270
+ step.type === "tool"
271
+ ? executeToolStep(step as WorkflowToolStep, results, thunkApi)
272
+ : executeLlmStep(step as WorkflowLlmStep, results, thunkApi),
273
+ maxRetries
274
+ );
275
+
276
+ } else if (step.type === "parallel") {
277
+ result = await executeParallelStep(step, results, thunkApi);
278
+ // Flatten sub-step results into the top-level map so subsequent steps
279
+ // can reference them directly via {{steps.<subId>.result}}.
280
+ for (const [subId, subResult] of Object.entries(
281
+ result as Record<string, any>
282
+ )) {
283
+ results[subId] = subResult;
284
+ }
285
+
286
+ } else if (step.type === "condition") {
287
+ const passed = evaluateCondition(step.check, results);
288
+ result = { passed, check: step.check };
289
+ const active = passed ? step.ifTrue : step.ifFalse;
290
+ const inactive = passed ? step.ifFalse : step.ifTrue;
291
+ // Mark steps not on the active branch as skipped
292
+ if (inactive) inactive.forEach((id) => skippedIds.add(id));
293
+ // Steps on the active branch are explicitly NOT skipped
294
+ if (active) active.forEach((id) => skippedIds.delete(id));
295
+ }
296
+
297
+ results[step.id] = result;
298
+ dispatch(updateStep({ id: step.id, updates: { status: "completed", result } }));
299
+ dispatch(incrementStepsExecuted());
300
+
301
+ } catch (err: any) {
302
+ dispatch(incrementFailedSteps());
303
+ dispatch(
304
+ updateStep({ id: step.id, updates: { status: "failed", error: err?.message ?? String(err) } })
305
+ );
306
+
307
+ const policy = (step as WorkflowToolStep | WorkflowLlmStep).onError ?? "stop";
308
+
309
+ if (policy === "stop") {
310
+ return {
311
+ success: false,
312
+ results,
313
+ failedStep: step.id,
314
+ error: err?.message ?? String(err),
315
+ };
316
+ }
317
+ // skip / retry-exhausted: record null and continue
318
+ results[step.id] = null;
319
+ }
320
+ }
321
+
322
+ return { success: true, results };
323
+ });
@@ -0,0 +1,73 @@
1
+ import { type PayloadAction, createSlice } from "@reduxjs/toolkit";
2
+ import type { RootState } from "app/store";
3
+ import type {
4
+ WorkflowStepState,
5
+ WorkflowExecutionStats,
6
+ } from "./workflowTypes";
7
+
8
+ interface WorkflowSliceState {
9
+ title: string | null;
10
+ steps: WorkflowStepState[];
11
+ stats: WorkflowExecutionStats;
12
+ }
13
+
14
+ const initialState: WorkflowSliceState = {
15
+ title: null,
16
+ steps: [],
17
+ stats: {
18
+ startTime: null,
19
+ totalStepsExecuted: 0,
20
+ failedSteps: 0,
21
+ },
22
+ };
23
+
24
+ const workflowSlice = createSlice({
25
+ name: "workflow",
26
+ initialState,
27
+ reducers: {
28
+ setWorkflow: (
29
+ state,
30
+ action: PayloadAction<{ title: string; steps: WorkflowStepState[] }>
31
+ ) => {
32
+ state.title = action.payload.title;
33
+ state.steps = action.payload.steps;
34
+ state.stats = { startTime: Date.now(), totalStepsExecuted: 0, failedSteps: 0 };
35
+ },
36
+
37
+ updateStep: (
38
+ state,
39
+ action: PayloadAction<{ id: string; updates: Partial<WorkflowStepState> }>
40
+ ) => {
41
+ const step = state.steps.find((s) => s.id === action.payload.id);
42
+ if (step) Object.assign(step, action.payload.updates);
43
+ },
44
+
45
+ incrementStepsExecuted: (state) => {
46
+ state.stats.totalStepsExecuted += 1;
47
+ },
48
+
49
+ incrementFailedSteps: (state) => {
50
+ state.stats.failedSteps += 1;
51
+ },
52
+
53
+ clearWorkflow: () => initialState,
54
+ },
55
+ });
56
+
57
+ export const {
58
+ setWorkflow,
59
+ updateStep,
60
+ incrementStepsExecuted,
61
+ incrementFailedSteps,
62
+ clearWorkflow,
63
+ } = workflowSlice.actions;
64
+
65
+ export const selectWorkflowSteps = (state: RootState) => state.workflow.steps;
66
+ export const selectWorkflowTitle = (state: RootState) => state.workflow.title;
67
+ export const selectWorkflowStats = (state: RootState) => state.workflow.stats;
68
+ export const selectPendingSteps = (state: RootState) =>
69
+ state.workflow.steps.filter((s) => s.status === "pending");
70
+ export const selectCompletedSteps = (state: RootState) =>
71
+ state.workflow.steps.filter((s) => s.status === "completed");
72
+
73
+ export default workflowSlice.reducer;
@@ -0,0 +1,106 @@
1
+ // Workflow: deterministic execution engine, zero LLM orchestration tokens.
2
+ // LLM calls createWorkflow once to define steps; engine executes without further LLM involvement.
3
+ // Only "llm" type steps invoke the model; all others run as pure tool calls.
4
+
5
+ export type WorkflowStepStatus = "pending" | "in-progress" | "completed" | "failed" | "skipped";
6
+
7
+ export type OnErrorPolicy = "stop" | "skip" | "retry";
8
+
9
+ // --- Step Types ---
10
+
11
+ export interface WorkflowToolStep {
12
+ id: string;
13
+ type: "tool";
14
+ title?: string;
15
+ /** Registered tool name */
16
+ tool: string;
17
+ /** Args supporting {{steps.<id>.result}} and {{steps.<id>.result[N]}} templates */
18
+ args: Record<string, any>;
19
+ onError?: OnErrorPolicy;
20
+ retryCount?: number;
21
+ }
22
+
23
+ export interface WorkflowLlmStep {
24
+ id: string;
25
+ type: "llm";
26
+ title?: string;
27
+ /** Prompt supporting {{steps.<id>.result}} templates */
28
+ prompt: string;
29
+ model?: string;
30
+ onError?: OnErrorPolicy;
31
+ }
32
+
33
+ /** All steps inside run concurrently via Promise.all */
34
+ export interface WorkflowParallelStep {
35
+ id: string;
36
+ type: "parallel";
37
+ title?: string;
38
+ steps: (WorkflowToolStep | WorkflowLlmStep)[];
39
+ }
40
+
41
+ /**
42
+ * Pure JS condition check — no LLM.
43
+ * `check` is a JS expression string evaluated with step results in scope.
44
+ *
45
+ * Supported syntax (allowlist):
46
+ * - Dotted property access: `steps.<stepId>.<property>[.<nested>...]`
47
+ * - Comparison operators: === !== > < >= <=
48
+ * - Logical operators: && || !
49
+ * - String / number / boolean / null literals
50
+ * - Parentheses for grouping
51
+ *
52
+ * Note: bracket notation (`steps['id']`) and `.result` wrappers are NOT supported.
53
+ * The value stored for each step is the raw executor return value, accessed directly:
54
+ * e.g. `"steps.validate.isValid === true"`
55
+ * `"steps.score.value >= 80 && steps.flag.ok !== false"`
56
+ */
57
+ export interface WorkflowConditionStep {
58
+ id: string;
59
+ type: "condition";
60
+ title?: string;
61
+ check: string;
62
+ /** Step IDs to execute when check is truthy (skip others) */
63
+ ifTrue?: string[];
64
+ /** Step IDs to execute when check is falsy (skip others) */
65
+ ifFalse?: string[];
66
+ }
67
+
68
+ export type WorkflowStep =
69
+ | WorkflowToolStep
70
+ | WorkflowLlmStep
71
+ | WorkflowParallelStep
72
+ | WorkflowConditionStep;
73
+
74
+ // --- Workflow Definition ---
75
+
76
+ export interface WorkflowDefinition {
77
+ title: string;
78
+ steps: WorkflowStep[];
79
+ }
80
+
81
+ // --- Execution State (stored in Redux) ---
82
+
83
+ export interface WorkflowStepState {
84
+ id: string;
85
+ title?: string;
86
+ type: WorkflowStep["type"];
87
+ status: WorkflowStepStatus;
88
+ result?: any;
89
+ error?: string;
90
+ }
91
+
92
+ export interface WorkflowExecutionStats {
93
+ startTime: number | null;
94
+ totalStepsExecuted: number;
95
+ failedSteps: number;
96
+ }
97
+
98
+ // --- Result ---
99
+
100
+ export interface WorkflowResult {
101
+ success: boolean;
102
+ /** Results keyed by step id */
103
+ results: Record<string, any>;
104
+ failedStep?: string;
105
+ error?: string;
106
+ }