agency-lang 0.0.104 → 0.0.105

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.
@@ -66,16 +66,14 @@ export declare class TypeScriptBuilder {
66
66
  private checkpointOpts;
67
67
  private getVisibleTypeAliases;
68
68
  private forkBranchSetup;
69
- private static TEMPLATE_FUNCTIONS;
69
+ private static DIRECT_CALL_FUNCTIONS;
70
70
  /**
71
- * Returns true if the function should be called via .invoke() (AgencyFunction).
72
- * The default is true — everything goes through .invoke() UNLESS it's a known
73
- * non-Agency function (TS import, internal __ prefixed helper, or template function).
71
+ * Returns true if a function call should have interrupt-checking boilerplate.
72
+ * Everything gets interrupt handling UNLESS it's a known non-Agency function.
74
73
  */
75
- private isAgencyFunction;
74
+ private shouldHandleInterrupts;
76
75
  private _plainTsImportNames;
77
76
  private _agencyImportNames;
78
- private isPlainTsImport;
79
77
  private _buildImportNameSets;
80
78
  private isGraphNode;
81
79
  private isImpureImportedFunction;
@@ -129,9 +127,11 @@ export declare class TypeScriptBuilder {
129
127
  */
130
128
  private isKnownClassMethod;
131
129
  /**
132
- * Build the __state config object for method calls on Agency class instances.
130
+ * Build the standard state config for __call/__callMethod dispatch.
131
+ * During global init, only ctx is available; otherwise includes threads
132
+ * and interruptData.
133
133
  */
134
- private buildMethodCallConfig;
134
+ private buildStateConfig;
135
135
  /**
136
136
  * Collect all fields for a class, walking the inheritance chain.
137
137
  * Returns parent fields first, then own fields.
@@ -179,7 +179,7 @@ export declare class TypeScriptBuilder {
179
179
  private processFunctionCallAsStatement;
180
180
  private processFunctionCall;
181
181
  private generateFunctionCallExpression;
182
- private emitAgencyFunctionCall;
182
+ private emitRuntimeDispatchCall;
183
183
  private emitDirectFunctionCall;
184
184
  /**
185
185
  * Build a CallType descriptor TsNode for an Agency function call.
@@ -201,43 +201,29 @@ export class TypeScriptBuilder {
201
201
  ts.assign(branchWithBranchKey, ts.obj({ stack: forked })),
202
202
  ];
203
203
  }
204
- // Plain JS functions defined in the imports template or runtime that are NOT
205
- // AgencyFunction instances. Keep this list small — prefer wrapping as AgencyFunction.
206
- static TEMPLATE_FUNCTIONS = new Set([
204
+ // Plain JS functions that bypass __call dispatch and are called directly.
205
+ // These are NOT AgencyFunction instances.
206
+ static DIRECT_CALL_FUNCTIONS = new Set([
207
207
  "approve", "reject", "propagate",
208
208
  "success", "failure",
209
209
  "isInterrupt", "isDebugger", "isRejected", "isApproved",
210
210
  "isSuccess", "isFailure", "mcp"
211
211
  ]);
212
212
  /**
213
- * Returns true if the function should be called via .invoke() (AgencyFunction).
214
- * The default is true — everything goes through .invoke() UNLESS it's a known
215
- * non-Agency function (TS import, internal __ prefixed helper, or template function).
213
+ * Returns true if a function call should have interrupt-checking boilerplate.
214
+ * Everything gets interrupt handling UNLESS it's a known non-Agency function.
216
215
  */
217
- isAgencyFunction(functionName, context) {
218
- if (context === "valueAccess") {
219
- return false;
220
- }
221
- // Internal machinery (builder-emitted helpers like __deepClone, __validateType, etc.)
216
+ shouldHandleInterrupts(functionName) {
222
217
  if (functionName.startsWith("__"))
223
218
  return false;
224
- // Template-defined plain JS functions
225
- if (TypeScriptBuilder.TEMPLATE_FUNCTIONS.has(functionName))
219
+ if (TypeScriptBuilder.DIRECT_CALL_FUNCTIONS.has(functionName))
226
220
  return false;
227
- // Plain TS imports (not from .agency files)
228
- if (this.isPlainTsImport(functionName))
221
+ if (this.isGraphNode(functionName))
229
222
  return false;
230
- // Everything else is (or might be) an AgencyFunction
231
223
  return true;
232
224
  }
233
225
  _plainTsImportNames = null;
234
226
  _agencyImportNames = null;
235
- isPlainTsImport(functionName) {
236
- if (!this._plainTsImportNames) {
237
- this._buildImportNameSets();
238
- }
239
- return this._plainTsImportNames.has(functionName);
240
- }
241
227
  _buildImportNameSets() {
242
228
  this._plainTsImportNames = new Set();
243
229
  this._agencyImportNames = new Set();
@@ -739,30 +725,19 @@ export class TypeScriptBuilder {
739
725
  }
740
726
  case "methodCall": {
741
727
  const isLastInChain = element === node.chain[node.chain.length - 1];
742
- const callNode = this.generateFunctionCallExpression(element.functionCall, "valueAccess");
743
- // The call node is ts.call(ts.id(name), args) — extract callee name and args
744
- if (callNode.kind === "call" &&
745
- callNode.callee.kind === "identifier") {
746
- const isClassMethod = this.isKnownClassMethod(callNode.callee.name);
747
- const args = isClassMethod
748
- ? [...callNode.arguments, this.buildMethodCallConfig()]
749
- : callNode.arguments;
750
- const propNode = ts.prop(result, callNode.callee.name, { optional: element.optional });
751
- const callExpr = ts.call(propNode, args);
752
- // Parenthesize awaited calls when more chain elements follow,
753
- // so .next() runs on the resolved value, not the Promise.
754
- result = isLastInChain
755
- ? ts.await(callExpr)
756
- : ts.raw(`(${this.str(ts.await(callExpr))})`);
757
- }
758
- else {
759
- // Fallback for complex cases (e.g. await-wrapped)
760
- const dot = element.optional ? "?." : ".";
761
- const awaited = `await ${this.str(result)}${dot}${this.str(callNode)}`;
762
- result = isLastInChain
763
- ? ts.raw(awaited)
764
- : ts.raw(`(${awaited})`);
728
+ const fnCall = element.functionCall;
729
+ // Build descriptor from the method call's arguments
730
+ const descriptor = this.buildCallDescriptor(fnCall);
731
+ const configObj = this.buildStateConfig();
732
+ const propArg = ts.str(fnCall.functionName);
733
+ const callArgs = [result, propArg, descriptor, configObj];
734
+ if (element.optional) {
735
+ callArgs.push(ts.bool(true));
765
736
  }
737
+ const callExpr = ts.call(ts.id("__callMethod"), callArgs);
738
+ result = isLastInChain
739
+ ? ts.await(callExpr)
740
+ : ts.raw(`(${this.str(ts.await(callExpr))})`);
766
741
  break;
767
742
  }
768
743
  }
@@ -856,16 +831,22 @@ export class TypeScriptBuilder {
856
831
  return false;
857
832
  }
858
833
  /**
859
- * Build the __state config object for method calls on Agency class instances.
834
+ * Build the standard state config for __call/__callMethod dispatch.
835
+ * During global init, only ctx is available; otherwise includes threads
836
+ * and interruptData.
860
837
  */
861
- buildMethodCallConfig() {
862
- return this.insideGlobalInit
863
- ? ts.functionCallConfig({ ctx: ts.runtime.ctx })
864
- : ts.functionCallConfig({
865
- ctx: ts.runtime.ctx,
866
- threads: ts.runtime.threads,
867
- interruptData: ts.raw("__state?.interruptData"),
868
- });
838
+ buildStateConfig(opts) {
839
+ if (this.insideGlobalInit) {
840
+ return ts.functionCallConfig({ ctx: ts.runtime.ctx });
841
+ }
842
+ return ts.functionCallConfig({
843
+ ctx: ts.runtime.ctx,
844
+ threads: ts.runtime.threads,
845
+ interruptData: ts.raw("__state?.interruptData"),
846
+ stateStack: opts?.stateStack,
847
+ isForked: opts?.isForked,
848
+ ...opts?.extra,
849
+ });
869
850
  }
870
851
  /**
871
852
  * Collect all fields for a class, walking the inheritance chain.
@@ -1396,14 +1377,12 @@ export class TypeScriptBuilder {
1396
1377
  }
1397
1378
  const callNode = this.processFunctionCall(node);
1398
1379
  const scope = this.getCurrentScope();
1399
- if (this.isAgencyFunction(node.functionName, "topLevelStatement") &&
1400
- !this.isGraphNode(node.functionName) &&
1380
+ if (this.shouldHandleInterrupts(node.functionName) &&
1401
1381
  scope.type !== "global") {
1402
1382
  // Async unassigned calls: register with pending promise store, no interrupt check
1403
1383
  if (node.async) {
1404
- // For agency functions, fork the stack for per-thread isolation
1405
- if (this.isAgencyFunction(node.functionName, "topLevelStatement") &&
1406
- !this.isGraphNode(node.functionName)) {
1384
+ // Fork the stack for per-thread isolation
1385
+ if (this.shouldHandleInterrupts(node.functionName)) {
1407
1386
  this._asyncBranchCheckNeeded = true;
1408
1387
  const branchKey = this._subStepPath.join(".");
1409
1388
  let statements = ts.statements(this.forkBranchSetup(branchKey));
@@ -1488,49 +1467,40 @@ export class TypeScriptBuilder {
1488
1467
  const functionName = context === "valueAccess"
1489
1468
  ? node.functionName
1490
1469
  : mapFunctionName(node.functionName);
1491
- const isAgency = this.isAgencyFunction(node.functionName, context);
1492
1470
  const shouldAwait = !node.async && context !== "valueAccess";
1493
- if (isAgency) {
1494
- return this.emitAgencyFunctionCall(node, functionName, shouldAwait, options);
1495
- }
1496
- else if (node.functionName === "system") {
1471
+ // system() is a builder macro — not a real function call
1472
+ if (node.functionName === "system") {
1497
1473
  const argNodes = node.arguments.map((a) => this.processCallArg(a));
1498
1474
  return $(ts.threads.active())
1499
1475
  .prop("push")
1500
1476
  .call([ts.smoltalkSystemMessage(argNodes)])
1501
1477
  .done();
1502
1478
  }
1503
- else {
1479
+ // __-prefixed helpers and DIRECT_CALL_FUNCTIONS: emit plain direct call
1480
+ if (functionName.startsWith("__") ||
1481
+ TypeScriptBuilder.DIRECT_CALL_FUNCTIONS.has(node.functionName)) {
1504
1482
  return this.emitDirectFunctionCall(node, functionName, shouldAwait);
1505
1483
  }
1484
+ // Everything else goes through __call runtime dispatch
1485
+ return this.emitRuntimeDispatchCall(node, functionName, shouldAwait, options);
1506
1486
  }
1507
- emitAgencyFunctionCall(node, functionName, shouldAwait, options) {
1487
+ emitRuntimeDispatchCall(node, functionName, shouldAwait, options) {
1508
1488
  const descriptor = this.buildCallDescriptor(node);
1509
1489
  const locationOpts = node.functionName === "checkpoint" ? {
1510
1490
  moduleId: ts.str(this.moduleId),
1511
1491
  scopeName: ts.str(this.currentScopeName()),
1512
1492
  stepPath: ts.str(this._subStepPath.join(".")),
1513
- } : {};
1514
- const configObj = this.insideGlobalInit
1515
- ? ts.functionCallConfig({
1516
- ctx: ts.runtime.ctx,
1517
- })
1518
- : ts.functionCallConfig({
1519
- ctx: ts.runtime.ctx,
1520
- threads: ts.runtime.threads,
1521
- interruptData: ts.raw("__state?.interruptData"),
1522
- stateStack: options?.stateStack,
1523
- isForked: node.async,
1524
- ...locationOpts,
1525
- });
1493
+ } : undefined;
1494
+ const configObj = this.buildStateConfig({
1495
+ stateStack: options?.stateStack,
1496
+ isForked: node.async,
1497
+ extra: locationOpts,
1498
+ });
1526
1499
  const callee = node.scope
1527
1500
  ? ts.scopedVar(functionName, node.scope, this.moduleId)
1528
1501
  : ts.id(functionName);
1529
- const invokeCall = $(callee)
1530
- .prop("invoke")
1531
- .call([descriptor, configObj])
1532
- .done();
1533
- return shouldAwait ? ts.await(invokeCall) : invokeCall;
1502
+ const callExpr = ts.call(ts.id("__call"), [callee, descriptor, configObj]);
1503
+ return shouldAwait ? ts.await(callExpr) : callExpr;
1534
1504
  }
1535
1505
  emitDirectFunctionCall(node, functionName, shouldAwait) {
1536
1506
  const argNodes = node.arguments.map((a) => this.processCallArg(a));
@@ -1834,9 +1804,8 @@ export class TypeScriptBuilder {
1834
1804
  this.scopedAssign(node.scope, variableName, this.processNode(value), node.accessChain),
1835
1805
  ];
1836
1806
  if (value.async) {
1837
- // For agency functions, fork the stack for per-thread isolation
1838
- if (this.isAgencyFunction(value.functionName, "topLevelStatement") &&
1839
- !this.isGraphNode(value.functionName)) {
1807
+ // Fork the stack for per-thread isolation
1808
+ if (this.shouldHandleInterrupts(value.functionName)) {
1840
1809
  this._asyncBranchCheckNeeded = true;
1841
1810
  const branchKey = this._subStepPath.join(".");
1842
1811
  stmts.unshift(...this.forkBranchSetup(branchKey));
@@ -1888,22 +1857,11 @@ export class TypeScriptBuilder {
1888
1857
  result = ts.index(result, this.processNode(el.index));
1889
1858
  break;
1890
1859
  case "methodCall": {
1891
- const callNode = this.generateFunctionCallExpression(el.functionCall, "valueAccess");
1892
- if (callNode.kind === "call" &&
1893
- callNode.callee.kind === "identifier") {
1894
- const isClassMethod = this.isKnownClassMethod(callNode.callee.name);
1895
- const args = isClassMethod
1896
- ? [...callNode.arguments, this.buildMethodCallConfig()]
1897
- : callNode.arguments;
1898
- const call2 = $(result)
1899
- .prop(callNode.callee.name)
1900
- .call(args)
1901
- .done();
1902
- result = isClassMethod ? ts.await(call2) : call2;
1903
- }
1904
- else {
1905
- result = ts.raw(`${this.str(result)}.${this.str(callNode)}`);
1906
- }
1860
+ const fnCall = el.functionCall;
1861
+ const descriptor = this.buildCallDescriptor(fnCall);
1862
+ const configObj = this.buildStateConfig();
1863
+ const callExpr = ts.call(ts.id("__callMethod"), [result, ts.str(fnCall.functionName), descriptor, configObj]);
1864
+ result = ts.await(callExpr);
1907
1865
  break;
1908
1866
  }
1909
1867
  }
@@ -2083,20 +2041,17 @@ export class TypeScriptBuilder {
2083
2041
  }
2084
2042
  buildHandlerArrow(handlerName) {
2085
2043
  const args = handlerName === "propagate" ? [] : [ts.id("__data")];
2086
- if (TypeScriptBuilder.TEMPLATE_FUNCTIONS.has(handlerName)) {
2044
+ if (TypeScriptBuilder.DIRECT_CALL_FUNCTIONS.has(handlerName)) {
2087
2045
  // Built-in handler (approve/reject/propagate): plain JS function, call directly
2088
2046
  return ts.arrowFn([{ name: "__data", typeAnnotation: "any" }], ts.call(ts.id(handlerName), args), { async: true });
2089
2047
  }
2090
- // User-defined Agency function handler: use .invoke()
2048
+ // User-defined function handler: use __call
2091
2049
  const descriptor = ts.obj({
2092
2050
  type: ts.str("positional"),
2093
2051
  args: ts.arr(args),
2094
2052
  });
2095
- const invokeCall = $(ts.id(handlerName))
2096
- .prop("invoke")
2097
- .call([descriptor])
2098
- .done();
2099
- return ts.arrowFn([{ name: "__data", typeAnnotation: "any" }], ts.await(invokeCall), { async: true });
2053
+ const callExpr = ts.call(ts.id("__call"), [ts.id(handlerName), descriptor, this.buildStateConfig()]);
2054
+ return ts.arrowFn([{ name: "__data", typeAnnotation: "any" }], ts.await(callExpr), { async: true });
2100
2055
  }
2101
2056
  processHandleBlockWithSteps(node) {
2102
2057
  const id = this._subStepPath[this._subStepPath.length - 1];
@@ -2223,78 +2178,49 @@ export class TypeScriptBuilder {
2223
2178
  if (placeholderCount !== 1) {
2224
2179
  throw new Error(`Method call on right side of |> must contain exactly one ? placeholder, got ${placeholderCount}`);
2225
2180
  }
2226
- // Build the receiver: base + all chain elements except the last method call
2227
2181
  const receiver = this.processValueAccessPartial(stage);
2228
- const args = methodArgs.map((a) => a.type === "placeholder" ? pipeArg : this.processNode(a));
2182
+ const argNodes = methodArgs.map((a) => a.type === "placeholder" ? pipeArg : this.processNode(a));
2229
2183
  const methodName = lastElement.functionCall.functionName;
2230
- const callExpr = $(receiver).prop(methodName).call(args).done();
2231
- return ts.arrowFn([{ name: "__pipeArg" }], callExpr, {
2232
- async: true,
2233
- });
2184
+ const descriptor = ts.obj({ type: ts.str("positional"), args: ts.arr(argNodes) });
2185
+ const callExpr = ts.call(ts.id("__callMethod"), [receiver, ts.str(methodName), descriptor, this.buildStateConfig()]);
2186
+ return ts.arrowFn([{ name: "__pipeArg" }], ts.await(callExpr), { async: true });
2234
2187
  }
2235
2188
  }
2236
- // No placeholder: treat as a bare method/property reference
2189
+ // No placeholder: bare method/property reference — use __callMethod to preserve `this`
2190
+ const receiver = this.processValueAccessPartial(stage);
2191
+ const lastEl = stage.chain[stage.chain.length - 1];
2192
+ const propName = lastEl.kind === "property" ? lastEl.name
2193
+ : lastEl.kind === "methodCall" ? lastEl.functionCall.functionName
2194
+ : null;
2195
+ if (propName) {
2196
+ const descriptor = ts.obj({ type: ts.str("positional"), args: ts.arr([pipeArg]) });
2197
+ const callExpr = ts.call(ts.id("__callMethod"), [receiver, ts.str(propName), descriptor, this.buildStateConfig()]);
2198
+ return ts.arrowFn([{ name: "__pipeArg" }], ts.await(callExpr), { async: true });
2199
+ }
2200
+ // Fallback for non-property access (e.g. index): use __call
2237
2201
  const callee = this.processNode(stage);
2238
- const args = [pipeArg];
2239
- return ts.arrowFn([{ name: "__pipeArg" }], ts.call(callee, args), {
2240
- async: true,
2241
- });
2202
+ const descriptor = ts.obj({ type: ts.str("positional"), args: ts.arr([pipeArg]) });
2203
+ const callExpr = ts.call(ts.id("__call"), [callee, descriptor, this.buildStateConfig()]);
2204
+ return ts.arrowFn([{ name: "__pipeArg" }], ts.await(callExpr), { async: true });
2242
2205
  }
2243
2206
  if (stage.type === "variableName") {
2244
- const isAgency = this.isAgencyFunction(stage.value, "topLevelStatement");
2245
- if (isAgency) {
2246
- // value |> fn → async (__pipeArg) => await fn.invoke({ type: "positional", args: [__pipeArg] }, __state)
2247
- const callee = this.processNode(stage);
2248
- const descriptor = ts.obj({
2249
- type: ts.str("positional"),
2250
- args: ts.arr([pipeArg]),
2251
- });
2252
- const stateConfig = ts.functionCallConfig({
2253
- ctx: ts.runtime.ctx,
2254
- threads: ts.runtime.threads,
2255
- interruptData: ts.raw("__state?.interruptData"),
2256
- });
2257
- const invokeCall = $(callee).prop("invoke").call([descriptor, stateConfig]).done();
2258
- return ts.arrowFn([{ name: "__pipeArg" }], ts.await(invokeCall), {
2259
- async: true,
2260
- });
2261
- }
2262
- // Non-agency: direct call
2263
2207
  const callee = this.processNode(stage);
2264
- return ts.arrowFn([{ name: "__pipeArg" }], ts.call(callee, [pipeArg]), {
2265
- async: true,
2266
- });
2208
+ const descriptor = ts.obj({ type: ts.str("positional"), args: ts.arr([pipeArg]) });
2209
+ const callExpr = ts.call(ts.id("__call"), [callee, descriptor, this.buildStateConfig()]);
2210
+ return ts.arrowFn([{ name: "__pipeArg" }], ts.await(callExpr), { async: true });
2267
2211
  }
2268
2212
  if (stage.type === "functionCall") {
2269
2213
  const placeholderCount = stage.arguments.filter((a) => a.type === "placeholder").length;
2270
2214
  if (placeholderCount !== 1) {
2271
2215
  throw new Error(`Function call on right side of |> must contain exactly one ? placeholder, got ${placeholderCount}`);
2272
2216
  }
2273
- const isAgency = this.isAgencyFunction(stage.functionName, "topLevelStatement");
2274
- if (isAgency) {
2275
- // value |> fn(10, ?) → async (__pipeArg) => await fn.invoke({ type: "positional", args: [10, __pipeArg] }, __state)
2276
- const argNodes = stage.arguments.map((a) => a.type === "placeholder" ? pipeArg : this.processNode(a));
2277
- const callee = stage.scope
2278
- ? ts.scopedVar(mapFunctionName(stage.functionName), stage.scope, this.moduleId)
2279
- : ts.raw(mapFunctionName(stage.functionName));
2280
- const descriptor = ts.obj({
2281
- type: ts.str("positional"),
2282
- args: ts.arr(argNodes),
2283
- });
2284
- const stateConfig = ts.functionCallConfig({
2285
- ctx: ts.runtime.ctx,
2286
- threads: ts.runtime.threads,
2287
- interruptData: ts.raw("__state?.interruptData"),
2288
- });
2289
- const invokeCall = $(callee).prop("invoke").call([descriptor, stateConfig]).done();
2290
- return ts.arrowFn([{ name: "__pipeArg" }], ts.await(invokeCall), {
2291
- async: true,
2292
- });
2293
- }
2294
- // Non-agency: direct call
2295
- const rawArgs = stage.arguments.map((a) => a.type === "placeholder" ? pipeArg : this.processNode(a));
2296
- const callee = ts.raw(mapFunctionName(stage.functionName));
2297
- return ts.arrowFn([{ name: "__pipeArg" }], ts.call(callee, rawArgs), { async: true });
2217
+ const argNodes = stage.arguments.map((a) => a.type === "placeholder" ? pipeArg : this.processNode(a));
2218
+ const callee = stage.scope
2219
+ ? ts.scopedVar(mapFunctionName(stage.functionName), stage.scope, this.moduleId)
2220
+ : ts.raw(mapFunctionName(stage.functionName));
2221
+ const descriptor = ts.obj({ type: ts.str("positional"), args: ts.arr(argNodes) });
2222
+ const callExpr = ts.call(ts.id("__call"), [callee, descriptor, this.buildStateConfig()]);
2223
+ return ts.arrowFn([{ name: "__pipeArg" }], ts.await(callExpr), { async: true });
2298
2224
  }
2299
2225
  throw new Error(`Invalid pipe stage type: ${stage.type}`);
2300
2226
  }
@@ -0,0 +1,3 @@
1
+ import type { CallType } from "./agencyFunction.js";
2
+ export declare function __call(target: unknown, descriptor: CallType, state?: unknown): Promise<unknown>;
3
+ export declare function __callMethod(obj: unknown, prop: string | number, descriptor: CallType, state?: unknown, optional?: boolean): Promise<unknown>;
@@ -0,0 +1,30 @@
1
+ import { AgencyFunction } from "./agencyFunction.js";
2
+ export async function __call(target, descriptor, state) {
3
+ if (AgencyFunction.isAgencyFunction(target)) {
4
+ return target.invoke(descriptor, state);
5
+ }
6
+ if (typeof target !== "function") {
7
+ throw new Error(`Cannot call non-function value: ${String(target)}`);
8
+ }
9
+ if (descriptor.type === "named") {
10
+ throw new Error(`Named arguments are not supported for non-Agency function '${target.name || "(anonymous)"}'`);
11
+ }
12
+ return target(...descriptor.args);
13
+ }
14
+ export async function __callMethod(obj, prop, descriptor, state, optional) {
15
+ if (optional && (obj === null || obj === undefined)) {
16
+ return undefined;
17
+ }
18
+ const target = obj[prop];
19
+ if (AgencyFunction.isAgencyFunction(target)) {
20
+ return target.invoke(descriptor, state);
21
+ }
22
+ if (typeof target !== "function") {
23
+ throw new Error(`Cannot call non-function value at property '${String(prop)}': ${String(target)}`);
24
+ }
25
+ if (descriptor.type === "named") {
26
+ throw new Error(`Named arguments are not supported for non-Agency function '${String(prop)}'`);
27
+ }
28
+ // Reuse the single property lookup while preserving `this` binding.
29
+ return Reflect.apply(target, obj, descriptor.args);
30
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,67 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { __call, __callMethod } from "./call.js";
3
+ import { AgencyFunction } from "./agencyFunction.js";
4
+ function makeAgencyFn(fn, name = "testFn") {
5
+ return new AgencyFunction({
6
+ name,
7
+ module: "test.agency",
8
+ fn,
9
+ params: [{ name: "x", hasDefault: false, defaultValue: undefined, variadic: false }],
10
+ toolDefinition: null,
11
+ });
12
+ }
13
+ describe("__call", () => {
14
+ it("calls AgencyFunction via .invoke() with descriptor and state", async () => {
15
+ const fn = makeAgencyFn(async (x, state) => ({ x, state }));
16
+ const result = await __call(fn, { type: "positional", args: [42] }, "myState");
17
+ expect(result).toEqual({ x: 42, state: "myState" });
18
+ });
19
+ it("calls plain TS function by spreading positional args", async () => {
20
+ const fn = (a, b) => a + b;
21
+ const result = await __call(fn, { type: "positional", args: [3, 4] });
22
+ expect(result).toBe(7);
23
+ });
24
+ it("throws on named args to a TS function", async () => {
25
+ const fn = (a) => a;
26
+ await expect(__call(fn, { type: "named", positionalArgs: [], namedArgs: { a: 1 } })).rejects.toThrow("Named arguments are not supported");
27
+ });
28
+ it("throws on non-callable target", async () => {
29
+ await expect(__call(42, { type: "positional", args: [] })).rejects.toThrow("Cannot call non-function value");
30
+ });
31
+ });
32
+ describe("__callMethod", () => {
33
+ it("calls AgencyFunction stored as object property via .invoke()", async () => {
34
+ const fn = makeAgencyFn(async (x, state) => x * 2);
35
+ const obj = { myFunc: fn };
36
+ const result = await __callMethod(obj, "myFunc", { type: "positional", args: [5] });
37
+ expect(result).toBe(10);
38
+ });
39
+ it("calls TS method preserving this binding", async () => {
40
+ const s = new Set();
41
+ await __callMethod(s, "add", { type: "positional", args: [42] });
42
+ expect(s.has(42)).toBe(true);
43
+ });
44
+ it("calls AgencyFunction stored in array by index", async () => {
45
+ const fn = makeAgencyFn(async (x, state) => x + 1);
46
+ const arr = [fn];
47
+ const result = await __callMethod(arr, 0, { type: "positional", args: [10] });
48
+ expect(result).toBe(11);
49
+ });
50
+ it("short-circuits to undefined when optional and obj is null", async () => {
51
+ const result = await __callMethod(null, "foo", { type: "positional", args: [] }, undefined, true);
52
+ expect(result).toBeUndefined();
53
+ });
54
+ it("short-circuits to undefined when optional and obj is undefined", async () => {
55
+ const result = await __callMethod(undefined, "foo", { type: "positional", args: [] }, undefined, true);
56
+ expect(result).toBeUndefined();
57
+ });
58
+ it("calls normally when optional and obj is non-nullish", async () => {
59
+ const obj = { greet: (name) => `hi ${name}` };
60
+ const result = await __callMethod(obj, "greet", { type: "positional", args: ["Bob"] }, undefined, true);
61
+ expect(result).toBe("hi Bob");
62
+ });
63
+ it("throws on named args to a TS method", async () => {
64
+ const obj = { fn: (a) => a };
65
+ await expect(__callMethod(obj, "fn", { type: "named", positionalArgs: [], namedArgs: { a: 1 } })).rejects.toThrow("Named arguments are not supported");
66
+ });
67
+ });
@@ -15,6 +15,7 @@ export { deepClone, extractResponse, createReturnObject, updateTokenStats, } fro
15
15
  export { functionRefReviver } from "./revivers/index.js";
16
16
  export { AgencyFunction, UNSET } from "./agencyFunction.js";
17
17
  export type { FuncParam, CallType, ToolDefinition, AgencyFunctionOpts } from "./agencyFunction.js";
18
+ export { __call, __callMethod } from "./call.js";
18
19
  export { callHook } from "./hooks.js";
19
20
  export type { AgencyCallbacks, CallbackMap, CallbackReturn } from "./hooks.js";
20
21
  export { not, eq, neq, lt, lte, gt, gte, and, or, head, tail, empty, builtinRead, builtinSleep, readSkill, } from "./builtins.js";
@@ -10,6 +10,7 @@ export { FileSink, CallbackSink } from "./trace/sinks.js";
10
10
  export { deepClone, extractResponse, createReturnObject, updateTokenStats, } from "./utils.js";
11
11
  export { functionRefReviver } from "./revivers/index.js";
12
12
  export { AgencyFunction, UNSET } from "./agencyFunction.js";
13
+ export { __call, __callMethod } from "./call.js";
13
14
  export { callHook } from "./hooks.js";
14
15
  export { not, eq, neq, lt, lte, gt, gte, and, or, head, tail, empty, builtinRead, builtinSleep, readSkill, } from "./builtins.js";
15
16
  export { readSkillTool, readSkillToolParams } from "./builtinTools.js";
@@ -1,4 +1,4 @@
1
- export declare const template = "import { fileURLToPath } from \"url\";\nimport __process from \"process\";\nimport { readFileSync, writeFileSync } from \"fs\";\nimport { z } from \"zod\";\nimport { goToNode, color, nanoid } from \"agency-lang\";\nimport { smoltalk } from \"agency-lang\";\nimport path from \"path\";\nimport type { GraphState, InternalFunctionState, Interrupt, InterruptResponse, RewindCheckpoint } from \"agency-lang/runtime\";\nimport {\n RuntimeContext, MessageThread, ThreadStore, Runner, McpManager,\n setupNode, setupFunction, runNode, runPrompt, callHook,\n checkpoint as __checkpoint_impl, getCheckpoint as __getCheckpoint_impl, restore as __restore_impl,\n interrupt, isInterrupt, isDebugger, isRejected, isApproved, interruptWithHandlers, debugStep,\n respondToInterrupt as _respondToInterrupt,\n approveInterrupt as _approveInterrupt,\n rejectInterrupt as _rejectInterrupt,\n resolveInterrupt as _resolveInterrupt,\n modifyInterrupt as _modifyInterrupt,\n rewindFrom as _rewindFrom,\n RestoreSignal,\n deepClone as __deepClone,\n not, eq, neq, lt, lte, gt, gte, and, or,\n head, tail, empty,\n success, failure, isSuccess, isFailure, __pipeBind, __tryCall, __catchResult,\n Schema, __validateType,\n readSkill as _readSkillRaw,\n readSkillTool as __readSkillTool,\n readSkillToolParams as __readSkillToolParams,\n AgencyFunction as __AgencyFunction, UNSET as __UNSET,\n functionRefReviver as __functionRefReviver,\n} from \"agency-lang/runtime\";\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\nconst __cwd = __process.cwd();\n\nconst getDirname = () => __dirname;\n\n{{{runtimeContextCode:string}}}\n\n// Path-dependent builtin wrappers\nexport function readSkill({filepath}: {filepath: string}): string {\n return _readSkillRaw({ filepath, dirname: __dirname });\n}\n\n// Handler result builtins\nfunction approve(value?: any) { return { type: \"approved\" as const, value }; }\nfunction reject(value?: any) { return { type: \"rejected\" as const, value }; }\nfunction propagate() { return { type: \"propagated\" as const }; }\n\n// Interrupt and rewind re-exports bound to this module's context\nexport { interrupt, isInterrupt, isDebugger };\nexport const respondToInterrupt = (interrupt: Interrupt, response: InterruptResponse, opts?: { overrides?: Record<string, unknown>; metadata?: Record<string, any> }) => _respondToInterrupt({ ctx: __globalCtx, interrupt, interruptResponse: response, overrides: opts?.overrides, metadata: opts?.metadata });\nexport const approveInterrupt = (interrupt: Interrupt, opts?: { overrides?: Record<string, unknown>; metadata?: Record<string, any> }) => _approveInterrupt({ ctx: __globalCtx, interrupt, overrides: opts?.overrides, metadata: opts?.metadata });\nexport const rejectInterrupt = (interrupt: Interrupt, opts?: { overrides?: Record<string, unknown>; metadata?: Record<string, any> }) => _rejectInterrupt({ ctx: __globalCtx, interrupt, overrides: opts?.overrides, metadata: opts?.metadata });\nexport const modifyInterrupt = (interrupt: Interrupt, newArguments: Record<string, any>, opts?: { overrides?: Record<string, unknown>; metadata?: Record<string, any> }) => _modifyInterrupt({ ctx: __globalCtx, interrupt, newArguments, overrides: opts?.overrides, metadata: opts?.metadata });\nexport const resolveInterrupt = (interrupt: Interrupt, value: any, opts?: { overrides?: Record<string, unknown>; metadata?: Record<string, any> }) => _resolveInterrupt({ ctx: __globalCtx, interrupt, value, overrides: opts?.overrides, metadata: opts?.metadata });\nexport const rewindFrom = (checkpoint: RewindCheckpoint, overrides: Record<string, unknown>, opts?: { metadata?: Record<string, any> }) => _rewindFrom({ ctx: __globalCtx, checkpoint, overrides, metadata: opts?.metadata });\n\nexport const __setDebugger = (dbg: any) => { __globalCtx.debuggerState = dbg; };\nexport const __setTraceWriter = (tw: any) => { __globalCtx.traceWriter = tw; };\nexport const __getCheckpoints = () => __globalCtx.checkpoints;\n\nconst __toolRegistry: Record<string, any> = {};\n\nfunction __registerTool(value: unknown, name?: string) {\n if (__AgencyFunction.isAgencyFunction(value)) {\n __toolRegistry[name ?? value.name] = value;\n }\n}\n\n// Wrap stateful runtime functions as AgencyFunction instances\nconst checkpoint = __AgencyFunction.create({ name: \"checkpoint\", module: \"__runtime\", fn: __checkpoint_impl, params: [], toolDefinition: null }, __toolRegistry);\nconst getCheckpoint = __AgencyFunction.create({ name: \"getCheckpoint\", module: \"__runtime\", fn: __getCheckpoint_impl, params: [{ name: \"checkpointId\", hasDefault: false, defaultValue: undefined, variadic: false }], toolDefinition: null }, __toolRegistry);\nconst restore = __AgencyFunction.create({ name: \"restore\", module: \"__runtime\", fn: __restore_impl, params: [{ name: \"checkpointIdOrCheckpoint\", hasDefault: false, defaultValue: undefined, variadic: false }, { name: \"options\", hasDefault: false, defaultValue: undefined, variadic: false }], toolDefinition: null }, __toolRegistry);";
1
+ export declare const template = "import { fileURLToPath } from \"url\";\nimport __process from \"process\";\nimport { readFileSync, writeFileSync } from \"fs\";\nimport { z } from \"zod\";\nimport { goToNode, color, nanoid } from \"agency-lang\";\nimport { smoltalk } from \"agency-lang\";\nimport path from \"path\";\nimport type { GraphState, InternalFunctionState, Interrupt, InterruptResponse, RewindCheckpoint } from \"agency-lang/runtime\";\nimport {\n RuntimeContext, MessageThread, ThreadStore, Runner, McpManager,\n setupNode, setupFunction, runNode, runPrompt, callHook,\n checkpoint as __checkpoint_impl, getCheckpoint as __getCheckpoint_impl, restore as __restore_impl,\n interrupt, isInterrupt, isDebugger, isRejected, isApproved, interruptWithHandlers, debugStep,\n respondToInterrupt as _respondToInterrupt,\n approveInterrupt as _approveInterrupt,\n rejectInterrupt as _rejectInterrupt,\n resolveInterrupt as _resolveInterrupt,\n modifyInterrupt as _modifyInterrupt,\n rewindFrom as _rewindFrom,\n RestoreSignal,\n deepClone as __deepClone,\n not, eq, neq, lt, lte, gt, gte, and, or,\n head, tail, empty,\n success, failure, isSuccess, isFailure, __pipeBind, __tryCall, __catchResult,\n Schema, __validateType,\n readSkill as _readSkillRaw,\n readSkillTool as __readSkillTool,\n readSkillToolParams as __readSkillToolParams,\n AgencyFunction as __AgencyFunction, UNSET as __UNSET,\n __call, __callMethod,\n functionRefReviver as __functionRefReviver,\n} from \"agency-lang/runtime\";\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\nconst __cwd = __process.cwd();\n\nconst getDirname = () => __dirname;\n\n{{{runtimeContextCode:string}}}\n\n// Path-dependent builtin wrappers\nexport function readSkill({filepath}: {filepath: string}): string {\n return _readSkillRaw({ filepath, dirname: __dirname });\n}\n\n// Handler result builtins\nfunction approve(value?: any) { return { type: \"approved\" as const, value }; }\nfunction reject(value?: any) { return { type: \"rejected\" as const, value }; }\nfunction propagate() { return { type: \"propagated\" as const }; }\n\n// Interrupt and rewind re-exports bound to this module's context\nexport { interrupt, isInterrupt, isDebugger };\nexport const respondToInterrupt = (interrupt: Interrupt, response: InterruptResponse, opts?: { overrides?: Record<string, unknown>; metadata?: Record<string, any> }) => _respondToInterrupt({ ctx: __globalCtx, interrupt, interruptResponse: response, overrides: opts?.overrides, metadata: opts?.metadata });\nexport const approveInterrupt = (interrupt: Interrupt, opts?: { overrides?: Record<string, unknown>; metadata?: Record<string, any> }) => _approveInterrupt({ ctx: __globalCtx, interrupt, overrides: opts?.overrides, metadata: opts?.metadata });\nexport const rejectInterrupt = (interrupt: Interrupt, opts?: { overrides?: Record<string, unknown>; metadata?: Record<string, any> }) => _rejectInterrupt({ ctx: __globalCtx, interrupt, overrides: opts?.overrides, metadata: opts?.metadata });\nexport const modifyInterrupt = (interrupt: Interrupt, newArguments: Record<string, any>, opts?: { overrides?: Record<string, unknown>; metadata?: Record<string, any> }) => _modifyInterrupt({ ctx: __globalCtx, interrupt, newArguments, overrides: opts?.overrides, metadata: opts?.metadata });\nexport const resolveInterrupt = (interrupt: Interrupt, value: any, opts?: { overrides?: Record<string, unknown>; metadata?: Record<string, any> }) => _resolveInterrupt({ ctx: __globalCtx, interrupt, value, overrides: opts?.overrides, metadata: opts?.metadata });\nexport const rewindFrom = (checkpoint: RewindCheckpoint, overrides: Record<string, unknown>, opts?: { metadata?: Record<string, any> }) => _rewindFrom({ ctx: __globalCtx, checkpoint, overrides, metadata: opts?.metadata });\n\nexport const __setDebugger = (dbg: any) => { __globalCtx.debuggerState = dbg; };\nexport const __setTraceWriter = (tw: any) => { __globalCtx.traceWriter = tw; };\nexport const __getCheckpoints = () => __globalCtx.checkpoints;\n\nconst __toolRegistry: Record<string, any> = {};\n\nfunction __registerTool(value: unknown, name?: string) {\n if (__AgencyFunction.isAgencyFunction(value)) {\n __toolRegistry[name ?? value.name] = value;\n }\n}\n\n// Wrap stateful runtime functions as AgencyFunction instances\nconst checkpoint = __AgencyFunction.create({ name: \"checkpoint\", module: \"__runtime\", fn: __checkpoint_impl, params: [], toolDefinition: null }, __toolRegistry);\nconst getCheckpoint = __AgencyFunction.create({ name: \"getCheckpoint\", module: \"__runtime\", fn: __getCheckpoint_impl, params: [{ name: \"checkpointId\", hasDefault: false, defaultValue: undefined, variadic: false }], toolDefinition: null }, __toolRegistry);\nconst restore = __AgencyFunction.create({ name: \"restore\", module: \"__runtime\", fn: __restore_impl, params: [{ name: \"checkpointIdOrCheckpoint\", hasDefault: false, defaultValue: undefined, variadic: false }, { name: \"options\", hasDefault: false, defaultValue: undefined, variadic: false }], toolDefinition: null }, __toolRegistry);";
2
2
  export type TemplateType = {
3
3
  runtimeContextCode: string;
4
4
  };
@@ -31,6 +31,7 @@ import {
31
31
  readSkillTool as __readSkillTool,
32
32
  readSkillToolParams as __readSkillToolParams,
33
33
  AgencyFunction as __AgencyFunction, UNSET as __UNSET,
34
+ __call, __callMethod,
34
35
  functionRefReviver as __functionRefReviver,
35
36
  } from "agency-lang/runtime";
36
37
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agency-lang",
3
- "version": "0.0.104",
3
+ "version": "0.0.105",
4
4
  "description": "The Agency language",
5
5
  "main": "lib/index.js",
6
6
  "scripts": {