@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,969 @@
1
+ // Core stepping and instruction execution
2
+
3
+ import type { RuntimeState, Instruction, StackFrame, FrameEntry } from './types';
4
+ import { isVibeValue, resolveValue, createVibeError } from './types';
5
+ import type { ContextMode } from '../ast';
6
+ import { buildLocalContext, buildGlobalContext } from './context';
7
+ import { execDeclareVar, execAssignVar } from './exec/variables';
8
+ import { execAIVibe } from './exec/ai';
9
+ import {
10
+ execStatement,
11
+ execStatements,
12
+ execReturnValue,
13
+ execIfBranch,
14
+ execEnterBlock,
15
+ execExitBlock,
16
+ finalizeModelDeclaration,
17
+ } from './exec/statements';
18
+ import { currentFrame } from './state';
19
+ import { RuntimeError } from '../errors';
20
+ import { requireBoolean } from './validation';
21
+ import {
22
+ execExpression,
23
+ execPushValue,
24
+ execBuildObject,
25
+ execBuildArray,
26
+ execBuildRange,
27
+ execCollectArgs,
28
+ } from './exec/expressions';
29
+ import {
30
+ execInterpolateString,
31
+ execInterpolateTemplate,
32
+ execTsEval,
33
+ } from './exec/typescript';
34
+ import {
35
+ execInterpolatePromptString,
36
+ execInterpolateRegularString,
37
+ execClearPromptContext,
38
+ } from './exec/interpolation';
39
+ import { execCallFunction } from './exec/functions';
40
+ import { execPushFrame, execPopFrame } from './exec/frames';
41
+ import { execToolDeclaration } from './exec/tools';
42
+
43
+ /**
44
+ * Apply context mode on scope exit.
45
+ * - verbose: keep all entries (add scope-exit marker)
46
+ * - forget: remove all entries added during scope (back to entryIndex)
47
+ * - compress: pause for AI to summarize and replace entries with summary
48
+ * Note: Only loops support context modes. Functions always "forget".
49
+ */
50
+ function applyContextMode(
51
+ state: RuntimeState,
52
+ frame: StackFrame,
53
+ contextMode: ContextMode,
54
+ entryIndex: number,
55
+ scopeType: 'for' | 'while',
56
+ label?: string
57
+ ): RuntimeState {
58
+ if (contextMode === 'forget') {
59
+ // Forget: remove all entries from scope (back to before scope-enter)
60
+ const newOrderedEntries = frame.orderedEntries.slice(0, entryIndex);
61
+ return {
62
+ ...state,
63
+ callStack: [
64
+ ...state.callStack.slice(0, -1),
65
+ { ...frame, orderedEntries: newOrderedEntries },
66
+ ],
67
+ };
68
+ }
69
+
70
+ if (contextMode === 'verbose') {
71
+ // Verbose: add scope-exit marker, keep all entries
72
+ const newOrderedEntries = [
73
+ ...frame.orderedEntries,
74
+ { kind: 'scope-exit' as const, scopeType, label },
75
+ ];
76
+ return {
77
+ ...state,
78
+ callStack: [
79
+ ...state.callStack.slice(0, -1),
80
+ { ...frame, orderedEntries: newOrderedEntries },
81
+ ],
82
+ };
83
+ }
84
+
85
+ // Compress mode: pause for AI summarization
86
+ if (typeof contextMode === 'object' && 'compress' in contextMode) {
87
+ const { arg1, arg2 } = contextMode.compress;
88
+
89
+ // Resolve prompt and model from args
90
+ let prompt: string | null = null;
91
+ let modelName: string | null = null;
92
+
93
+ if (arg1) {
94
+ if (arg1.kind === 'literal') {
95
+ // String literal is always a prompt
96
+ prompt = arg1.value;
97
+ } else {
98
+ // Identifier - check if it's a model or prompt variable
99
+ const varValue = lookupVariable(state, arg1.name);
100
+ if (varValue && typeof varValue === 'object' && '__vibeModel' in varValue) {
101
+ // It's a model
102
+ modelName = arg1.name;
103
+ } else {
104
+ // It's a prompt (text value)
105
+ prompt = String(varValue ?? '');
106
+ }
107
+ }
108
+ }
109
+
110
+ if (arg2 && arg2.kind === 'identifier') {
111
+ // Second arg is always model
112
+ modelName = arg2.name;
113
+ }
114
+
115
+ // Fall back to lastUsedModel if no explicit model
116
+ const resolvedModel = modelName ?? state.lastUsedModel;
117
+ if (!resolvedModel) {
118
+ throw new RuntimeError('compress requires a model but none declared', { line: 0, column: 0 }, '');
119
+ }
120
+
121
+ // Extract entries to summarize (from scope-enter to now)
122
+ const entriesToSummarize = frame.orderedEntries.slice(entryIndex);
123
+
124
+ // If empty scope, skip compression
125
+ if (entriesToSummarize.length <= 1) {
126
+ // Only scope-enter, nothing to summarize
127
+ const newOrderedEntries = [
128
+ ...frame.orderedEntries,
129
+ { kind: 'scope-exit' as const, scopeType, label },
130
+ ];
131
+ return {
132
+ ...state,
133
+ callStack: [
134
+ ...state.callStack.slice(0, -1),
135
+ { ...frame, orderedEntries: newOrderedEntries },
136
+ ],
137
+ };
138
+ }
139
+
140
+ // Pause for AI summarization
141
+ return {
142
+ ...state,
143
+ status: 'awaiting_compress',
144
+ pendingCompress: {
145
+ prompt,
146
+ model: resolvedModel,
147
+ entriesToSummarize,
148
+ entryIndex,
149
+ scopeType,
150
+ label,
151
+ },
152
+ };
153
+ }
154
+
155
+ // Default: just return unchanged
156
+ return state;
157
+ }
158
+
159
+ /**
160
+ * Look up a variable's value in the current scope chain.
161
+ */
162
+ function lookupVariable(state: RuntimeState, name: string): unknown {
163
+ // Search from current frame up through scope chain
164
+ for (let i = state.callStack.length - 1; i >= 0; i--) {
165
+ const frame = state.callStack[i];
166
+ if (name in frame.locals) {
167
+ return frame.locals[name].value;
168
+ }
169
+ }
170
+ return undefined;
171
+ }
172
+
173
+ // Get the next instruction that will be executed (or null if done/paused)
174
+ export function getNextInstruction(state: RuntimeState): Instruction | null {
175
+ if (state.status !== 'running' || state.instructionStack.length === 0) {
176
+ return null;
177
+ }
178
+ return state.instructionStack[0];
179
+ }
180
+
181
+ // Step N instructions (or until pause/complete)
182
+ export function stepN(state: RuntimeState, n: number): RuntimeState {
183
+ let current = state;
184
+ for (let i = 0; i < n && current.status === 'running'; i++) {
185
+ current = step(current);
186
+ }
187
+ return current;
188
+ }
189
+
190
+ // Step until a condition is met (returns state where condition is true BEFORE executing)
191
+ export function stepUntilCondition(
192
+ state: RuntimeState,
193
+ predicate: (state: RuntimeState, nextInstruction: Instruction | null) => boolean
194
+ ): RuntimeState {
195
+ let current = state;
196
+
197
+ while (current.status === 'running') {
198
+ const next = getNextInstruction(current);
199
+
200
+ if (predicate(current, next)) {
201
+ return current;
202
+ }
203
+
204
+ if (!next) {
205
+ return current;
206
+ }
207
+
208
+ current = step(current);
209
+ }
210
+
211
+ return current;
212
+ }
213
+
214
+ // Step until we're about to execute a specific statement type
215
+ export function stepUntilStatement(
216
+ state: RuntimeState,
217
+ statementType: string
218
+ ): RuntimeState {
219
+ return stepUntilCondition(state, (_state, next) => {
220
+ if (next?.op === 'exec_statement') {
221
+ return next.stmt.type === statementType;
222
+ }
223
+ return false;
224
+ });
225
+ }
226
+
227
+ // Step until we're about to execute a specific instruction operation
228
+ export function stepUntilOp(
229
+ state: RuntimeState,
230
+ op: Instruction['op']
231
+ ): RuntimeState {
232
+ return stepUntilCondition(state, (_state, next) => next?.op === op);
233
+ }
234
+
235
+ // Execute a single instruction and return new state
236
+ export function step(state: RuntimeState): RuntimeState {
237
+ if (state.status !== 'running') {
238
+ return state;
239
+ }
240
+
241
+ if (state.instructionStack.length === 0) {
242
+ return {
243
+ ...state,
244
+ status: 'completed',
245
+ localContext: buildLocalContext(state),
246
+ globalContext: buildGlobalContext(state),
247
+ };
248
+ }
249
+
250
+ const stateWithContext: RuntimeState = {
251
+ ...state,
252
+ localContext: buildLocalContext(state),
253
+ globalContext: buildGlobalContext(state),
254
+ };
255
+
256
+ const [instruction, ...restInstructions] = stateWithContext.instructionStack;
257
+ const newState: RuntimeState = { ...stateWithContext, instructionStack: restInstructions };
258
+
259
+ try {
260
+ return executeInstruction(newState, instruction);
261
+ } catch (error) {
262
+ const errorObj = error instanceof Error ? error : new Error(String(error));
263
+ return {
264
+ ...newState,
265
+ status: 'error',
266
+ error: errorObj.message,
267
+ errorObject: errorObj,
268
+ };
269
+ }
270
+ }
271
+
272
+ // Run until we hit a pause point or complete
273
+ export function runUntilPause(state: RuntimeState): RuntimeState {
274
+ let current = state;
275
+ while (current.status === 'running' && current.instructionStack.length > 0) {
276
+ current = step(current);
277
+ }
278
+
279
+ if (current.status === 'running' && current.instructionStack.length === 0) {
280
+ return {
281
+ ...current,
282
+ status: 'completed',
283
+ localContext: buildLocalContext(current),
284
+ globalContext: buildGlobalContext(current),
285
+ };
286
+ }
287
+ return current;
288
+ }
289
+
290
+ // Execute a single instruction
291
+ function executeInstruction(state: RuntimeState, instruction: Instruction): RuntimeState {
292
+ switch (instruction.op) {
293
+ case 'exec_statement':
294
+ return execStatement(state, instruction.stmt);
295
+
296
+ case 'exec_expression':
297
+ return execExpression(state, instruction.expr);
298
+
299
+ case 'exec_statements':
300
+ return execStatements(state, instruction.stmts, instruction.index, instruction.location);
301
+
302
+ case 'declare_var':
303
+ return execDeclareVar(state, instruction.name, instruction.isConst, instruction.type, undefined, instruction.isPrivate, instruction.location);
304
+
305
+ case 'assign_var':
306
+ return execAssignVar(state, instruction.name, instruction.location);
307
+
308
+ case 'call_function':
309
+ return execCallFunction(state, instruction.funcName, instruction.argCount, instruction.location);
310
+
311
+ case 'push_frame':
312
+ return execPushFrame(state, instruction.name);
313
+
314
+ case 'pop_frame':
315
+ return execPopFrame(state);
316
+
317
+ case 'return_value':
318
+ return execReturnValue(state);
319
+
320
+ case 'enter_block':
321
+ return execEnterBlock(state, instruction.savedKeys);
322
+
323
+ case 'exit_block':
324
+ return execExitBlock(state, instruction.savedKeys, instruction.location);
325
+
326
+ case 'clear_async_context':
327
+ // Clear all async context flags (used after fire-and-forget async statements)
328
+ return {
329
+ ...state,
330
+ currentAsyncVarName: null,
331
+ currentAsyncIsConst: false,
332
+ currentAsyncType: null,
333
+ currentAsyncIsPrivate: false,
334
+ currentAsyncIsDestructure: false,
335
+ currentAsyncIsFireAndForget: false,
336
+ };
337
+
338
+ case 'ai_vibe':
339
+ return execAIVibe(state, instruction.model, instruction.context, instruction.operationType);
340
+
341
+ case 'ts_eval':
342
+ return execTsEval(state, instruction.params, instruction.body, instruction.location);
343
+
344
+ case 'call_imported_ts':
345
+ throw new Error('call_imported_ts should be handled in execCallFunction');
346
+
347
+ case 'if_branch':
348
+ return execIfBranch(state, instruction.consequent, instruction.alternate);
349
+
350
+ case 'for_in_init': {
351
+ const { stmt } = instruction;
352
+ let items = state.lastResult;
353
+
354
+ // Handle VibeValue with error - throw the error
355
+ if (isVibeValue(items) && items.err && items.errDetails) {
356
+ throw new RuntimeError(
357
+ `${items.errDetails.type}: ${items.errDetails.message}`,
358
+ instruction.location,
359
+ ''
360
+ );
361
+ }
362
+
363
+ // Auto-unwrap VibeValue - check if value is iterable
364
+ if (isVibeValue(items)) {
365
+ const innerValue = items.value;
366
+ if (!Array.isArray(innerValue) && typeof innerValue !== 'number') {
367
+ const valueType = innerValue === null ? 'null' : typeof innerValue;
368
+ throw new RuntimeError(
369
+ `Cannot iterate over VibeValue: value is ${valueType}, not an array. Use .toolCalls to iterate tool calls.`,
370
+ instruction.location,
371
+ ''
372
+ );
373
+ }
374
+ items = innerValue;
375
+ }
376
+
377
+ // Handle range: single number N → [1, 2, ..., N] (inclusive)
378
+ if (typeof items === 'number') {
379
+ if (!Number.isInteger(items)) {
380
+ throw new RuntimeError(`for-in range must be an integer, got ${items}`, instruction.location, '');
381
+ }
382
+ if (items < 0) {
383
+ throw new RuntimeError(`for-in range must be non-negative, got ${items}`, instruction.location, '');
384
+ }
385
+ items = Array.from({ length: items }, (_, i) => i + 1);
386
+ }
387
+
388
+ // Note: Explicit ranges now use the `..` operator (e.g., 2..5)
389
+ // which produces an array before reaching for_in_init
390
+
391
+ if (!Array.isArray(items)) {
392
+ throw new RuntimeError('for-in requires array or range', instruction.location, '');
393
+ }
394
+
395
+ const frame = currentFrame(state);
396
+ const savedKeys = Object.keys(frame.locals);
397
+
398
+ // Add scope-enter marker
399
+ const label = stmt.variable;
400
+ const entryIndex = frame.orderedEntries.length;
401
+ const newOrderedEntries = [
402
+ ...frame.orderedEntries,
403
+ { kind: 'scope-enter' as const, scopeType: 'for' as const, label },
404
+ ];
405
+ const updatedState = {
406
+ ...state,
407
+ callStack: [
408
+ ...state.callStack.slice(0, -1),
409
+ { ...frame, orderedEntries: newOrderedEntries },
410
+ ],
411
+ };
412
+
413
+ return {
414
+ ...updatedState,
415
+ instructionStack: [
416
+ { op: 'for_in_iterate', variable: stmt.variable, items, index: 0, body: stmt.body, savedKeys, contextMode: stmt.contextMode, label, entryIndex, location: instruction.location },
417
+ ...state.instructionStack,
418
+ ],
419
+ };
420
+ }
421
+
422
+ case 'for_in_iterate': {
423
+ const { variable, items, index, body, savedKeys, contextMode, label, entryIndex, location } = instruction;
424
+
425
+ if (index >= items.length) {
426
+ // Loop complete - add scope-exit marker and apply context mode
427
+ const frame = currentFrame(state);
428
+ const exitState = applyContextMode(state, frame, contextMode!, entryIndex, 'for', label);
429
+
430
+ // Cleanup scope variables (will await pending async first)
431
+ return execExitBlock(exitState, savedKeys, location);
432
+ }
433
+
434
+ // First iteration: declare the loop variable
435
+ // Subsequent iterations: assign the new value
436
+ const frame = currentFrame(state);
437
+ let newState: RuntimeState;
438
+ if (frame.locals[variable]) {
439
+ // Variable exists - assign new value
440
+ newState = execAssignVar({ ...state, lastResult: items[index] }, variable);
441
+ } else {
442
+ // First iteration - declare the variable
443
+ newState = execDeclareVar(state, variable, false, null, items[index]);
444
+ }
445
+
446
+ // Get current local variable names to know what to clean up after body execution
447
+ const bodyFrame = currentFrame(newState);
448
+ const bodyKeys = Object.keys(bodyFrame.locals);
449
+
450
+ // Push: enter block, body execution, exit block, then next iteration
451
+ return {
452
+ ...newState,
453
+ instructionStack: [
454
+ { op: 'enter_block', savedKeys: bodyKeys, location: instruction.location },
455
+ ...body.body.map(s => ({ op: 'exec_statement' as const, stmt: s, location: s.location })),
456
+ { op: 'exit_block', savedKeys: bodyKeys, location: instruction.location },
457
+ { op: 'for_in_iterate', variable, items, index: index + 1, body, savedKeys, contextMode, label, entryIndex, location: instruction.location },
458
+ ...state.instructionStack,
459
+ ],
460
+ };
461
+ }
462
+
463
+ case 'while_init': {
464
+ const { stmt, savedKeys } = instruction;
465
+ const condition = requireBoolean(state.lastResult, 'while condition');
466
+
467
+ if (!condition) {
468
+ // Condition false - exit loop (first check, no scope entered yet)
469
+ return state;
470
+ }
471
+
472
+ // Add scope-enter marker on first true condition
473
+ const frame = currentFrame(state);
474
+ const label = undefined;
475
+ const entryIndex = frame.orderedEntries.length;
476
+ const newOrderedEntries = [
477
+ ...frame.orderedEntries,
478
+ { kind: 'scope-enter' as const, scopeType: 'while' as const },
479
+ ];
480
+ const updatedState = {
481
+ ...state,
482
+ callStack: [
483
+ ...state.callStack.slice(0, -1),
484
+ { ...frame, orderedEntries: newOrderedEntries },
485
+ ],
486
+ };
487
+
488
+ // Condition true - execute body then re-check condition
489
+ return {
490
+ ...updatedState,
491
+ instructionStack: [
492
+ { op: 'while_iterate', stmt, savedKeys, contextMode: stmt.contextMode, label, entryIndex, location: instruction.location },
493
+ ...state.instructionStack,
494
+ ],
495
+ };
496
+ }
497
+
498
+ case 'while_iterate': {
499
+ const { stmt, savedKeys, contextMode, label, entryIndex } = instruction;
500
+ const bodyFrame = currentFrame(state);
501
+ const bodyKeys = Object.keys(bodyFrame.locals);
502
+
503
+ // Execute body, cleanup, re-evaluate condition, then check if loop continues
504
+ return {
505
+ ...state,
506
+ instructionStack: [
507
+ { op: 'enter_block', savedKeys: bodyKeys, location: instruction.location },
508
+ ...stmt.body.body.map(s => ({ op: 'exec_statement' as const, stmt: s, location: s.location })),
509
+ { op: 'exit_block', savedKeys: bodyKeys, location: instruction.location },
510
+ { op: 'exec_expression', expr: stmt.condition, location: stmt.condition.location },
511
+ { op: 'while_check', stmt, savedKeys, contextMode, label, entryIndex, location: instruction.location },
512
+ ...state.instructionStack,
513
+ ],
514
+ };
515
+ }
516
+
517
+ case 'while_check': {
518
+ const { stmt, savedKeys, contextMode, label, entryIndex, location } = instruction;
519
+ const condition = requireBoolean(state.lastResult, 'while condition');
520
+
521
+ if (!condition) {
522
+ // Loop complete - add scope-exit marker and apply context mode
523
+ const frame = currentFrame(state);
524
+ const exitState = applyContextMode(state, frame, contextMode!, entryIndex, 'while', label);
525
+
526
+ // Cleanup scope variables (will await pending async first)
527
+ return execExitBlock(exitState, savedKeys, location);
528
+ }
529
+
530
+ // Condition still true - continue loop
531
+ return {
532
+ ...state,
533
+ instructionStack: [
534
+ { op: 'while_iterate', stmt, savedKeys, contextMode, label, entryIndex, location: instruction.location },
535
+ ...state.instructionStack,
536
+ ],
537
+ };
538
+ }
539
+
540
+ case 'break_loop': {
541
+ const { savedKeys, contextMode, label, entryIndex, scopeType, location } = instruction;
542
+
543
+ // First, await any pending async operations in the current scope
544
+ const pendingAsyncIds: string[] = [];
545
+ for (const opId of state.pendingAsyncIds) {
546
+ const operation = state.asyncOperations.get(opId);
547
+ if (operation && (operation.status === 'pending' || operation.status === 'running')) {
548
+ pendingAsyncIds.push(opId);
549
+ }
550
+ }
551
+
552
+ if (pendingAsyncIds.length > 0) {
553
+ // Need to await async operations before breaking
554
+ return {
555
+ ...state,
556
+ status: 'awaiting_async',
557
+ awaitingAsyncIds: pendingAsyncIds,
558
+ // Re-queue break_loop to continue after async completes
559
+ instructionStack: [instruction, ...state.instructionStack],
560
+ };
561
+ }
562
+
563
+ // Apply context mode (may trigger compress for summarization)
564
+ const frame = currentFrame(state);
565
+ const exitState = applyContextMode(state, frame, contextMode ?? 'forget', entryIndex, scopeType, label);
566
+
567
+ // If compress triggered awaiting_compress, don't proceed with exit_block yet
568
+ if (exitState.status === 'awaiting_compress') {
569
+ // Re-queue a simplified break cleanup after compress completes
570
+ return {
571
+ ...exitState,
572
+ instructionStack: [
573
+ { op: 'exit_block', savedKeys, location },
574
+ ...state.instructionStack,
575
+ ],
576
+ };
577
+ }
578
+
579
+ // Cleanup scope variables
580
+ return execExitBlock(exitState, savedKeys, location);
581
+ }
582
+
583
+ case 'push_value':
584
+ return execPushValue(state);
585
+
586
+ case 'build_object':
587
+ return execBuildObject(state, instruction.keys);
588
+
589
+ case 'build_array':
590
+ return execBuildArray(state, instruction.count);
591
+
592
+ case 'build_range':
593
+ return execBuildRange(state);
594
+
595
+ case 'collect_args':
596
+ return execCollectArgs(state, instruction.count);
597
+
598
+ case 'literal':
599
+ return { ...state, lastResult: instruction.value };
600
+
601
+ case 'interpolate_string':
602
+ // Regular string interpolation - {var} expands to value
603
+ return execInterpolateRegularString(state, instruction.template, instruction.location);
604
+
605
+ case 'interpolate_prompt_string':
606
+ // Prompt string interpolation - {var} = reference, !{var} = expand
607
+ return execInterpolatePromptString(state, instruction.template, instruction.location);
608
+
609
+ case 'clear_prompt_context':
610
+ // Clear the inPromptContext flag after evaluating prompt
611
+ return execClearPromptContext(state);
612
+
613
+ case 'interpolate_template':
614
+ // Legacy template literal handling - redirect to regular string (unified to {var} pattern)
615
+ return execInterpolateRegularString(state, instruction.template, instruction.location);
616
+
617
+ case 'binary_op': {
618
+ const rawRight = state.lastResult;
619
+ const rawLeft = state.valueStack[state.valueStack.length - 1];
620
+ const newStack = state.valueStack.slice(0, -1);
621
+
622
+ // Error propagation: if either operand is a VibeValue with error, propagate it
623
+ if (isVibeValue(rawLeft) && rawLeft.err) {
624
+ return { ...state, valueStack: newStack, lastResult: rawLeft };
625
+ }
626
+ if (isVibeValue(rawRight) && rawRight.err) {
627
+ return { ...state, valueStack: newStack, lastResult: rawRight };
628
+ }
629
+
630
+ // Auto-unwrap VibeValue for operations
631
+ const left = resolveValue(rawLeft);
632
+ const right = resolveValue(rawRight);
633
+
634
+ // Handle null in operations
635
+ const op = instruction.operator;
636
+
637
+ // String concatenation with + - coerce null to empty string
638
+ if (op === '+' && (typeof left === 'string' || typeof right === 'string')) {
639
+ const leftStr = left === null ? '' : String(left);
640
+ const rightStr = right === null ? '' : String(right);
641
+ return { ...state, valueStack: newStack, lastResult: leftStr + rightStr };
642
+ }
643
+
644
+ // Arithmetic operations with null - create error
645
+ if (left === null || right === null) {
646
+ if (op === '-' || op === '*' || op === '/' || op === '%' || (op === '+' && typeof left !== 'string' && typeof right !== 'string')) {
647
+ const errorValue = createVibeError(
648
+ `Cannot perform arithmetic operation '${op}' with null`,
649
+ instruction.location
650
+ );
651
+ return { ...state, valueStack: newStack, lastResult: errorValue };
652
+ }
653
+ }
654
+
655
+ const result = evaluateBinaryOp(op, left, right);
656
+ return { ...state, valueStack: newStack, lastResult: result };
657
+ }
658
+
659
+ case 'unary_op': {
660
+ const rawOperand = state.lastResult;
661
+
662
+ // Error propagation: if operand is VibeValue with error, propagate it
663
+ if (isVibeValue(rawOperand) && rawOperand.err) {
664
+ return { ...state, lastResult: rawOperand };
665
+ }
666
+
667
+ // Auto-unwrap VibeValue for operations
668
+ const operand = resolveValue(rawOperand);
669
+ const op = instruction.operator;
670
+
671
+ // Unary minus with null - create error
672
+ if (operand === null && op === '-') {
673
+ const errorValue = createVibeError(
674
+ `Cannot perform unary '${op}' on null`,
675
+ instruction.location
676
+ );
677
+ return { ...state, lastResult: errorValue };
678
+ }
679
+
680
+ const result = evaluateUnaryOp(op, operand);
681
+ return { ...state, lastResult: result };
682
+ }
683
+
684
+ case 'index_access': {
685
+ const rawIndex = state.lastResult;
686
+ const rawArr = state.valueStack[state.valueStack.length - 1];
687
+ const newStack = state.valueStack.slice(0, -1);
688
+
689
+ // Error propagation: if array or index is a VibeValue with error, propagate it
690
+ if (isVibeValue(rawArr) && rawArr.err) {
691
+ return { ...state, valueStack: newStack, lastResult: rawArr };
692
+ }
693
+ if (isVibeValue(rawIndex) && rawIndex.err) {
694
+ return { ...state, valueStack: newStack, lastResult: rawIndex };
695
+ }
696
+
697
+ // Auto-unwrap VibeValue
698
+ const arr = resolveValue(rawArr) as unknown[];
699
+ const index = resolveValue(rawIndex) as number;
700
+
701
+ if (!Array.isArray(arr)) {
702
+ throw new Error(`Cannot index non-array: ${typeof arr}`);
703
+ }
704
+ if (typeof index !== 'number' || !Number.isInteger(index)) {
705
+ throw new Error(`Array index must be an integer, got ${typeof index}`);
706
+ }
707
+
708
+ // Support negative indices (Python-style: -1 = last, -2 = second to last, etc.)
709
+ const normalizedIndex = index < 0 ? arr.length + index : index;
710
+ if (normalizedIndex < 0 || normalizedIndex >= arr.length) {
711
+ throw new Error(`Array index out of bounds: ${index} (length: ${arr.length})`);
712
+ }
713
+
714
+ return { ...state, valueStack: newStack, lastResult: arr[normalizedIndex] };
715
+ }
716
+
717
+ case 'slice_access': {
718
+ const { hasStart, hasEnd } = instruction;
719
+
720
+ // Pop values in reverse order they were pushed
721
+ let rawEnd: unknown;
722
+ let rawStart: unknown;
723
+ let newStack = state.valueStack;
724
+
725
+ if (hasEnd) {
726
+ rawEnd = newStack[newStack.length - 1];
727
+ newStack = newStack.slice(0, -1);
728
+ }
729
+ if (hasStart) {
730
+ rawStart = newStack[newStack.length - 1];
731
+ newStack = newStack.slice(0, -1);
732
+ }
733
+
734
+ const rawArr = newStack[newStack.length - 1];
735
+ newStack = newStack.slice(0, -1);
736
+
737
+ // Error propagation: if array or indices are VibeValues with errors, propagate
738
+ if (isVibeValue(rawArr) && rawArr.err) {
739
+ return { ...state, valueStack: newStack, lastResult: rawArr };
740
+ }
741
+ if (hasStart && isVibeValue(rawStart) && rawStart.err) {
742
+ return { ...state, valueStack: newStack, lastResult: rawStart };
743
+ }
744
+ if (hasEnd && isVibeValue(rawEnd) && rawEnd.err) {
745
+ return { ...state, valueStack: newStack, lastResult: rawEnd };
746
+ }
747
+
748
+ // Auto-unwrap VibeValue
749
+ const arr = resolveValue(rawArr) as unknown[];
750
+ const start = hasStart ? resolveValue(rawStart) as number : undefined;
751
+ const end = hasEnd ? resolveValue(rawEnd) as number : undefined;
752
+
753
+ if (!Array.isArray(arr)) {
754
+ throw new Error(`Cannot slice non-array: ${typeof arr}`);
755
+ }
756
+
757
+ // Default values: start=0, end=arr.length (Python-style)
758
+ let startIdx = start ?? 0;
759
+ let endIdx = end ?? arr.length;
760
+
761
+ if (typeof startIdx !== 'number' || !Number.isInteger(startIdx)) {
762
+ throw new Error(`Slice start must be an integer, got ${typeof startIdx}`);
763
+ }
764
+ if (typeof endIdx !== 'number' || !Number.isInteger(endIdx)) {
765
+ throw new Error(`Slice end must be an integer, got ${typeof endIdx}`);
766
+ }
767
+
768
+ // Support negative indices (Python-style: -1 = last, -2 = second to last, etc.)
769
+ if (startIdx < 0) startIdx = arr.length + startIdx;
770
+ if (endIdx < 0) endIdx = arr.length + endIdx;
771
+
772
+ // Exclusive end slice (Python-style)
773
+ const sliced = arr.slice(startIdx, endIdx);
774
+ return { ...state, valueStack: newStack, lastResult: sliced };
775
+ }
776
+
777
+ // Tool operations
778
+ case 'exec_tool_declaration':
779
+ return execToolDeclaration(state, instruction.decl);
780
+
781
+ // Model declaration - config values are on the valueStack
782
+ case 'declare_model': {
783
+ return finalizeModelDeclaration(state, instruction.stmt);
784
+ }
785
+
786
+ // Destructuring assignment - assign AI result fields to variables
787
+ case 'destructure_assign': {
788
+ const { fields, isConst } = instruction;
789
+
790
+ // Get the result value (should be Record<string, unknown> from AI return)
791
+ let fieldValues: Record<string, unknown>;
792
+ let rawResult = state.lastResult;
793
+
794
+ // Check if this is a pending async operation - if so, await it
795
+ if (isVibeValue(rawResult) && rawResult.asyncOperationId) {
796
+ const opId = rawResult.asyncOperationId;
797
+ const operation = state.asyncOperations.get(opId);
798
+
799
+ // If operation is still pending or running, trigger await
800
+ if (operation && (operation.status === 'pending' || operation.status === 'running')) {
801
+ return {
802
+ ...state,
803
+ status: 'awaiting_async',
804
+ awaitingAsyncIds: [opId],
805
+ // Re-add current instruction to retry after await
806
+ instructionStack: [instruction, ...state.instructionStack],
807
+ };
808
+ }
809
+
810
+ // If operation completed, use the result
811
+ if (operation && operation.status === 'completed' && operation.result) {
812
+ rawResult = operation.result;
813
+ }
814
+ }
815
+
816
+ // Unwrap VibeValue if present
817
+ if (isVibeValue(rawResult)) {
818
+ rawResult = rawResult.value;
819
+ }
820
+
821
+ if (typeof rawResult === 'object' && rawResult !== null) {
822
+ fieldValues = rawResult as Record<string, unknown>;
823
+ } else {
824
+ throw new RuntimeError(
825
+ `Destructuring requires an object, got ${typeof rawResult}`,
826
+ instruction.location,
827
+ ''
828
+ );
829
+ }
830
+
831
+ // Declare each field as a variable
832
+ let newState: RuntimeState = { ...state, pendingDestructuring: null };
833
+ for (const field of fields) {
834
+ const value = fieldValues[field.name];
835
+ if (value === undefined) {
836
+ throw new RuntimeError(
837
+ `Missing field '${field.name}' in destructuring result`,
838
+ instruction.location,
839
+ ''
840
+ );
841
+ }
842
+ newState = execDeclareVar(newState, field.name, isConst, field.type, value, field.isPrivate);
843
+ }
844
+
845
+ return newState;
846
+ }
847
+
848
+ // Member/property access
849
+ case 'member_access': {
850
+ const rawObject = state.lastResult;
851
+ const property = instruction.property;
852
+
853
+ // Handle VibeValue reserved properties first
854
+ if (isVibeValue(rawObject)) {
855
+ // Reserved property: .err - return boolean (true if error)
856
+ if (property === 'err') {
857
+ return { ...state, lastResult: rawObject.err };
858
+ }
859
+ // Reserved property: .errDetails - return error details object
860
+ if (property === 'errDetails') {
861
+ return { ...state, lastResult: rawObject.errDetails };
862
+ }
863
+ // Reserved property: .toolCalls - return tool calls array
864
+ if (property === 'toolCalls') {
865
+ return { ...state, lastResult: rawObject.toolCalls };
866
+ }
867
+ // For all other properties, unwrap and continue with normal handling below
868
+ }
869
+
870
+ // Unwrap VibeValue and AIResultObject for normal property access
871
+ const object = resolveValue(rawObject);
872
+
873
+ // Handle toString() method on any type
874
+ if (property === 'toString') {
875
+ return { ...state, lastResult: { __boundMethod: true, object, method: 'toString' } };
876
+ }
877
+
878
+ // Handle built-in methods on arrays
879
+ if (Array.isArray(object)) {
880
+ if (property === 'len' || property === 'push' || property === 'pop') {
881
+ // Return bound method for calling
882
+ return { ...state, lastResult: { __boundMethod: true, object, method: property } };
883
+ }
884
+ // For numeric properties, do index access
885
+ const index = Number(property);
886
+ if (!isNaN(index)) {
887
+ return { ...state, lastResult: object[index] };
888
+ }
889
+ throw new Error(`Unknown array property: ${property}`);
890
+ }
891
+
892
+ // Handle built-in methods on strings
893
+ if (typeof object === 'string') {
894
+ if (property === 'len') {
895
+ return { ...state, lastResult: { __boundMethod: true, object, method: property } };
896
+ }
897
+ throw new Error(`Unknown string property: ${property}`);
898
+ }
899
+
900
+ // Handle regular object property access
901
+ if (typeof object === 'object' && object !== null) {
902
+ const val = (object as Record<string, unknown>)[property];
903
+ return { ...state, lastResult: val };
904
+ }
905
+
906
+ throw new Error(`Cannot access property '${property}' on ${typeof object}`);
907
+ }
908
+
909
+ default:
910
+ throw new Error(`Unknown instruction: ${(instruction as Instruction).op}`);
911
+ }
912
+ }
913
+
914
+ // Evaluate binary operators
915
+ function evaluateBinaryOp(op: string, left: unknown, right: unknown): unknown {
916
+ switch (op) {
917
+ // Addition / concatenation
918
+ case '+':
919
+ // Array concatenation: [1,2] + [3,4] = [1,2,3,4]
920
+ if (Array.isArray(left) && Array.isArray(right)) {
921
+ return [...left, ...right];
922
+ }
923
+ // String/number addition (JS handles coercion)
924
+ return (left as number) + (right as number);
925
+ case '-':
926
+ return (left as number) - (right as number);
927
+ case '*':
928
+ return (left as number) * (right as number);
929
+ case '/':
930
+ return (left as number) / (right as number);
931
+ case '%':
932
+ return (left as number) % (right as number);
933
+
934
+ // Comparison operators
935
+ case '==':
936
+ return left === right;
937
+ case '!=':
938
+ return left !== right;
939
+ case '<':
940
+ return (left as number) < (right as number);
941
+ case '>':
942
+ return (left as number) > (right as number);
943
+ case '<=':
944
+ return (left as number) <= (right as number);
945
+ case '>=':
946
+ return (left as number) >= (right as number);
947
+
948
+ // Logical operators
949
+ case 'and':
950
+ return Boolean(left) && Boolean(right);
951
+ case 'or':
952
+ return Boolean(left) || Boolean(right);
953
+
954
+ default:
955
+ throw new Error(`Unknown binary operator: ${op}`);
956
+ }
957
+ }
958
+
959
+ // Evaluate unary operators
960
+ function evaluateUnaryOp(op: string, operand: unknown): unknown {
961
+ switch (op) {
962
+ case 'not':
963
+ return !Boolean(operand);
964
+ case '-':
965
+ return -(operand as number);
966
+ default:
967
+ throw new Error(`Unknown unary operator: ${op}`);
968
+ }
969
+ }