@vibe-lang/runtime 0.2.5

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 (250) hide show
  1. package/package.json +46 -0
  2. package/src/ast/index.ts +375 -0
  3. package/src/ast.ts +2 -0
  4. package/src/debug/advanced-features.ts +482 -0
  5. package/src/debug/bun-inspector.ts +424 -0
  6. package/src/debug/handoff-manager.ts +283 -0
  7. package/src/debug/index.ts +150 -0
  8. package/src/debug/runner.ts +365 -0
  9. package/src/debug/server.ts +565 -0
  10. package/src/debug/stack-merger.ts +267 -0
  11. package/src/debug/state.ts +581 -0
  12. package/src/debug/test/advanced-features.test.ts +300 -0
  13. package/src/debug/test/e2e.test.ts +218 -0
  14. package/src/debug/test/handoff-manager.test.ts +256 -0
  15. package/src/debug/test/runner.test.ts +256 -0
  16. package/src/debug/test/stack-merger.test.ts +163 -0
  17. package/src/debug/test/state.test.ts +400 -0
  18. package/src/debug/test/ts-debug-integration.test.ts +374 -0
  19. package/src/debug/test/ts-import-tracker.test.ts +125 -0
  20. package/src/debug/test/ts-source-map.test.ts +169 -0
  21. package/src/debug/ts-import-tracker.ts +151 -0
  22. package/src/debug/ts-source-map.ts +171 -0
  23. package/src/errors/index.ts +124 -0
  24. package/src/index.ts +358 -0
  25. package/src/lexer/index.ts +348 -0
  26. package/src/lexer.ts +2 -0
  27. package/src/parser/index.ts +792 -0
  28. package/src/parser/parse.ts +45 -0
  29. package/src/parser/test/async.test.ts +248 -0
  30. package/src/parser/test/destructuring.test.ts +167 -0
  31. package/src/parser/test/do-expression.test.ts +486 -0
  32. package/src/parser/test/errors/do-expression.test.ts +95 -0
  33. package/src/parser/test/errors/error-locations.test.ts +230 -0
  34. package/src/parser/test/errors/invalid-expressions.test.ts +144 -0
  35. package/src/parser/test/errors/missing-tokens.test.ts +126 -0
  36. package/src/parser/test/errors/model-declaration.test.ts +185 -0
  37. package/src/parser/test/errors/nested-blocks.test.ts +226 -0
  38. package/src/parser/test/errors/unclosed-delimiters.test.ts +122 -0
  39. package/src/parser/test/errors/unexpected-tokens.test.ts +120 -0
  40. package/src/parser/test/import-export.test.ts +143 -0
  41. package/src/parser/test/literals.test.ts +404 -0
  42. package/src/parser/test/model-declaration.test.ts +161 -0
  43. package/src/parser/test/nested-blocks.test.ts +402 -0
  44. package/src/parser/test/parser.test.ts +743 -0
  45. package/src/parser/test/private.test.ts +136 -0
  46. package/src/parser/test/template-literal.test.ts +127 -0
  47. package/src/parser/test/tool-declaration.test.ts +302 -0
  48. package/src/parser/test/ts-block.test.ts +252 -0
  49. package/src/parser/test/type-annotations.test.ts +254 -0
  50. package/src/parser/visitor/helpers.ts +330 -0
  51. package/src/parser/visitor.ts +794 -0
  52. package/src/parser.ts +2 -0
  53. package/src/runtime/ai/cache-chunking.test.ts +69 -0
  54. package/src/runtime/ai/cache-chunking.ts +73 -0
  55. package/src/runtime/ai/client.ts +109 -0
  56. package/src/runtime/ai/context.ts +168 -0
  57. package/src/runtime/ai/formatters.ts +316 -0
  58. package/src/runtime/ai/index.ts +38 -0
  59. package/src/runtime/ai/language-ref.ts +38 -0
  60. package/src/runtime/ai/providers/anthropic.ts +253 -0
  61. package/src/runtime/ai/providers/google.ts +201 -0
  62. package/src/runtime/ai/providers/openai.ts +156 -0
  63. package/src/runtime/ai/retry.ts +100 -0
  64. package/src/runtime/ai/return-tools.ts +301 -0
  65. package/src/runtime/ai/test/client.test.ts +83 -0
  66. package/src/runtime/ai/test/formatters.test.ts +485 -0
  67. package/src/runtime/ai/test/retry.test.ts +137 -0
  68. package/src/runtime/ai/test/return-tools.test.ts +450 -0
  69. package/src/runtime/ai/test/tool-loop.test.ts +319 -0
  70. package/src/runtime/ai/test/tool-schema.test.ts +241 -0
  71. package/src/runtime/ai/tool-loop.ts +203 -0
  72. package/src/runtime/ai/tool-schema.ts +151 -0
  73. package/src/runtime/ai/types.ts +113 -0
  74. package/src/runtime/ai-logger.ts +255 -0
  75. package/src/runtime/ai-provider.ts +347 -0
  76. package/src/runtime/async/dependencies.ts +276 -0
  77. package/src/runtime/async/executor.ts +293 -0
  78. package/src/runtime/async/index.ts +43 -0
  79. package/src/runtime/async/scheduling.ts +163 -0
  80. package/src/runtime/async/test/dependencies.test.ts +284 -0
  81. package/src/runtime/async/test/executor.test.ts +388 -0
  82. package/src/runtime/context.ts +357 -0
  83. package/src/runtime/exec/ai.ts +139 -0
  84. package/src/runtime/exec/expressions.ts +475 -0
  85. package/src/runtime/exec/frames.ts +26 -0
  86. package/src/runtime/exec/functions.ts +305 -0
  87. package/src/runtime/exec/interpolation.ts +312 -0
  88. package/src/runtime/exec/statements.ts +604 -0
  89. package/src/runtime/exec/tools.ts +129 -0
  90. package/src/runtime/exec/typescript.ts +215 -0
  91. package/src/runtime/exec/variables.ts +279 -0
  92. package/src/runtime/index.ts +975 -0
  93. package/src/runtime/modules.ts +452 -0
  94. package/src/runtime/serialize.ts +103 -0
  95. package/src/runtime/state.ts +489 -0
  96. package/src/runtime/stdlib/core.ts +45 -0
  97. package/src/runtime/stdlib/directory.test.ts +156 -0
  98. package/src/runtime/stdlib/edit.test.ts +154 -0
  99. package/src/runtime/stdlib/fastEdit.test.ts +201 -0
  100. package/src/runtime/stdlib/glob.test.ts +106 -0
  101. package/src/runtime/stdlib/grep.test.ts +144 -0
  102. package/src/runtime/stdlib/index.ts +16 -0
  103. package/src/runtime/stdlib/readFile.test.ts +123 -0
  104. package/src/runtime/stdlib/tools/index.ts +707 -0
  105. package/src/runtime/stdlib/writeFile.test.ts +157 -0
  106. package/src/runtime/step.ts +969 -0
  107. package/src/runtime/test/ai-context.test.ts +1086 -0
  108. package/src/runtime/test/ai-result-object.test.ts +419 -0
  109. package/src/runtime/test/ai-tool-flow.test.ts +859 -0
  110. package/src/runtime/test/async-execution-order.test.ts +618 -0
  111. package/src/runtime/test/async-execution.test.ts +344 -0
  112. package/src/runtime/test/async-nested.test.ts +660 -0
  113. package/src/runtime/test/async-parallel-timing.test.ts +546 -0
  114. package/src/runtime/test/basic1.test.ts +154 -0
  115. package/src/runtime/test/binary-operators.test.ts +431 -0
  116. package/src/runtime/test/break-statement.test.ts +257 -0
  117. package/src/runtime/test/context-modes.test.ts +650 -0
  118. package/src/runtime/test/context.test.ts +466 -0
  119. package/src/runtime/test/core-functions.test.ts +228 -0
  120. package/src/runtime/test/e2e.test.ts +88 -0
  121. package/src/runtime/test/error-locations/error-locations.test.ts +80 -0
  122. package/src/runtime/test/error-locations/main-error.vibe +4 -0
  123. package/src/runtime/test/error-locations/main-import-error.vibe +3 -0
  124. package/src/runtime/test/error-locations/utils/helper.vibe +5 -0
  125. package/src/runtime/test/for-in.test.ts +312 -0
  126. package/src/runtime/test/helpers.ts +69 -0
  127. package/src/runtime/test/imports.test.ts +334 -0
  128. package/src/runtime/test/json-expressions.test.ts +232 -0
  129. package/src/runtime/test/literals.test.ts +372 -0
  130. package/src/runtime/test/logical-indexing.test.ts +478 -0
  131. package/src/runtime/test/member-methods.test.ts +324 -0
  132. package/src/runtime/test/model-config.test.ts +338 -0
  133. package/src/runtime/test/null-handling.test.ts +342 -0
  134. package/src/runtime/test/private-visibility.test.ts +332 -0
  135. package/src/runtime/test/runtime-state.test.ts +514 -0
  136. package/src/runtime/test/scoping.test.ts +370 -0
  137. package/src/runtime/test/string-interpolation.test.ts +354 -0
  138. package/src/runtime/test/template-literal.test.ts +181 -0
  139. package/src/runtime/test/tool-execution.test.ts +467 -0
  140. package/src/runtime/test/tool-schema-generation.test.ts +477 -0
  141. package/src/runtime/test/tostring.test.ts +210 -0
  142. package/src/runtime/test/ts-block.test.ts +594 -0
  143. package/src/runtime/test/ts-error-location.test.ts +231 -0
  144. package/src/runtime/test/types.test.ts +732 -0
  145. package/src/runtime/test/verbose-logger.test.ts +710 -0
  146. package/src/runtime/test/vibe-expression.test.ts +54 -0
  147. package/src/runtime/test/vibe-value-errors.test.ts +541 -0
  148. package/src/runtime/test/while.test.ts +232 -0
  149. package/src/runtime/tools/builtin.ts +30 -0
  150. package/src/runtime/tools/directory-tools.ts +70 -0
  151. package/src/runtime/tools/file-tools.ts +228 -0
  152. package/src/runtime/tools/index.ts +5 -0
  153. package/src/runtime/tools/registry.ts +48 -0
  154. package/src/runtime/tools/search-tools.ts +134 -0
  155. package/src/runtime/tools/security.ts +36 -0
  156. package/src/runtime/tools/system-tools.ts +312 -0
  157. package/src/runtime/tools/test/fixtures/base-types.ts +40 -0
  158. package/src/runtime/tools/test/fixtures/test-types.ts +132 -0
  159. package/src/runtime/tools/test/registry.test.ts +713 -0
  160. package/src/runtime/tools/test/security.test.ts +86 -0
  161. package/src/runtime/tools/test/system-tools.test.ts +679 -0
  162. package/src/runtime/tools/test/ts-schema.test.ts +357 -0
  163. package/src/runtime/tools/ts-schema.ts +341 -0
  164. package/src/runtime/tools/types.ts +89 -0
  165. package/src/runtime/tools/utility-tools.ts +198 -0
  166. package/src/runtime/ts-eval.ts +126 -0
  167. package/src/runtime/types.ts +797 -0
  168. package/src/runtime/validation.ts +160 -0
  169. package/src/runtime/verbose-logger.ts +459 -0
  170. package/src/runtime.ts +2 -0
  171. package/src/semantic/analyzer-context.ts +62 -0
  172. package/src/semantic/analyzer-validators.ts +575 -0
  173. package/src/semantic/analyzer-visitors.ts +534 -0
  174. package/src/semantic/analyzer.ts +83 -0
  175. package/src/semantic/index.ts +11 -0
  176. package/src/semantic/symbol-table.ts +58 -0
  177. package/src/semantic/test/async-validation.test.ts +301 -0
  178. package/src/semantic/test/compress-validation.test.ts +179 -0
  179. package/src/semantic/test/const-reassignment.test.ts +111 -0
  180. package/src/semantic/test/control-flow.test.ts +346 -0
  181. package/src/semantic/test/destructuring.test.ts +185 -0
  182. package/src/semantic/test/duplicate-declarations.test.ts +168 -0
  183. package/src/semantic/test/export-validation.test.ts +111 -0
  184. package/src/semantic/test/fixtures/math.ts +31 -0
  185. package/src/semantic/test/imports.test.ts +148 -0
  186. package/src/semantic/test/json-type.test.ts +68 -0
  187. package/src/semantic/test/literals.test.ts +127 -0
  188. package/src/semantic/test/model-validation.test.ts +179 -0
  189. package/src/semantic/test/prompt-validation.test.ts +343 -0
  190. package/src/semantic/test/scoping.test.ts +312 -0
  191. package/src/semantic/test/tool-validation.test.ts +306 -0
  192. package/src/semantic/test/ts-type-checking.test.ts +563 -0
  193. package/src/semantic/test/type-constraints.test.ts +111 -0
  194. package/src/semantic/test/type-inference.test.ts +87 -0
  195. package/src/semantic/test/type-validation.test.ts +552 -0
  196. package/src/semantic/test/undefined-variables.test.ts +163 -0
  197. package/src/semantic/ts-block-checker.ts +204 -0
  198. package/src/semantic/ts-signatures.ts +194 -0
  199. package/src/semantic/ts-types.ts +170 -0
  200. package/src/semantic/types.ts +58 -0
  201. package/tests/fixtures/conditional-logic.vibe +14 -0
  202. package/tests/fixtures/function-call.vibe +16 -0
  203. package/tests/fixtures/imports/cycle-detection/a.vibe +6 -0
  204. package/tests/fixtures/imports/cycle-detection/b.vibe +5 -0
  205. package/tests/fixtures/imports/cycle-detection/main.vibe +3 -0
  206. package/tests/fixtures/imports/module-isolation/main-b.vibe +8 -0
  207. package/tests/fixtures/imports/module-isolation/main.vibe +9 -0
  208. package/tests/fixtures/imports/module-isolation/moduleA.vibe +6 -0
  209. package/tests/fixtures/imports/module-isolation/moduleB.vibe +6 -0
  210. package/tests/fixtures/imports/nested-import/helper.vibe +6 -0
  211. package/tests/fixtures/imports/nested-import/main.vibe +3 -0
  212. package/tests/fixtures/imports/nested-import/utils.ts +3 -0
  213. package/tests/fixtures/imports/nested-isolation/file2.vibe +15 -0
  214. package/tests/fixtures/imports/nested-isolation/file3.vibe +10 -0
  215. package/tests/fixtures/imports/nested-isolation/main.vibe +21 -0
  216. package/tests/fixtures/imports/pure-cycle/a.vibe +5 -0
  217. package/tests/fixtures/imports/pure-cycle/b.vibe +5 -0
  218. package/tests/fixtures/imports/pure-cycle/main.vibe +3 -0
  219. package/tests/fixtures/imports/ts-boolean/checks.ts +14 -0
  220. package/tests/fixtures/imports/ts-boolean/main.vibe +10 -0
  221. package/tests/fixtures/imports/ts-boolean/type-mismatch.vibe +5 -0
  222. package/tests/fixtures/imports/ts-boolean/use-constant.vibe +18 -0
  223. package/tests/fixtures/imports/ts-error-handling/helpers.ts +42 -0
  224. package/tests/fixtures/imports/ts-error-handling/main.vibe +5 -0
  225. package/tests/fixtures/imports/ts-import/main.vibe +4 -0
  226. package/tests/fixtures/imports/ts-import/math.ts +9 -0
  227. package/tests/fixtures/imports/ts-variables/call-non-function.vibe +5 -0
  228. package/tests/fixtures/imports/ts-variables/data.ts +10 -0
  229. package/tests/fixtures/imports/ts-variables/import-json.vibe +5 -0
  230. package/tests/fixtures/imports/ts-variables/import-type-mismatch.vibe +5 -0
  231. package/tests/fixtures/imports/ts-variables/import-variable.vibe +5 -0
  232. package/tests/fixtures/imports/vibe-import/greet.vibe +5 -0
  233. package/tests/fixtures/imports/vibe-import/main.vibe +3 -0
  234. package/tests/fixtures/multiple-ai-calls.vibe +10 -0
  235. package/tests/fixtures/simple-greeting.vibe +6 -0
  236. package/tests/fixtures/template-literals.vibe +11 -0
  237. package/tests/integration/basic-ai/basic-ai.integration.test.ts +166 -0
  238. package/tests/integration/basic-ai/basic-ai.vibe +12 -0
  239. package/tests/integration/bug-fix/bug-fix.integration.test.ts +201 -0
  240. package/tests/integration/bug-fix/buggy-code.ts +22 -0
  241. package/tests/integration/bug-fix/fix-bug.vibe +21 -0
  242. package/tests/integration/compress/compress.integration.test.ts +206 -0
  243. package/tests/integration/destructuring/destructuring.integration.test.ts +92 -0
  244. package/tests/integration/hello-world-translator/hello-world-translator.integration.test.ts +61 -0
  245. package/tests/integration/line-annotator/context-modes.integration.test.ts +261 -0
  246. package/tests/integration/line-annotator/line-annotator.integration.test.ts +148 -0
  247. package/tests/integration/multi-feature/cumulative-sum.integration.test.ts +75 -0
  248. package/tests/integration/multi-feature/number-analyzer.integration.test.ts +191 -0
  249. package/tests/integration/multi-feature/number-analyzer.vibe +59 -0
  250. package/tests/integration/tool-calls/tool-calls.integration.test.ts +93 -0
