dirac-lang 0.1.20 → 0.1.22

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.
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  integrate
3
- } from "./chunk-UXTK2GC2.js";
3
+ } from "./chunk-XGR3IHWY.js";
4
4
  import {
5
5
  DiracParser
6
6
  } from "./chunk-HRHAMPOB.js";
@@ -419,12 +419,12 @@ async function executeIf(session, element) {
419
419
  const condition = await evaluatePredicate(session, conditionElement);
420
420
  if (condition) {
421
421
  if (thenElement) {
422
- const { integrateChildren: integrateChildren2 } = await import("./interpreter-X7EARER5.js");
422
+ const { integrateChildren: integrateChildren2 } = await import("./interpreter-WUPTTWCU.js");
423
423
  await integrateChildren2(session, thenElement);
424
424
  }
425
425
  } else {
426
426
  if (elseElement) {
427
- const { integrateChildren: integrateChildren2 } = await import("./interpreter-X7EARER5.js");
427
+ const { integrateChildren: integrateChildren2 } = await import("./interpreter-WUPTTWCU.js");
428
428
  await integrateChildren2(session, elseElement);
429
429
  }
430
430
  }
@@ -437,7 +437,7 @@ async function evaluatePredicate(session, predicateElement) {
437
437
  return await evaluateCondition(session, predicateElement);
438
438
  }
439
439
  const outputLengthBefore = session.output.length;
440
- const { integrate: integrate2 } = await import("./interpreter-X7EARER5.js");
440
+ const { integrate: integrate2 } = await import("./interpreter-WUPTTWCU.js");
441
441
  await integrate2(session, predicateElement);
442
442
  const newOutputChunks = session.output.slice(outputLengthBefore);
443
443
  const result = newOutputChunks.join("").trim();
@@ -460,11 +460,11 @@ async function evaluateCondition(session, condElement) {
460
460
  }
461
461
  const outputLengthBefore = session.output.length;
462
462
  const args = [];
463
- const { integrate: integrate2 } = await import("./interpreter-X7EARER5.js");
463
+ const { integrate: integrate2 } = await import("./interpreter-WUPTTWCU.js");
464
464
  for (const child of condElement.children) {
465
465
  if (child.tag.toLowerCase() === "arg") {
466
466
  const argOutputStart = session.output.length;
467
- const { integrateChildren: integrateChildren2 } = await import("./interpreter-X7EARER5.js");
467
+ const { integrateChildren: integrateChildren2 } = await import("./interpreter-WUPTTWCU.js");
468
468
  await integrateChildren2(session, child);
469
469
  const newChunks = session.output.slice(argOutputStart);
470
470
  const argValue = newChunks.join("");
@@ -879,16 +879,17 @@ ${expr}
879
879
  for (const v of session.variables) {
880
880
  context[v.name] = v.value;
881
881
  }
882
- const { default: fs3 } = await import("fs");
882
+ const { default: fs4 } = await import("fs");
883
883
  const { default: path2 } = await import("path");
884
884
  const { fileURLToPath } = await import("url");
885
- context.fs = fs3;
885
+ context.fs = fs4;
886
886
  context.path = path2;
887
887
  context.__dirname = process.cwd();
888
888
  context.getParams = () => {
889
889
  const params = session.parameterStack[session.parameterStack.length - 1];
890
890
  return params && params[0] ? params[0] : null;
891
891
  };
892
+ context.session = session;
892
893
  let result;
893
894
  const func = new AsyncFunction(...Object.keys(context), expr);
894
895
  result = await func(...Object.values(context));
@@ -1068,7 +1069,7 @@ async function executeExpr(session, element) {
1068
1069
  }
1069
1070
 
1070
1071
  // src/tags/system.ts
1071
- import { exec } from "child_process";
1072
+ import { exec, spawn } from "child_process";
1072
1073
  import { promisify } from "util";
1073
1074
  var execAsync = promisify(exec);
1074
1075
  async function executeSystem(session, element) {
@@ -1090,8 +1091,22 @@ async function executeSystem(session, element) {
1090
1091
  if (!command.trim()) {
1091
1092
  return;
1092
1093
  }
1094
+ const backgroundAttr = element.attributes?.background;
1095
+ const isBackground = backgroundAttr === "true";
1093
1096
  if (session.debug) {
1094
- console.error(`[SYSTEM] Executing: ${command}`);
1097
+ console.error(`[SYSTEM] Executing${isBackground ? " (background)" : ""}: ${command}`);
1098
+ }
1099
+ if (isBackground) {
1100
+ const child = spawn(command, {
1101
+ detached: true,
1102
+ stdio: "ignore",
1103
+ shell: true
1104
+ });
1105
+ child.unref();
1106
+ if (session.debug) {
1107
+ console.error(`[SYSTEM] Background process started with PID: ${child.pid}`);
1108
+ }
1109
+ return;
1095
1110
  }
1096
1111
  try {
1097
1112
  const { stdout, stderr } = await execAsync(command, {
@@ -1267,7 +1282,7 @@ async function executeTagCheck(session, element) {
1267
1282
  const executeTag = correctedTag || tagName;
1268
1283
  console.error(`[tag-check] Executing <${executeTag}/> as all checks passed and execute=true.`);
1269
1284
  const elementToExecute = correctedTag ? { ...child, tag: correctedTag } : child;
1270
- const { integrate: integrate2 } = await import("./interpreter-X7EARER5.js");
1285
+ const { integrate: integrate2 } = await import("./interpreter-WUPTTWCU.js");
1271
1286
  await integrate2(session, elementToExecute);
1272
1287
  }
1273
1288
  }
@@ -1276,7 +1291,7 @@ async function executeTagCheck(session, element) {
1276
1291
  // src/tags/throw.ts
1277
1292
  async function executeThrow(session, element) {
1278
1293
  const exceptionName = element.attributes?.name || "exception";
1279
- const { integrateChildren: integrateChildren2 } = await import("./interpreter-X7EARER5.js");
1294
+ const { integrateChildren: integrateChildren2 } = await import("./interpreter-WUPTTWCU.js");
1280
1295
  const exceptionDom = {
1281
1296
  tag: "exception-content",
1282
1297
  attributes: { name: exceptionName },
@@ -1289,7 +1304,7 @@ async function executeThrow(session, element) {
1289
1304
  // src/tags/try.ts
1290
1305
  async function executeTry(session, element) {
1291
1306
  setExceptionBoundary(session);
1292
- const { integrateChildren: integrateChildren2 } = await import("./interpreter-X7EARER5.js");
1307
+ const { integrateChildren: integrateChildren2 } = await import("./interpreter-WUPTTWCU.js");
1293
1308
  await integrateChildren2(session, element);
1294
1309
  unsetExceptionBoundary(session);
1295
1310
  }
@@ -1299,7 +1314,7 @@ async function executeCatch(session, element) {
1299
1314
  const exceptionName = element.attributes?.name || "exception";
1300
1315
  const caughtCount = lookupException(session, exceptionName);
1301
1316
  if (caughtCount > 0) {
1302
- const { integrateChildren: integrateChildren2 } = await import("./interpreter-X7EARER5.js");
1317
+ const { integrateChildren: integrateChildren2 } = await import("./interpreter-WUPTTWCU.js");
1303
1318
  await integrateChildren2(session, element);
1304
1319
  }
1305
1320
  flushCurrentException(session);
@@ -1308,7 +1323,7 @@ async function executeCatch(session, element) {
1308
1323
  // src/tags/exception.ts
1309
1324
  async function executeException(session, element) {
1310
1325
  const exceptions = getCurrentExceptions(session);
1311
- const { integrateChildren: integrateChildren2 } = await import("./interpreter-X7EARER5.js");
1326
+ const { integrateChildren: integrateChildren2 } = await import("./interpreter-WUPTTWCU.js");
1312
1327
  for (const exceptionDom of exceptions) {
1313
1328
  await integrateChildren2(session, exceptionDom);
1314
1329
  }
@@ -1448,7 +1463,7 @@ async function executeForeach(session, element) {
1448
1463
  const parser2 = new DiracParser2();
1449
1464
  try {
1450
1465
  const fromElement = parser2.parse(fromAttr);
1451
- const { integrate: integrate2 } = await import("./interpreter-X7EARER5.js");
1466
+ const { integrate: integrate2 } = await import("./interpreter-WUPTTWCU.js");
1452
1467
  await integrate2(session, fromElement);
1453
1468
  } catch (e) {
1454
1469
  session.output = savedOutput;
@@ -1580,6 +1595,110 @@ function extractAttrFromXml(xml, attrName) {
1580
1595
  return match ? match[1] : "";
1581
1596
  }
1582
1597
 
1598
+ // src/tags/environment.ts
1599
+ async function executeEnvironment(session, element) {
1600
+ const name = element.attributes.name;
1601
+ if (!name) {
1602
+ throw new Error("<environment> requires name attribute");
1603
+ }
1604
+ const value = process.env[name] || "";
1605
+ emit(session, value);
1606
+ }
1607
+
1608
+ // src/tags/input.ts
1609
+ import * as fs3 from "fs";
1610
+ import * as readline from "readline";
1611
+ var fileReaders = /* @__PURE__ */ new Map();
1612
+ var fileIterators = /* @__PURE__ */ new Map();
1613
+ async function executeInput(session, element) {
1614
+ const source = element.attributes.source;
1615
+ const mode = element.attributes.mode || "all";
1616
+ const pathAttr = element.attributes.path;
1617
+ if (!source) {
1618
+ throw new Error("<input> requires source attribute (stdin or file)");
1619
+ }
1620
+ if (source === "file" && !pathAttr) {
1621
+ throw new Error('<input source="file"> requires path attribute');
1622
+ }
1623
+ let value = "";
1624
+ if (source === "stdin") {
1625
+ if (mode === "all") {
1626
+ value = await readAllStdin();
1627
+ } else if (mode === "line") {
1628
+ value = await readLineStdin();
1629
+ } else {
1630
+ throw new Error(`<input> invalid mode: ${mode}. Use 'all' or 'line'`);
1631
+ }
1632
+ } else if (source === "file") {
1633
+ const path2 = substituteAttribute(session, pathAttr);
1634
+ if (mode === "all") {
1635
+ value = fs3.readFileSync(path2, "utf-8");
1636
+ } else if (mode === "line") {
1637
+ value = await readLineFromFile(path2);
1638
+ } else {
1639
+ throw new Error(`<input> invalid mode: ${mode}. Use 'all' or 'line'`);
1640
+ }
1641
+ } else {
1642
+ throw new Error(`<input> invalid source: ${source}. Use 'stdin' or 'file'`);
1643
+ }
1644
+ emit(session, value);
1645
+ }
1646
+ async function readAllStdin() {
1647
+ return new Promise((resolve2, reject) => {
1648
+ const chunks = [];
1649
+ process.stdin.on("data", (chunk) => {
1650
+ chunks.push(chunk);
1651
+ });
1652
+ process.stdin.on("end", () => {
1653
+ resolve2(Buffer.concat(chunks).toString("utf-8"));
1654
+ });
1655
+ process.stdin.on("error", (err) => {
1656
+ reject(err);
1657
+ });
1658
+ });
1659
+ }
1660
+ var stdinReader = null;
1661
+ var stdinIterator = null;
1662
+ async function readLineStdin() {
1663
+ if (!stdinReader) {
1664
+ stdinReader = readline.createInterface({
1665
+ input: process.stdin,
1666
+ output: process.stdout,
1667
+ terminal: false
1668
+ });
1669
+ stdinIterator = stdinReader[Symbol.asyncIterator]();
1670
+ }
1671
+ const result = await stdinIterator.next();
1672
+ if (result.done) {
1673
+ stdinReader.close();
1674
+ stdinReader = null;
1675
+ stdinIterator = null;
1676
+ return "";
1677
+ }
1678
+ return result.value;
1679
+ }
1680
+ async function readLineFromFile(path2) {
1681
+ if (!fileReaders.has(path2)) {
1682
+ const fileStream = fs3.createReadStream(path2);
1683
+ const rl = readline.createInterface({
1684
+ input: fileStream,
1685
+ crlfDelay: Infinity
1686
+ });
1687
+ fileReaders.set(path2, rl);
1688
+ fileIterators.set(path2, rl[Symbol.asyncIterator]());
1689
+ }
1690
+ const iterator = fileIterators.get(path2);
1691
+ const result = await iterator.next();
1692
+ if (result.done) {
1693
+ const reader = fileReaders.get(path2);
1694
+ reader.close();
1695
+ fileReaders.delete(path2);
1696
+ fileIterators.delete(path2);
1697
+ return "";
1698
+ }
1699
+ return result.value;
1700
+ }
1701
+
1583
1702
  // src/runtime/interpreter.ts
1584
1703
  async function integrate(session, element) {
1585
1704
  if (session.limits.currentDepth >= session.limits.maxDepth) {
@@ -1672,6 +1791,12 @@ async function integrate(session, element) {
1672
1791
  case "attr":
1673
1792
  await executeAttr(session, element);
1674
1793
  break;
1794
+ case "environment":
1795
+ await executeEnvironment(session, element);
1796
+ break;
1797
+ case "input":
1798
+ await executeInput(session, element);
1799
+ break;
1675
1800
  case "require_module":
1676
1801
  await executeRequireModule(session, element);
1677
1802
  break;
package/dist/cli.js CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  execute
4
- } from "./chunk-VA7VOLNV.js";
5
- import "./chunk-UXTK2GC2.js";
4
+ } from "./chunk-7X5JYLMO.js";
5
+ import "./chunk-XGR3IHWY.js";
6
6
  import "./chunk-HRHAMPOB.js";
7
7
  import "./chunk-E7PWEMZA.js";
8
8
  import "./chunk-52ED23DR.js";
@@ -13,7 +13,7 @@ import "dotenv/config";
13
13
  // package.json
14
14
  var package_default = {
15
15
  name: "dirac-lang",
16
- version: "0.1.20",
16
+ version: "0.1.22",
17
17
  description: "LLM-Augmented Declarative Execution",
18
18
  type: "module",
19
19
  main: "dist/index.js",
package/dist/index.js CHANGED
@@ -2,10 +2,10 @@ import {
2
2
  createLLMAdapter,
3
3
  execute,
4
4
  executeUserCommand
5
- } from "./chunk-VA7VOLNV.js";
5
+ } from "./chunk-7X5JYLMO.js";
6
6
  import {
7
7
  integrate
8
- } from "./chunk-UXTK2GC2.js";
8
+ } from "./chunk-XGR3IHWY.js";
9
9
  import {
10
10
  DiracParser
11
11
  } from "./chunk-HRHAMPOB.js";
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  integrate,
3
3
  integrateChildren
4
- } from "./chunk-UXTK2GC2.js";
4
+ } from "./chunk-XGR3IHWY.js";
5
5
  import "./chunk-HRHAMPOB.js";
6
6
  import "./chunk-E7PWEMZA.js";
7
7
  import "./chunk-52ED23DR.js";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  integrate
3
- } from "./chunk-UXTK2GC2.js";
3
+ } from "./chunk-XGR3IHWY.js";
4
4
  import {
5
5
  DiracParser
6
6
  } from "./chunk-HRHAMPOB.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dirac-lang",
3
- "version": "0.1.20",
3
+ "version": "0.1.22",
4
4
  "description": "LLM-Augmented Declarative Execution",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -29,6 +29,8 @@ import { executeTestIf } from '../tags/test-if.js';
29
29
  import { executeAvailableSubroutines } from '../tags/available-subroutines.js';
30
30
  import { executeForeach } from '../tags/foreach.js';
31
31
  import { executeAttr } from '../tags/attr.js';
32
+ import { executeEnvironment } from '../tags/environment.js';
33
+ import { executeInput } from '../tags/input.js';
32
34
 
33
35
  export async function integrate(session: DiracSession, element: DiracElement): Promise<void> {
34
36
  // Check execution limits
@@ -152,6 +154,14 @@ export async function integrate(session: DiracSession, element: DiracElement): P
152
154
  case 'attr':
153
155
  await executeAttr(session, element);
154
156
  break;
157
+
158
+ case 'environment':
159
+ await executeEnvironment(session, element);
160
+ break;
161
+
162
+ case 'input':
163
+ await executeInput(session, element);
164
+ break;
155
165
 
156
166
  case 'require_module':
157
167
  await executeRequireModule(session, element);
@@ -0,0 +1,21 @@
1
+ /**
2
+ * <environment> tag - read environment variables
3
+ * Usage: <environment name="VAR_NAME" />
4
+ */
5
+
6
+ import type { DiracSession, DiracElement } from '../types/index.js';
7
+ import { emit } from '../runtime/session.js';
8
+
9
+ export async function executeEnvironment(session: DiracSession, element: DiracElement): Promise<void> {
10
+ const name = element.attributes.name;
11
+
12
+ if (!name) {
13
+ throw new Error('<environment> requires name attribute');
14
+ }
15
+
16
+ // Get environment variable value
17
+ const value = process.env[name] || '';
18
+
19
+ // Emit the value to output
20
+ emit(session, value);
21
+ }
package/src/tags/eval.ts CHANGED
@@ -48,6 +48,9 @@ export async function executeEval(session: DiracSession, element: DiracElement):
48
48
  return params && params[0] ? params[0] : null;
49
49
  };
50
50
 
51
+ // Add session object for orchestration (reserved identifier in eval context)
52
+ context.session = session;
53
+
51
54
  let result: any;
52
55
  // Execute as async function to support top-level await
53
56
  const func = new AsyncFunction(...Object.keys(context), expr);
package/src/tags/index.ts CHANGED
@@ -15,6 +15,8 @@ import * as ifTag from './if';
15
15
  import * as importTag from './import';
16
16
  import * as mongodb from './mongodb';
17
17
  import * as requireModule from './require_module';
18
+ import * as environment from './environment';
19
+ import * as input from './input';
18
20
 
19
21
  export default {
20
22
  ...system,
@@ -34,4 +36,6 @@ export default {
34
36
  ...importTag,
35
37
  ...mongodb,
36
38
  ...requireModule,
39
+ ...environment,
40
+ ...input,
37
41
  };
@@ -0,0 +1,159 @@
1
+ /**
2
+ * <input> tag - read from stdin or file
3
+ * Usage:
4
+ * <input source="stdin" mode="all" />
5
+ * <input source="stdin" mode="line" />
6
+ * <input source="file" path="file.txt" mode="all" />
7
+ * <input source="file" path="file.txt" mode="line" />
8
+ */
9
+
10
+ import type { DiracSession, DiracElement } from '../types/index.js';
11
+ import { emit, substituteAttribute } from '../runtime/session.js';
12
+ import * as fs from 'fs';
13
+ import * as readline from 'readline';
14
+
15
+ // Store file readers for line-by-line reading
16
+ const fileReaders = new Map<string, readline.Interface>();
17
+ const fileIterators = new Map<string, AsyncIterator<string>>();
18
+
19
+ export async function executeInput(session: DiracSession, element: DiracElement): Promise<void> {
20
+ const source = element.attributes.source;
21
+ const mode = element.attributes.mode || 'all';
22
+ const pathAttr = element.attributes.path;
23
+
24
+ if (!source) {
25
+ throw new Error('<input> requires source attribute (stdin or file)');
26
+ }
27
+
28
+ if (source === 'file' && !pathAttr) {
29
+ throw new Error('<input source="file"> requires path attribute');
30
+ }
31
+
32
+ let value = '';
33
+
34
+ if (source === 'stdin') {
35
+ if (mode === 'all') {
36
+ // Read all stdin at once
37
+ value = await readAllStdin();
38
+ } else if (mode === 'line') {
39
+ // Read one line from stdin
40
+ value = await readLineStdin();
41
+ } else {
42
+ throw new Error(`<input> invalid mode: ${mode}. Use 'all' or 'line'`);
43
+ }
44
+ } else if (source === 'file') {
45
+ const path = substituteAttribute(session, pathAttr);
46
+
47
+ if (mode === 'all') {
48
+ // Read entire file
49
+ value = fs.readFileSync(path, 'utf-8');
50
+ } else if (mode === 'line') {
51
+ // Read one line from file
52
+ value = await readLineFromFile(path);
53
+ } else {
54
+ throw new Error(`<input> invalid mode: ${mode}. Use 'all' or 'line'`);
55
+ }
56
+ } else {
57
+ throw new Error(`<input> invalid source: ${source}. Use 'stdin' or 'file'`);
58
+ }
59
+
60
+ // Emit the value to output
61
+ emit(session, value);
62
+ }
63
+
64
+ /**
65
+ * Read all stdin content at once
66
+ */
67
+ async function readAllStdin(): Promise<string> {
68
+ return new Promise((resolve, reject) => {
69
+ const chunks: Buffer[] = [];
70
+
71
+ process.stdin.on('data', (chunk) => {
72
+ chunks.push(chunk);
73
+ });
74
+
75
+ process.stdin.on('end', () => {
76
+ resolve(Buffer.concat(chunks).toString('utf-8'));
77
+ });
78
+
79
+ process.stdin.on('error', (err) => {
80
+ reject(err);
81
+ });
82
+ });
83
+ }
84
+
85
+ /**
86
+ * Read one line from stdin
87
+ */
88
+ let stdinReader: readline.Interface | null = null;
89
+ let stdinIterator: AsyncIterator<string> | null = null;
90
+
91
+ async function readLineStdin(): Promise<string> {
92
+ if (!stdinReader) {
93
+ stdinReader = readline.createInterface({
94
+ input: process.stdin,
95
+ output: process.stdout,
96
+ terminal: false
97
+ });
98
+ stdinIterator = stdinReader[Symbol.asyncIterator]();
99
+ }
100
+
101
+ const result = await stdinIterator!.next();
102
+
103
+ if (result.done) {
104
+ // EOF reached, close reader
105
+ stdinReader.close();
106
+ stdinReader = null;
107
+ stdinIterator = null;
108
+ return '';
109
+ }
110
+
111
+ return result.value;
112
+ }
113
+
114
+ /**
115
+ * Read one line from a file
116
+ */
117
+ async function readLineFromFile(path: string): Promise<string> {
118
+ // Create reader if it doesn't exist for this file
119
+ if (!fileReaders.has(path)) {
120
+ const fileStream = fs.createReadStream(path);
121
+ const rl = readline.createInterface({
122
+ input: fileStream,
123
+ crlfDelay: Infinity
124
+ });
125
+ fileReaders.set(path, rl);
126
+ fileIterators.set(path, rl[Symbol.asyncIterator]());
127
+ }
128
+
129
+ const iterator = fileIterators.get(path)!;
130
+ const result = await iterator.next();
131
+
132
+ if (result.done) {
133
+ // EOF reached, close and cleanup
134
+ const reader = fileReaders.get(path)!;
135
+ reader.close();
136
+ fileReaders.delete(path);
137
+ fileIterators.delete(path);
138
+ return '';
139
+ }
140
+
141
+ return result.value;
142
+ }
143
+
144
+ /**
145
+ * Clean up all open file readers (can be called at end of session)
146
+ */
147
+ export function cleanupInputReaders(): void {
148
+ if (stdinReader) {
149
+ stdinReader.close();
150
+ stdinReader = null;
151
+ stdinIterator = null;
152
+ }
153
+
154
+ for (const reader of fileReaders.values()) {
155
+ reader.close();
156
+ }
157
+ fileReaders.clear();
158
+ fileIterators.clear();
159
+ }
@@ -5,7 +5,7 @@
5
5
 
6
6
  import type { DiracSession, DiracElement } from '../types/index.js';
7
7
  import { substituteVariables, emit } from '../runtime/session.js';
8
- import { exec } from 'child_process';
8
+ import { exec, spawn } from 'child_process';
9
9
  import { promisify } from 'util';
10
10
  import { integrate } from '../runtime/interpreter.js';
11
11
 
@@ -43,10 +43,33 @@ export async function executeSystem(session: DiracSession, element: DiracElement
43
43
  return;
44
44
  }
45
45
 
46
+ // Check for background attribute
47
+ const backgroundAttr = element.attributes?.background;
48
+ const isBackground = backgroundAttr === 'true';
49
+
46
50
  if (session.debug) {
47
- console.error(`[SYSTEM] Executing: ${command}`);
51
+ console.error(`[SYSTEM] Executing${isBackground ? ' (background)' : ''}: ${command}`);
52
+ }
53
+
54
+ // Background mode - spawn and don't wait
55
+ if (isBackground) {
56
+ const child = spawn(command, {
57
+ detached: true,
58
+ stdio: 'ignore',
59
+ shell: true,
60
+ });
61
+
62
+ // Unref so parent can exit without waiting
63
+ child.unref();
64
+
65
+ if (session.debug) {
66
+ console.error(`[SYSTEM] Background process started with PID: ${child.pid}`);
67
+ }
68
+
69
+ return;
48
70
  }
49
71
 
72
+ // Foreground mode - wait for completion (original behavior)
50
73
  try {
51
74
  const { stdout, stderr } = await execAsync(command, {
52
75
  encoding: 'utf-8',
@@ -0,0 +1,19 @@
1
+ <dirac>
2
+ <defvar name="counter" value="0" />
3
+
4
+ <loop count="4">
5
+ <defvar name="line">
6
+ <input source="file" path="tests/test-input.txt" mode="line" />
7
+ </defvar>
8
+
9
+ <test-if test="$line != ''">
10
+ <assign name="counter">
11
+ <eval><variable name="counter" /> + 1</eval>
12
+ </assign>
13
+ <variable name="counter" />
14
+ <output>:</output>
15
+ <variable name="line" />
16
+ <output>|</output>
17
+ </test-if>
18
+ </loop>
19
+ </dirac>
@@ -0,0 +1,22 @@
1
+ <!-- TEST: environment_basic -->
2
+ <!-- EXPECT: HOME exists: true USER exists: true -->
3
+ <dirac>
4
+ <defvar name="home"><environment name="HOME" /></defvar>
5
+ <defvar name="user"><environment name="USER" /></defvar>
6
+
7
+ <output>HOME exists: </output>
8
+ <test-if test="$home != ''">
9
+ <output>true</output>
10
+ </test-if>
11
+ <test-if test="$home == ''">
12
+ <output>false</output>
13
+ </test-if>
14
+
15
+ <output> USER exists: </output>
16
+ <test-if test="$user != ''">
17
+ <output>true</output>
18
+ </test-if>
19
+ <test-if test="$user == ''">
20
+ <output>false</output>
21
+ </test-if>
22
+ </dirac>
@@ -0,0 +1,5 @@
1
+ <!-- TEST: environment_nonexistent -->
2
+ <!-- EXPECT: Empty: [] -->
3
+ <dirac>
4
+ <output>Empty: [<environment name="NONEXISTENT_VAR_12345_TEST" />]</output>
5
+ </dirac>
@@ -0,0 +1,15 @@
1
+ <!-- TEST: environment_with_defvar -->
2
+ <!-- EXPECT: Path exists: true -->
3
+ <dirac>
4
+ <defvar name="mypath">
5
+ <environment name="PATH" />
6
+ </defvar>
7
+
8
+ <output>Path exists: </output>
9
+ <test-if test="$mypath != ''">
10
+ <output>true</output>
11
+ </test-if>
12
+ <test-if test="$mypath == ''">
13
+ <output>false</output>
14
+ </test-if>
15
+ </dirac>
@@ -0,0 +1,5 @@
1
+ <!-- TEST: input_file_all -->
2
+ <!-- EXPECT: line one line two line three -->
3
+ <dirac>
4
+ <input source="file" path="tests/test-input.txt" mode="all" />
5
+ </dirac>
@@ -0,0 +1,15 @@
1
+ <!-- TEST: input_file_line -->
2
+ <!-- EXPECT: First: line one Second: line two Third: line three EOF: -->
3
+ <dirac>
4
+ <output>First: </output>
5
+ <input source="file" path="tests/test-input.txt" mode="line" />
6
+
7
+ <output> Second: </output>
8
+ <input source="file" path="tests/test-input.txt" mode="line" />
9
+
10
+ <output> Third: </output>
11
+ <input source="file" path="tests/test-input.txt" mode="line" />
12
+
13
+ <output> EOF: </output>
14
+ <input source="file" path="tests/test-input.txt" mode="line" />
15
+ </dirac>
@@ -0,0 +1,12 @@
1
+ <!-- TEST: input_file_with_loop -->
2
+ <!-- EXPECT: line one | line two | line three | -->
3
+ <dirac>
4
+ <loop count="3">
5
+ <defvar name="line">
6
+ <input source="file" path="tests/test-input.txt" mode="line" />
7
+ </defvar>
8
+
9
+ <variable name="line" />
10
+ <output> | </output>
11
+ </loop>
12
+ </dirac>
@@ -0,0 +1,39 @@
1
+ <dirac>
2
+ <output>Testing background processes with loop...</output>
3
+
4
+ <!-- Setup: Clean up old test files -->
5
+ <system>rm -f /tmp/dirac-bg-counter.txt</system>
6
+
7
+ <!-- Start a background process that writes incrementing numbers -->
8
+ <system background="true">(for i in 1 2 3 4 5; do echo "Count: $i"; sleep 1; done > /tmp/dirac-bg-counter.txt) &</system>
9
+ <output>✓ Background counter started</output>
10
+
11
+ <!-- This executes immediately without waiting -->
12
+ <output>✓ DIRAC continuing without blocking</output>
13
+
14
+ <!-- Wait a moment for background process to start writing -->
15
+ <system>sleep 2</system>
16
+
17
+ <!-- Read the results in a loop (3 times, showing progressive output) -->
18
+ <loop count="3">
19
+ <system>wc -l /tmp/dirac-bg-counter.txt 2>/dev/null || echo "0"</system>
20
+ <output>Check: Background file now has lines</output>
21
+ <system>sleep 1</system>
22
+ </loop>
23
+
24
+ <!-- Give background process time to finish all 5 counts -->
25
+ <system>sleep 2</system>
26
+
27
+ <!-- Verify final result contains all 5 counts -->
28
+ <system>wc -l /tmp/dirac-bg-counter.txt</system>
29
+ <output>✓ Background process completed</output>
30
+
31
+ <!-- Verify content -->
32
+ <system>grep -c "Count:" /tmp/dirac-bg-counter.txt</system>
33
+ <output>✓ Found all count entries</output>
34
+
35
+ <!-- Cleanup -->
36
+ <system>rm -f /tmp/dirac-bg-counter.txt</system>
37
+
38
+ <output>✓ Background process with loop test passed</output>
39
+ </dirac>
@@ -0,0 +1,3 @@
1
+ line one
2
+ line two
3
+ line three