setup-cave-workflow-demo.mjs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352
  1. import fs from 'fs/promises';
  2. import os from 'os';
  3. import path from 'path';
  4. const repoRoot = '/Users/ivx/Documents/VLCode-Lite';
  5. const examplesDir = path.join(repoRoot, 'examples', 'workflows');
  6. const emptyWindowWorkflowDir = path.join(os.homedir(), '.vl-code', 'empty-window', '.vl-code', 'workflows');
  7. const solverSource = `import fs from 'fs';
  8. const cavePath = process.argv[2] || 'cave.json';
  9. const cave = JSON.parse(fs.readFileSync(cavePath, 'utf8'));
  10. const trace = [];
  11. const seen = new Map();
  12. let best = null;
  13. function scoreState(node, energy, crystals, path) {
  14. return (node === cave.goal ? 500 : 0) + (crystals * 11) + energy - path.length;
  15. }
  16. function remember(signature, score) {
  17. const prev = seen.get(signature) ?? -Infinity;
  18. if (prev >= score) return false;
  19. seen.set(signature, score);
  20. return true;
  21. }
  22. function explore(node, energy, crystals, path, visitedRooms) {
  23. const room = cave.rooms[node] || {};
  24. const nextVisitedRooms = new Set(visitedRooms);
  25. const gained = nextVisitedRooms.has(node) ? 0 : (room.crystals || 0);
  26. nextVisitedRooms.add(node);
  27. const nextCrystals = crystals + gained;
  28. const nextPath = [...path, node];
  29. const signature = [node, energy, nextCrystals, [...nextVisitedRooms].sort().join(',')].join('|');
  30. const score = scoreState(node, energy, nextCrystals, nextPath);
  31. if (!remember(signature, score)) return;
  32. trace.push(\`visit=\${node}; energy=\${energy}; crystals=\${nextCrystals}; path=\${nextPath.join('>')}\`);
  33. if (!best || score > best.score) {
  34. best = { node, energy, crystals: nextCrystals, path: nextPath, score };
  35. }
  36. if (node === cave.goal) return;
  37. const edges = (cave.tunnels[node] || []).slice().sort((a, b) => {
  38. const aReward = (cave.rooms[a.to]?.crystals || 0) - a.cost;
  39. const bReward = (cave.rooms[b.to]?.crystals || 0) - b.cost;
  40. return bReward - aReward;
  41. });
  42. for (const edge of edges) {
  43. if (energy < edge.cost) {
  44. trace.push(\`skip=\${node}->\${edge.to}; reason=energy; need=\${edge.cost}; have=\${energy}\`);
  45. continue;
  46. }
  47. const recharge = cave.rooms[edge.to]?.recharge || 0;
  48. explore(edge.to, energy - edge.cost + recharge, nextCrystals, nextPath, nextVisitedRooms);
  49. }
  50. }
  51. explore(cave.start, cave.energy, 0, [], new Set());
  52. const reportLines = [
  53. \`zone=\${cave.zone}\`,
  54. \`goal=\${cave.goal}\`,
  55. \`bestNode=\${best?.node || 'unknown'}\`,
  56. \`score=\${best?.score ?? 0}\`,
  57. \`energyLeft=\${best?.energy ?? 0}\`,
  58. \`crystals=\${best?.crystals ?? 0}\`,
  59. \`route=\${best?.path?.join('>') || ''}\`
  60. ];
  61. fs.writeFileSync('report.txt', reportLines.join('\\n'));
  62. fs.writeFileSync('trace.txt', trace.join('\\n'));
  63. console.log(\`best=\${best?.node || 'unknown'}; score=\${best?.score ?? 0}; route=\${best?.path?.join('>') || ''}; crystals=\${best?.crystals ?? 0}\`);
  64. `;
  65. function makeScoutWorkflow(name = 'CaveZoneScout') {
  66. return {
  67. version: '3.16',
  68. name,
  69. description: 'Writes a cave-search program into an isolated child space, runs it, and captures the trace/report.',
  70. steps: [
  71. {
  72. id: 'Tool_010_WriteMap',
  73. tool: 'WriteFile',
  74. input: {
  75. file_path: '="cave.json"',
  76. content: '=mapJson',
  77. },
  78. next: 'Tool_020_WriteSolver',
  79. },
  80. {
  81. id: 'Tool_020_WriteSolver',
  82. tool: 'WriteFile',
  83. input: {
  84. file_path: '="explore-cave.mjs"',
  85. content: solverSource,
  86. },
  87. next: 'Tool_030_RunSolver',
  88. },
  89. {
  90. id: 'Tool_030_RunSolver',
  91. tool: 'Bash',
  92. input: {
  93. command: '="node explore-cave.mjs cave.json"',
  94. },
  95. out: {
  96. '$solverStdout': '=_result',
  97. },
  98. next: 'Tool_040_ReadReport',
  99. },
  100. {
  101. id: 'Tool_040_ReadReport',
  102. tool: 'ReadFile',
  103. input: {
  104. file_path: '="report.txt"',
  105. },
  106. out: {
  107. '$reportText': '=_result',
  108. },
  109. next: 'Tool_050_ReadTrace',
  110. },
  111. {
  112. id: 'Tool_050_ReadTrace',
  113. tool: 'ReadFile',
  114. input: {
  115. file_path: '="trace.txt"',
  116. limit: 80,
  117. },
  118. out: {
  119. '$traceText': '=_result',
  120. },
  121. next: 'Set_060_Summary',
  122. },
  123. {
  124. id: 'Set_060_Summary',
  125. target: '$zoneSummary',
  126. value: '=zoneName + " => " + $solverStdout',
  127. next: 'Write_070_Summary',
  128. },
  129. {
  130. id: 'Write_070_Summary',
  131. target: '="Artifacts/zone-summary.txt"',
  132. value: '=$zoneSummary',
  133. mode: 'overwrite',
  134. next: 'Stop_End',
  135. },
  136. { id: 'Stop_End' },
  137. ],
  138. };
  139. }
  140. const northMap = {
  141. zone: 'North Rim',
  142. start: 'N0',
  143. goal: 'N7',
  144. energy: 10,
  145. rooms: {
  146. N0: { crystals: 0 },
  147. N1: { crystals: 2 },
  148. N2: { crystals: 1, recharge: 1 },
  149. N3: { crystals: 3 },
  150. N4: { crystals: 2 },
  151. N5: { crystals: 4, recharge: 2 },
  152. N6: { crystals: 1 },
  153. N7: { crystals: 6 },
  154. },
  155. tunnels: {
  156. N0: [{ to: 'N1', cost: 2 }, { to: 'N2', cost: 3 }],
  157. N1: [{ to: 'N3', cost: 2 }, { to: 'N4', cost: 3 }],
  158. N2: [{ to: 'N4', cost: 2 }, { to: 'N5', cost: 4 }],
  159. N3: [{ to: 'N6', cost: 2 }],
  160. N4: [{ to: 'N6', cost: 1 }, { to: 'N5', cost: 2 }],
  161. N5: [{ to: 'N7', cost: 2 }],
  162. N6: [{ to: 'N7', cost: 1 }],
  163. },
  164. };
  165. const southMap = {
  166. zone: 'South Spiral',
  167. start: 'S0',
  168. goal: 'S8',
  169. energy: 11,
  170. rooms: {
  171. S0: { crystals: 0 },
  172. S1: { crystals: 1 },
  173. S2: { crystals: 3 },
  174. S3: { crystals: 2, recharge: 1 },
  175. S4: { crystals: 4 },
  176. S5: { crystals: 1 },
  177. S6: { crystals: 5, recharge: 2 },
  178. S7: { crystals: 2 },
  179. S8: { crystals: 7 },
  180. },
  181. tunnels: {
  182. S0: [{ to: 'S1', cost: 1 }, { to: 'S2', cost: 3 }],
  183. S1: [{ to: 'S3', cost: 2 }, { to: 'S4', cost: 4 }],
  184. S2: [{ to: 'S4', cost: 2 }, { to: 'S5', cost: 3 }],
  185. S3: [{ to: 'S6', cost: 2 }],
  186. S4: [{ to: 'S6', cost: 1 }, { to: 'S7', cost: 2 }],
  187. S5: [{ to: 'S7', cost: 2 }],
  188. S6: [{ to: 'S8', cost: 2 }],
  189. S7: [{ to: 'S8', cost: 1 }],
  190. },
  191. };
  192. const anomalyMap = {
  193. zone: 'Deep Anomaly',
  194. start: 'D0',
  195. goal: 'D9',
  196. energy: 14,
  197. rooms: {
  198. D0: { crystals: 0 },
  199. D1: { crystals: 2 },
  200. D2: { crystals: 3 },
  201. D3: { crystals: 1, recharge: 1 },
  202. D4: { crystals: 4 },
  203. D5: { crystals: 2, recharge: 2 },
  204. D6: { crystals: 6 },
  205. D7: { crystals: 3 },
  206. D8: { crystals: 5 },
  207. D9: { crystals: 9 },
  208. },
  209. tunnels: {
  210. D0: [{ to: 'D1', cost: 2 }, { to: 'D2', cost: 2 }],
  211. D1: [{ to: 'D3', cost: 2 }, { to: 'D4', cost: 4 }],
  212. D2: [{ to: 'D4', cost: 2 }, { to: 'D5', cost: 3 }],
  213. D3: [{ to: 'D6', cost: 3 }],
  214. D4: [{ to: 'D6', cost: 2 }, { to: 'D7', cost: 2 }],
  215. D5: [{ to: 'D7', cost: 1 }, { to: 'D8', cost: 3 }],
  216. D6: [{ to: 'D9', cost: 2 }],
  217. D7: [{ to: 'D9', cost: 2 }],
  218. D8: [{ to: 'D9', cost: 1 }],
  219. },
  220. };
  221. const scoutWorkflow = makeScoutWorkflow('CaveZoneScout');
  222. const deepScoutWorkflow = makeScoutWorkflow('GeneratedDeepScout');
  223. const supervisorWorkflow = {
  224. version: '3.16',
  225. name: 'CaveSupervisorDemo',
  226. description: 'Supervisor workflow that runs two scout subflows in parallel, pauses for review, then generates a deep-scout child workflow and resumes exploration.',
  227. steps: [
  228. {
  229. id: 'Set_010_OutputRoot',
  230. target: '$outputRoot',
  231. value: '=".codex/demo-artifacts/cave-workflow-demo"',
  232. next: 'Set_020_DeepFlowFile',
  233. },
  234. {
  235. id: 'Set_020_DeepFlowFile',
  236. target: '$deepFlowFile',
  237. value: '=".vl-code/workflows/cave-deep-scout-generated.json"',
  238. next: 'Fork_030_InitialScouts',
  239. },
  240. {
  241. id: 'Fork_030_InitialScouts',
  242. children: ['Subflow_040_NorthScout', 'Subflow_050_SouthScout'],
  243. next: 'Pause_060_ManagerReview',
  244. },
  245. {
  246. id: 'Subflow_040_NorthScout',
  247. workflow_path: 'cave-zone-scout',
  248. mode: 'sync',
  249. work_dir: '=$outputRoot + "/north"',
  250. params: {
  251. zoneName: '="north-rim"',
  252. mapJson: JSON.stringify(northMap, null, 2),
  253. },
  254. out: {
  255. '$northSummary': '=_result.variables["$zoneSummary"]',
  256. '$northReport': '=_result.variables["$reportText"]',
  257. },
  258. },
  259. {
  260. id: 'Subflow_050_SouthScout',
  261. workflow_path: 'cave-zone-scout',
  262. mode: 'sync',
  263. work_dir: '=$outputRoot + "/south"',
  264. params: {
  265. zoneName: '="south-spiral"',
  266. mapJson: JSON.stringify(southMap, null, 2),
  267. },
  268. out: {
  269. '$southSummary': '=_result.variables["$zoneSummary"]',
  270. '$southReport': '=_result.variables["$reportText"]',
  271. },
  272. },
  273. {
  274. id: 'Pause_060_ManagerReview',
  275. reason: 'Manager review: compare north/south scout reports before dispatching a deep anomaly probe.',
  276. next: 'Tool_070_WriteDeepScoutWorkflow',
  277. },
  278. {
  279. id: 'Tool_070_WriteDeepScoutWorkflow',
  280. tool: 'WriteFile',
  281. input: {
  282. file_path: '=$deepFlowFile',
  283. content: JSON.stringify(deepScoutWorkflow, null, 2),
  284. },
  285. next: 'Subflow_080_DeepScout',
  286. },
  287. {
  288. id: 'Subflow_080_DeepScout',
  289. workflow_path: 'cave-deep-scout-generated',
  290. mode: 'sync',
  291. work_dir: '=$outputRoot + "/deep-scout"',
  292. params: {
  293. zoneName: '="deep-anomaly"',
  294. mapJson: JSON.stringify(anomalyMap, null, 2),
  295. },
  296. out: {
  297. '$deepSummary': '=_result.variables["$zoneSummary"]',
  298. '$deepReport': '=_result.variables["$reportText"]',
  299. },
  300. next: 'Set_090_FinalNarrative',
  301. },
  302. {
  303. id: 'Set_090_FinalNarrative',
  304. target: '$finalNarrative',
  305. value: '="North\\n" + $northSummary + "\\n\\nSouth\\n" + $southSummary + "\\n\\nDeep\\n" + $deepSummary',
  306. next: 'Write_100_ManagerReport',
  307. },
  308. {
  309. id: 'Write_100_ManagerReport',
  310. target: '=$outputRoot + "/manager-report.txt"',
  311. value: '=$finalNarrative',
  312. mode: 'overwrite',
  313. next: 'Stop_End',
  314. },
  315. { id: 'Stop_End' },
  316. ],
  317. };
  318. async function writeWorkflow(targetDir, fileName, workflow) {
  319. await fs.mkdir(targetDir, { recursive: true });
  320. await fs.writeFile(path.join(targetDir, fileName), JSON.stringify(workflow, null, 2), 'utf8');
  321. }
  322. await writeWorkflow(examplesDir, 'cave-zone-scout.json', scoutWorkflow);
  323. await writeWorkflow(examplesDir, 'cave-supervisor-demo.json', supervisorWorkflow);
  324. await writeWorkflow(emptyWindowWorkflowDir, 'cave-zone-scout.json', scoutWorkflow);
  325. await writeWorkflow(emptyWindowWorkflowDir, 'cave-supervisor-demo.json', supervisorWorkflow);
  326. console.log(JSON.stringify({
  327. ok: true,
  328. examplesDir,
  329. emptyWindowWorkflowDir,
  330. files: ['cave-zone-scout.json', 'cave-supervisor-demo.json'],
  331. }, null, 2));