@@ -0,0 +1,489 @@
1
+ import * as AST from '../ast';
2
+ import type { RuntimeState, AIOperation, AIInteraction, StackFrame, FrameEntry, PromptToolCall, ToolCallRecord, VibeValue } from './types';
3
+ import { createVibeValue } from './types';
4
+ import type { ToolRoundResult } from './ai/tool-loop';
5
+
6
+ // Options for creating initial state
7
+ export interface InitialStateOptions {
8
+ logAiInteractions?: boolean;
9
+ rootDir?: string; // Root directory for file operation sandboxing (defaults to cwd)
10
+ maxParallel?: number; // Max concurrent async operations (default: 4)
11
+ }
12
+
13
+ // Create initial runtime state from a program AST
14
+ export function createInitialState(
15
+ program: AST.Program,
16
+ options?: InitialStateOptions
17
+ ): RuntimeState {
18
+ // Collect function declarations
19
+ const functions: Record<string, AST.FunctionDeclaration> = {};
20
+ for (const stmt of program.body) {
21
+ if (stmt.type === 'FunctionDeclaration') {
22
+ functions[stmt.name] = stmt;
23
+ }
24
+ }
25
+
26
+ // Create initial instruction stack with all top-level statements
27
+ // We pop from the front, so first statement should be at index 0
28
+ const instructionStack = program.body
29
+ .map((stmt) => ({ op: 'exec_statement' as const, stmt, location: stmt.location }));
30
+
31
+ return {
32
+ status: 'running',
33
+ program,
34
+ functions,
35
+ tsModules: {},
36
+ vibeModules: {},
37
+ importedNames: {},
38
+ callStack: [createFrame('<entry>')],
39
+ instructionStack,
40
+ valueStack: [],
41
+ lastResult: null,
42
+ lastResultSource: null,
43
+ aiHistory: [],
44
+ executionLog: [],
45
+ logAiInteractions: options?.logAiInteractions ?? false,
46
+ aiInteractions: [],
47
+ localContext: [],
48
+ globalContext: [],
49
+ pendingAI: null,
50
+ pendingCompress: null,
51
+ pendingTS: null,
52
+ pendingImportedTsCall: null,
53
+ pendingToolCall: null,
54
+ pendingDestructuring: null,
55
+ expectedFields: null,
56
+ lastUsedModel: null,
57
+ rootDir: options?.rootDir ?? process.cwd(),
58
+ error: null,
59
+ errorObject: null,
60
+
61
+ // Async execution tracking
62
+ asyncOperations: new Map(),
63
+ pendingAsyncIds: new Set(),
64
+ asyncVarToOpId: new Map(),
65
+ asyncWaves: [],
66
+ currentWaveId: 0,
67
+ maxParallel: options?.maxParallel ?? 4,
68
+ nextAsyncId: 1,
69
+ awaitingAsyncIds: [],
70
+
71
+ // Async declaration context (set when inside async let/const)
72
+ currentAsyncVarName: null,
73
+ currentAsyncIsConst: false,
74
+ currentAsyncType: null,
75
+ currentAsyncIsPrivate: false,
76
+ currentAsyncIsDestructure: false,
77
+ currentAsyncIsFireAndForget: false,
78
+
79
+ // Async function isolation
80
+ isInAsyncIsolation: false,
81
+
82
+ // Scheduled async operations
83
+ pendingAsyncStarts: [],
84
+
85
+ // String interpolation context (for prompt strings in do/vibe)
86
+ inPromptContext: false,
87
+ };
88
+ }
89
+
90
+ // Create a new stack frame
91
+ export function createFrame(name: string, parentFrameIndex: number | null = null): StackFrame {
92
+ return {
93
+ name,
94
+ locals: {},
95
+ parentFrameIndex,
96
+ orderedEntries: [],
97
+ };
98
+ }
99
+
100
+ // Resume execution after AI response
101
+ export function resumeWithAIResponse(
102
+ state: RuntimeState,
103
+ response: unknown,
104
+ interaction?: AIInteraction,
105
+ toolRounds?: ToolRoundResult[]
106
+ ): RuntimeState {
107
+ if (state.status !== 'awaiting_ai' || !state.pendingAI) {
108
+ throw new Error('Cannot resume: not awaiting AI response');
109
+ }
110
+
111
+ const responseStr = typeof response === 'string' ? response : JSON.stringify(response);
112
+ const pendingAI = state.pendingAI;
113
+
114
+ const aiOp: AIOperation = {
115
+ type: pendingAI.type,
116
+ prompt: pendingAI.prompt,
117
+ response: responseStr,
118
+ timestamp: Date.now(),
119
+ };
120
+
121
+ // Add interaction to log if provided and logging is enabled
122
+ const aiInteractions = state.logAiInteractions && interaction
123
+ ? [...state.aiInteractions, interaction]
124
+ : state.aiInteractions;
125
+
126
+ // Build ordered entries with prompt containing embedded tool calls
127
+ const frame = state.callStack[state.callStack.length - 1];
128
+
129
+ // Convert tool rounds to PromptToolCall format (embedded in prompt entry for context)
130
+ const promptToolCalls: PromptToolCall[] = (toolRounds ?? []).flatMap((round) =>
131
+ round.toolCalls.map((call, index) => {
132
+ const result = round.results[index];
133
+ const toolCall: PromptToolCall = {
134
+ toolName: call.toolName,
135
+ args: call.args,
136
+ };
137
+ if (result?.error) {
138
+ toolCall.error = result.error;
139
+ } else if (result?.result !== undefined) {
140
+ toolCall.result = result.result;
141
+ }
142
+ return toolCall;
143
+ })
144
+ );
145
+
146
+ // Build ToolCallRecord array for VibeValue (all rounds accumulated)
147
+ const toolCallRecords: ToolCallRecord[] = (toolRounds ?? []).flatMap((round) =>
148
+ round.toolCalls.map((call, index) => {
149
+ const result = round.results[index];
150
+ const error = result?.error;
151
+ return {
152
+ toolName: call.toolName,
153
+ args: call.args,
154
+ result: error ? null : String(result?.result ?? ''),
155
+ err: !!error,
156
+ errDetails: error ? { message: error } : null,
157
+ duration: result?.duration ?? 0,
158
+ };
159
+ })
160
+ );
161
+
162
+ // Create VibeValue with final response value and all tool calls
163
+ const aiResultValue: VibeValue = createVibeValue(response, {
164
+ source: 'ai',
165
+ toolCalls: toolCallRecords,
166
+ });
167
+
168
+ // Create prompt entry with embedded tool calls (order: prompt → tools → response)
169
+ // Note: promptEntry.response stores raw value for context display
170
+ const promptEntry: FrameEntry = {
171
+ kind: 'prompt' as const,
172
+ aiType: pendingAI.type,
173
+ prompt: pendingAI.prompt,
174
+ toolCalls: promptToolCalls.length > 0 ? promptToolCalls : undefined,
175
+ response, // Include response for context history
176
+ };
177
+
178
+ const newOrderedEntries = [
179
+ ...frame.orderedEntries,
180
+ promptEntry,
181
+ ];
182
+ const updatedCallStack = [
183
+ ...state.callStack.slice(0, -1),
184
+ { ...frame, orderedEntries: newOrderedEntries },
185
+ ];
186
+
187
+ return {
188
+ ...state,
189
+ status: 'running',
190
+ lastResult: aiResultValue, // Store VibeValue with response and toolCalls
191
+ lastResultSource: 'ai',
192
+ callStack: updatedCallStack,
193
+ aiHistory: [...state.aiHistory, aiOp],
194
+ aiInteractions,
195
+ pendingAI: null,
196
+ executionLog: [
197
+ ...state.executionLog,
198
+ {
199
+ timestamp: Date.now(),
200
+ instructionType: `ai_${pendingAI.type}_response`,
201
+ details: { prompt: pendingAI.prompt, toolRounds: toolRounds?.length ?? 0 },
202
+ result: responseStr,
203
+ },
204
+ ],
205
+ };
206
+ }
207
+
208
+ // Resume execution after user input
209
+ export function resumeWithUserInput(state: RuntimeState, input: string): RuntimeState {
210
+ if (state.status !== 'awaiting_user' || !state.pendingAI) {
211
+ throw new Error('Cannot resume: not awaiting user input');
212
+ }
213
+
214
+ const pendingAI = state.pendingAI;
215
+
216
+ const aiOp: AIOperation = {
217
+ type: 'ask',
218
+ prompt: pendingAI.prompt,
219
+ response: input,
220
+ timestamp: Date.now(),
221
+ };
222
+
223
+ // Add the completed prompt to orderedEntries in current frame (with response for history)
224
+ const frame = state.callStack[state.callStack.length - 1];
225
+ const newOrderedEntries = [
226
+ ...frame.orderedEntries,
227
+ {
228
+ kind: 'prompt' as const,
229
+ aiType: 'ask' as const,
230
+ prompt: pendingAI.prompt,
231
+ response: input, // Include user input for context history
232
+ }
233
+ ];
234
+ const updatedCallStack = [
235
+ ...state.callStack.slice(0, -1),
236
+ { ...frame, orderedEntries: newOrderedEntries },
237
+ ];
238
+
239
+ return {
240
+ ...state,
241
+ status: 'running',
242
+ lastResult: input,
243
+ lastResultSource: 'user',
244
+ callStack: updatedCallStack,
245
+ aiHistory: [...state.aiHistory, aiOp],
246
+ pendingAI: null,
247
+ executionLog: [
248
+ ...state.executionLog,
249
+ {
250
+ timestamp: Date.now(),
251
+ instructionType: 'user_input_response',
252
+ details: { prompt: pendingAI.prompt },
253
+ result: input,
254
+ },
255
+ ],
256
+ };
257
+ }
258
+
259
+ // Resume execution after TypeScript evaluation (inline ts block)
260
+ export function resumeWithTsResult(state: RuntimeState, result: unknown): RuntimeState {
261
+ if (state.status !== 'awaiting_ts' || !state.pendingTS) {
262
+ throw new Error('Cannot resume: not awaiting TypeScript result');
263
+ }
264
+
265
+ // Normalize undefined to null for Vibe's single null concept
266
+ const normalizedResult = result === undefined ? null : result;
267
+
268
+ return {
269
+ ...state,
270
+ status: 'running',
271
+ lastResult: normalizedResult,
272
+ pendingTS: null,
273
+ executionLog: [
274
+ ...state.executionLog,
275
+ {
276
+ timestamp: Date.now(),
277
+ instructionType: 'ts_eval_result',
278
+ details: { params: state.pendingTS.params },
279
+ result: normalizedResult,
280
+ },
281
+ ],
282
+ };
283
+ }
284
+
285
+ // Resume execution after imported TS function call
286
+ export function resumeWithImportedTsResult(state: RuntimeState, result: unknown): RuntimeState {
287
+ if (state.status !== 'awaiting_ts' || !state.pendingImportedTsCall) {
288
+ throw new Error('Cannot resume: not awaiting imported TS function result');
289
+ }
290
+
291
+ // Normalize undefined to null for Vibe's single null concept
292
+ const normalizedResult = result === undefined ? null : result;
293
+
294
+ return {
295
+ ...state,
296
+ status: 'running',
297
+ lastResult: normalizedResult,
298
+ pendingImportedTsCall: null,
299
+ executionLog: [
300
+ ...state.executionLog,
301
+ {
302
+ timestamp: Date.now(),
303
+ instructionType: 'imported_ts_call_result',
304
+ details: { funcName: state.pendingImportedTsCall.funcName },
305
+ result: normalizedResult,
306
+ },
307
+ ],
308
+ };
309
+ }
310
+
311
+ // Resume execution after compress AI summarization
312
+ export function resumeWithCompressResult(state: RuntimeState, summary: string): RuntimeState {
313
+ if (state.status !== 'awaiting_compress' || !state.pendingCompress) {
314
+ throw new Error('Cannot resume: not awaiting compress result');
315
+ }
316
+
317
+ const { entryIndex, scopeType, label } = state.pendingCompress;
318
+ const frame = state.callStack[state.callStack.length - 1];
319
+
320
+ // Replace loop entries with summary, keeping scope markers
321
+ const newOrderedEntries: FrameEntry[] = [
322
+ ...frame.orderedEntries.slice(0, entryIndex),
323
+ { kind: 'scope-enter', scopeType, label },
324
+ { kind: 'summary', text: summary },
325
+ { kind: 'scope-exit', scopeType, label },
326
+ ];
327
+
328
+ return {
329
+ ...state,
330
+ status: 'running',
331
+ pendingCompress: null,
332
+ callStack: [
333
+ ...state.callStack.slice(0, -1),
334
+ { ...frame, orderedEntries: newOrderedEntries },
335
+ ],
336
+ executionLog: [
337
+ ...state.executionLog,
338
+ {
339
+ timestamp: Date.now(),
340
+ instructionType: 'compress_result',
341
+ details: { scopeType, label, prompt: state.pendingCompress.prompt },
342
+ result: summary,
343
+ },
344
+ ],
345
+ };
346
+ }
347
+
348
+ // Resume execution after tool call
349
+ export function resumeWithToolResult(
350
+ state: RuntimeState,
351
+ result: unknown,
352
+ error?: string
353
+ ): RuntimeState {
354
+ if (state.status !== 'awaiting_tool' || !state.pendingToolCall) {
355
+ throw new Error('Cannot resume: not awaiting tool result');
356
+ }
357
+
358
+ const pendingTool = state.pendingToolCall;
359
+ const finalResult = error ? { error } : result;
360
+
361
+ return {
362
+ ...state,
363
+ status: 'running',
364
+ lastResult: finalResult,
365
+ pendingToolCall: null,
366
+ executionLog: [
367
+ ...state.executionLog,
368
+ {
369
+ timestamp: Date.now(),
370
+ instructionType: 'tool_call_result',
371
+ details: {
372
+ toolName: pendingTool.toolName,
373
+ hasError: !!error,
374
+ },
375
+ result: finalResult,
376
+ },
377
+ ],
378
+ };
379
+ }
380
+
381
+ // Resume execution after async operations complete
382
+ export function resumeWithAsyncResults(
383
+ state: RuntimeState,
384
+ results: Map<string, VibeValue>
385
+ ): RuntimeState {
386
+ if (state.status !== 'awaiting_async') {
387
+ throw new Error('Cannot resume: not awaiting async results');
388
+ }
389
+
390
+ // Update variables with their async results
391
+ const frame = state.callStack[state.callStack.length - 1];
392
+ const newLocals = { ...frame.locals };
393
+
394
+ // Track the last result for operations that need it (e.g., destructuring)
395
+ let lastResult: VibeValue | null = null;
396
+
397
+ for (const [opId, result] of results) {
398
+ const operation = state.asyncOperations.get(opId);
399
+ if (operation?.variableName) {
400
+ // Preserve existing variable properties (isConst, isPrivate, typeAnnotation)
401
+ // when updating with the async result
402
+ const existingVar = newLocals[operation.variableName];
403
+ newLocals[operation.variableName] = {
404
+ ...result,
405
+ isConst: existingVar?.isConst ?? result.isConst,
406
+ isPrivate: existingVar?.isPrivate ?? result.isPrivate,
407
+ typeAnnotation: existingVar?.typeAnnotation ?? result.typeAnnotation,
408
+ };
409
+ }
410
+ // Mark operation as no longer pending
411
+ state.pendingAsyncIds.delete(opId);
412
+ // Keep track of the result (for destructuring and other uses)
413
+ lastResult = result;
414
+ }
415
+
416
+ const updatedCallStack = [
417
+ ...state.callStack.slice(0, -1),
418
+ { ...frame, locals: newLocals },
419
+ ];
420
+
421
+ return {
422
+ ...state,
423
+ status: 'running',
424
+ // Set lastResult so instructions like destructure_assign can use it
425
+ lastResult: lastResult ?? state.lastResult,
426
+ callStack: updatedCallStack,
427
+ awaitingAsyncIds: [],
428
+ executionLog: [
429
+ ...state.executionLog,
430
+ {
431
+ timestamp: Date.now(),
432
+ instructionType: 'async_results',
433
+ details: { operationCount: results.size },
434
+ },
435
+ ],
436
+ };
437
+ }
438
+
439
+ // Pause execution manually
440
+ export function pauseExecution(state: RuntimeState): RuntimeState {
441
+ if (state.status !== 'running') {
442
+ return state;
443
+ }
444
+ return {
445
+ ...state,
446
+ status: 'paused',
447
+ };
448
+ }
449
+
450
+ // Resume from manual pause
451
+ export function resumeExecution(state: RuntimeState): RuntimeState {
452
+ if (state.status !== 'paused') {
453
+ return state;
454
+ }
455
+ return {
456
+ ...state,
457
+ status: 'running',
458
+ };
459
+ }
460
+
461
+ // Get current frame
462
+ export function currentFrame(state: RuntimeState): StackFrame {
463
+ const frame = state.callStack[state.callStack.length - 1];
464
+ if (!frame) {
465
+ throw new Error('No active stack frame');
466
+ }
467
+ return frame;
468
+ }
469
+
470
+ // Get variable value (walks scope chain)
471
+ export function getVariable(state: RuntimeState, name: string): unknown {
472
+ // Walk the scope chain
473
+ let frameIndex: number | null = state.callStack.length - 1;
474
+ while (frameIndex !== null && frameIndex >= 0) {
475
+ const frame: StackFrame = state.callStack[frameIndex];
476
+ const variable = frame.locals[name];
477
+ if (variable) {
478
+ return variable.value;
479
+ }
480
+ frameIndex = frame.parentFrameIndex;
481
+ }
482
+
483
+ // Check if it's a function
484
+ if (state.functions[name]) {
485
+ return { __vibeFunction: true, name };
486
+ }
487
+
488
+ throw new Error(`ReferenceError: '${name}' is not defined`);
489
+ }
@@ -0,0 +1,45 @@
1
+ // Core system functions - automatically available in all Vibe modules
2
+ // These functions are injected into scope without requiring an import.
3
+ // They CANNOT be imported via "system" or any other module path.
4
+
5
+ /**
6
+ * Print a message to the console.
7
+ * @param message - The message to print
8
+ */
9
+ export function print(message: unknown): void {
10
+ console.log(message);
11
+ }
12
+
13
+ /**
14
+ * Get an environment variable value.
15
+ * @param name - The environment variable name
16
+ * @param defaultValue - Default value if not set (defaults to empty string)
17
+ * @returns The environment variable value or default
18
+ */
19
+ export function env(name: string, defaultValue: string = ''): string {
20
+ return process.env[name] ?? defaultValue;
21
+ }
22
+
23
+ // Registry of all core functions
24
+ // These are checked during identifier resolution before throwing "not defined" error
25
+ export const coreFunctions: Record<string, (...args: unknown[]) => unknown> = {
26
+ print,
27
+ env,
28
+ };
29
+
30
+ // Set of core function names for quick lookup
31
+ export const coreFunctionNames = new Set(Object.keys(coreFunctions));
32
+
33
+ /**
34
+ * Check if a name is a core function
35
+ */
36
+ export function isCoreFunction(name: string): boolean {
37
+ return coreFunctionNames.has(name);
38
+ }
39
+
40
+ /**
41
+ * Get a core function by name
42
+ */
43
+ export function getCoreFunction(name: string): ((...args: unknown[]) => unknown) | undefined {
44
+ return coreFunctions[name];
45
+ }
@@ -0,0 +1,156 @@
1
+ // Tests for directory tools: mkdir, listDir, dirExists
2
+
3
+ import { describe, test, expect, beforeEach, afterEach } from 'bun:test';
4
+ import { mkdir, listDir, dirExists } from './tools';
5
+ import * as fs from 'fs';
6
+ import * as path from 'path';
7
+
8
+ const TEST_DIR = path.join(__dirname, '.test-workspace-directory');
9
+
10
+ describe('mkdir tool', () => {
11
+ beforeEach(() => {
12
+ fs.mkdirSync(TEST_DIR, { recursive: true });
13
+ });
14
+
15
+ afterEach(() => {
16
+ if (fs.existsSync(TEST_DIR)) {
17
+ fs.rmSync(TEST_DIR, { recursive: true });
18
+ }
19
+ });
20
+
21
+ test('creates single directory', async () => {
22
+ const dirPath = path.join(TEST_DIR, 'newdir');
23
+
24
+ const result = await mkdir.executor({ path: dirPath }, { rootDir: TEST_DIR });
25
+
26
+ expect(result).toBe(true);
27
+ expect(fs.existsSync(dirPath)).toBe(true);
28
+ expect(fs.statSync(dirPath).isDirectory()).toBe(true);
29
+ });
30
+
31
+ test('creates nested directories with recursive: true', async () => {
32
+ const dirPath = path.join(TEST_DIR, 'a', 'b', 'c');
33
+
34
+ const result = await mkdir.executor({ path: dirPath, recursive: true }, { rootDir: TEST_DIR });
35
+
36
+ expect(result).toBe(true);
37
+ expect(fs.existsSync(dirPath)).toBe(true);
38
+ });
39
+
40
+ test('fails without recursive when parent does not exist', async () => {
41
+ const dirPath = path.join(TEST_DIR, 'parent', 'child');
42
+
43
+ await expect(mkdir.executor({ path: dirPath }, { rootDir: TEST_DIR })).rejects.toThrow();
44
+ });
45
+
46
+ test('succeeds silently if directory already exists with recursive', async () => {
47
+ const dirPath = path.join(TEST_DIR, 'existing');
48
+ fs.mkdirSync(dirPath);
49
+
50
+ const result = await mkdir.executor({ path: dirPath, recursive: true }, { rootDir: TEST_DIR });
51
+
52
+ expect(result).toBe(true);
53
+ });
54
+ });
55
+
56
+ describe('listDir tool', () => {
57
+ beforeEach(() => {
58
+ fs.mkdirSync(TEST_DIR, { recursive: true });
59
+ });
60
+
61
+ afterEach(() => {
62
+ if (fs.existsSync(TEST_DIR)) {
63
+ fs.rmSync(TEST_DIR, { recursive: true });
64
+ }
65
+ });
66
+
67
+ test('lists files in directory', async () => {
68
+ fs.writeFileSync(path.join(TEST_DIR, 'file1.txt'), '');
69
+ fs.writeFileSync(path.join(TEST_DIR, 'file2.txt'), '');
70
+
71
+ const result = (await listDir.executor({ path: TEST_DIR }, { rootDir: TEST_DIR })) as string[];
72
+
73
+ expect(result).toHaveLength(2);
74
+ expect(result).toContain('file1.txt');
75
+ expect(result).toContain('file2.txt');
76
+ });
77
+
78
+ test('lists subdirectories', async () => {
79
+ fs.mkdirSync(path.join(TEST_DIR, 'subdir1'));
80
+ fs.mkdirSync(path.join(TEST_DIR, 'subdir2'));
81
+ fs.writeFileSync(path.join(TEST_DIR, 'file.txt'), '');
82
+
83
+ const result = (await listDir.executor({ path: TEST_DIR }, { rootDir: TEST_DIR })) as string[];
84
+
85
+ expect(result).toHaveLength(3);
86
+ expect(result).toContain('subdir1');
87
+ expect(result).toContain('subdir2');
88
+ expect(result).toContain('file.txt');
89
+ });
90
+
91
+ test('returns empty array for empty directory', async () => {
92
+ const emptyDir = path.join(TEST_DIR, 'empty');
93
+ fs.mkdirSync(emptyDir);
94
+
95
+ const result = (await listDir.executor({ path: emptyDir }, { rootDir: TEST_DIR })) as string[];
96
+
97
+ expect(result).toEqual([]);
98
+ });
99
+
100
+ test('error: directory not found', async () => {
101
+ const nonExistent = path.join(TEST_DIR, 'nonexistent');
102
+
103
+ await expect(listDir.executor({ path: nonExistent }, { rootDir: TEST_DIR })).rejects.toThrow();
104
+ });
105
+
106
+ test('does not list nested contents (shallow)', async () => {
107
+ fs.mkdirSync(path.join(TEST_DIR, 'subdir'));
108
+ fs.writeFileSync(path.join(TEST_DIR, 'subdir', 'nested.txt'), '');
109
+ fs.writeFileSync(path.join(TEST_DIR, 'root.txt'), '');
110
+
111
+ const result = (await listDir.executor({ path: TEST_DIR }, { rootDir: TEST_DIR })) as string[];
112
+
113
+ expect(result).toHaveLength(2);
114
+ expect(result).toContain('subdir');
115
+ expect(result).toContain('root.txt');
116
+ expect(result).not.toContain('nested.txt');
117
+ });
118
+ });
119
+
120
+ describe('dirExists tool', () => {
121
+ beforeEach(() => {
122
+ fs.mkdirSync(TEST_DIR, { recursive: true });
123
+ });
124
+
125
+ afterEach(() => {
126
+ if (fs.existsSync(TEST_DIR)) {
127
+ fs.rmSync(TEST_DIR, { recursive: true });
128
+ }
129
+ });
130
+
131
+ test('returns true for existing directory', async () => {
132
+ const dirPath = path.join(TEST_DIR, 'existing');
133
+ fs.mkdirSync(dirPath);
134
+
135
+ const result = await dirExists.executor({ path: dirPath }, { rootDir: TEST_DIR });
136
+
137
+ expect(result).toBe(true);
138
+ });
139
+
140
+ test('returns false for non-existent path', async () => {
141
+ const dirPath = path.join(TEST_DIR, 'nonexistent');
142
+
143
+ const result = await dirExists.executor({ path: dirPath }, { rootDir: TEST_DIR });
144
+
145
+ expect(result).toBe(false);
146
+ });
147
+
148
+ test('returns false for file (not directory)', async () => {
149
+ const filePath = path.join(TEST_DIR, 'file.txt');
150
+ fs.writeFileSync(filePath, 'content');
151
+
152
+ const result = await dirExists.executor({ path: filePath }, { rootDir: TEST_DIR });
153
+
154
+ expect(result).toBe(false);
155
+ });
156
+ });