wave-agent-sdk 1.0.4 → 1.0.6

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 (194) hide show
  1. package/dist/agent.d.ts +23 -1
  2. package/dist/agent.js +34 -4
  3. package/dist/managers/aiManager.d.ts +36 -0
  4. package/dist/managers/aiManager.js +240 -8
  5. package/dist/managers/messageManager.d.ts +1 -3
  6. package/dist/managers/messageManager.js +19 -9
  7. package/dist/managers/slashCommandManager.js +28 -0
  8. package/dist/managers/subagentManager.d.ts +0 -2
  9. package/dist/services/session.js +2 -9
  10. package/dist/telemetry/instrumentation.d.ts +1 -1
  11. package/dist/telemetry/instrumentation.js +57 -28
  12. package/dist/tools/bashTool.js +16 -6
  13. package/dist/tools/grepTool.js +11 -3
  14. package/dist/types/index.d.ts +3 -0
  15. package/dist/types/messaging.d.ts +2 -0
  16. package/dist/utils/containerSetup.js +0 -3
  17. package/dist/utils/messageOperations.d.ts +10 -1
  18. package/dist/utils/messageOperations.js +19 -1
  19. package/dist/utils/notificationXml.js +5 -0
  20. package/dist/utils/ripgrep.d.ts +4 -4
  21. package/dist/utils/ripgrep.js +4 -27
  22. package/package.json +20 -8
  23. package/bin/rg +0 -79
  24. package/dist/managers/forkedAgentManager.d.ts +0 -50
  25. package/dist/managers/forkedAgentManager.js +0 -130
  26. package/scripts/install_ripgrep.js +0 -111
  27. package/scripts/postinstall.js +0 -38
  28. package/src/agent.ts +0 -1271
  29. package/src/constants/subagents.ts +0 -4
  30. package/src/constants/toolLimits.ts +0 -15
  31. package/src/constants/tools.ts +0 -24
  32. package/src/core/plugin.ts +0 -237
  33. package/src/core/session.ts +0 -9
  34. package/src/index.ts +0 -38
  35. package/src/managers/MemoryRuleManager.ts +0 -198
  36. package/src/managers/aiManager.ts +0 -2429
  37. package/src/managers/backgroundTaskManager.ts +0 -480
  38. package/src/managers/bangManager.ts +0 -119
  39. package/src/managers/cronManager.ts +0 -401
  40. package/src/managers/foregroundTaskManager.ts +0 -30
  41. package/src/managers/forkedAgentManager.ts +0 -216
  42. package/src/managers/hookManager.ts +0 -1089
  43. package/src/managers/liveConfigManager.ts +0 -445
  44. package/src/managers/lspManager.ts +0 -470
  45. package/src/managers/mcpManager.ts +0 -1010
  46. package/src/managers/messageManager.ts +0 -1180
  47. package/src/managers/messageQueue.ts +0 -157
  48. package/src/managers/permissionManager.ts +0 -1140
  49. package/src/managers/planManager.ts +0 -112
  50. package/src/managers/pluginManager.ts +0 -330
  51. package/src/managers/pluginScopeManager.ts +0 -124
  52. package/src/managers/reversionManager.ts +0 -178
  53. package/src/managers/skillManager.ts +0 -578
  54. package/src/managers/slashCommandManager.ts +0 -564
  55. package/src/managers/subagentManager.ts +0 -915
  56. package/src/managers/toolManager.ts +0 -489
  57. package/src/managers/workflowManager.ts +0 -493
  58. package/src/prompts/autoMemory.ts +0 -33
  59. package/src/prompts/autoMemoryExtraction.ts +0 -146
  60. package/src/prompts/index.ts +0 -506
  61. package/src/prompts/planModeReminders.ts +0 -126
  62. package/src/services/GitService.ts +0 -131
  63. package/src/services/MarketplaceService.ts +0 -1052
  64. package/src/services/MemoryRuleService.ts +0 -71
  65. package/src/services/aiService.ts +0 -919
  66. package/src/services/authService.ts +0 -565
  67. package/src/services/autoMemoryService.ts +0 -280
  68. package/src/services/configurationService.ts +0 -1520
  69. package/src/services/fileWatcher.ts +0 -319
  70. package/src/services/hook.ts +0 -348
  71. package/src/services/initializationService.ts +0 -357
  72. package/src/services/interactionService.ts +0 -229
  73. package/src/services/jsonlHandler.ts +0 -295
  74. package/src/services/memory.ts +0 -234
  75. package/src/services/pluginLoader.ts +0 -252
  76. package/src/services/remoteSettingsService.ts +0 -366
  77. package/src/services/reversionService.ts +0 -122
  78. package/src/services/session.ts +0 -922
  79. package/src/services/taskManager.ts +0 -321
  80. package/src/telemetry/events.ts +0 -65
  81. package/src/telemetry/instrumentation.ts +0 -499
  82. package/src/telemetry/sessionTracing.ts +0 -348
  83. package/src/tools/agentTool.ts +0 -322
  84. package/src/tools/askUserQuestion.ts +0 -160
  85. package/src/tools/bashTool.ts +0 -621
  86. package/src/tools/buildTool.ts +0 -61
  87. package/src/tools/cronCreateTool.ts +0 -161
  88. package/src/tools/cronDeleteTool.ts +0 -51
  89. package/src/tools/cronListTool.ts +0 -47
  90. package/src/tools/editTool.ts +0 -313
  91. package/src/tools/enterPlanMode.ts +0 -124
  92. package/src/tools/enterWorktreeTool.ts +0 -190
  93. package/src/tools/exitPlanMode.ts +0 -139
  94. package/src/tools/exitWorktreeTool.ts +0 -236
  95. package/src/tools/globTool.ts +0 -167
  96. package/src/tools/grepTool.ts +0 -399
  97. package/src/tools/lspTool.ts +0 -883
  98. package/src/tools/readTool.ts +0 -426
  99. package/src/tools/skillTool.ts +0 -248
  100. package/src/tools/taskManagementTools.ts +0 -574
  101. package/src/tools/taskStopTool.ts +0 -78
  102. package/src/tools/types.ts +0 -137
  103. package/src/tools/webFetchTool.ts +0 -373
  104. package/src/tools/workflowTool.ts +0 -205
  105. package/src/tools/writeTool.ts +0 -267
  106. package/src/types/agent.ts +0 -128
  107. package/src/types/auth.ts +0 -19
  108. package/src/types/commands.ts +0 -30
  109. package/src/types/config.ts +0 -43
  110. package/src/types/configuration.ts +0 -168
  111. package/src/types/core.ts +0 -105
  112. package/src/types/cron.ts +0 -13
  113. package/src/types/environment.ts +0 -106
  114. package/src/types/fileSearch.ts +0 -4
  115. package/src/types/history.ts +0 -7
  116. package/src/types/hooks.ts +0 -256
  117. package/src/types/index.ts +0 -41
  118. package/src/types/lsp.ts +0 -98
  119. package/src/types/marketplace.ts +0 -71
  120. package/src/types/mcp.ts +0 -43
  121. package/src/types/memoryRule.ts +0 -31
  122. package/src/types/messaging.ts +0 -114
  123. package/src/types/permissions.ts +0 -80
  124. package/src/types/plugins.ts +0 -39
  125. package/src/types/processes.ts +0 -99
  126. package/src/types/reversion.ts +0 -29
  127. package/src/types/session.ts +0 -12
  128. package/src/types/skills.ts +0 -108
  129. package/src/types/tasks.ts +0 -13
  130. package/src/types/telemetry.ts +0 -98
  131. package/src/types/tools.ts +0 -44
  132. package/src/types/workflow.ts +0 -6
  133. package/src/utils/abortUtils.ts +0 -118
  134. package/src/utils/atomicWrite.ts +0 -61
  135. package/src/utils/bashParser.ts +0 -899
  136. package/src/utils/cacheControlUtils.ts +0 -426
  137. package/src/utils/commandArgumentParser.ts +0 -161
  138. package/src/utils/commandPathResolver.ts +0 -88
  139. package/src/utils/configPaths.ts +0 -220
  140. package/src/utils/configValidator.ts +0 -100
  141. package/src/utils/constants.ts +0 -39
  142. package/src/utils/container.ts +0 -92
  143. package/src/utils/containerSetup.ts +0 -371
  144. package/src/utils/convertMessagesForAPI.ts +0 -326
  145. package/src/utils/cronTasks.ts +0 -128
  146. package/src/utils/cronTasksLock.ts +0 -182
  147. package/src/utils/cronToHuman.ts +0 -99
  148. package/src/utils/customCommands.ts +0 -95
  149. package/src/utils/editUtils.ts +0 -20
  150. package/src/utils/fileFormat.ts +0 -40
  151. package/src/utils/fileSearch.ts +0 -151
  152. package/src/utils/fileUtils.ts +0 -223
  153. package/src/utils/gitUtils.ts +0 -315
  154. package/src/utils/globalLogger.ts +0 -128
  155. package/src/utils/groupMessagesByApiRound.ts +0 -121
  156. package/src/utils/hookMatcher.ts +0 -176
  157. package/src/utils/markdownParser.ts +0 -317
  158. package/src/utils/mcpUtils.ts +0 -138
  159. package/src/utils/messageOperations.ts +0 -641
  160. package/src/utils/modelCapabilities.ts +0 -30
  161. package/src/utils/nameGenerator.ts +0 -99
  162. package/src/utils/notificationXml.ts +0 -57
  163. package/src/utils/openaiClient.ts +0 -251
  164. package/src/utils/parseCronExpression.ts +0 -78
  165. package/src/utils/path.ts +0 -72
  166. package/src/utils/pathEncoder.ts +0 -400
  167. package/src/utils/pathSafety.ts +0 -40
  168. package/src/utils/promptHistory.ts +0 -168
  169. package/src/utils/ripgrep.ts +0 -35
  170. package/src/utils/shellResolver.ts +0 -190
  171. package/src/utils/skillParser.ts +0 -246
  172. package/src/utils/stringUtils.ts +0 -156
  173. package/src/utils/subagentParser.ts +0 -279
  174. package/src/utils/taskReminder.ts +0 -96
  175. package/src/utils/tokenCalculation.ts +0 -43
  176. package/src/utils/tokenEstimate.ts +0 -34
  177. package/src/utils/toolResultStorage.ts +0 -117
  178. package/src/utils/worktreeSession.ts +0 -26
  179. package/src/utils/worktreeUtils.ts +0 -786
  180. package/src/workflow/budgetTracker.ts +0 -34
  181. package/src/workflow/concurrencyLimiter.ts +0 -47
  182. package/src/workflow/journal.ts +0 -95
  183. package/src/workflow/progressReporter.ts +0 -141
  184. package/src/workflow/runState.ts +0 -65
  185. package/src/workflow/scriptRuntime.ts +0 -274
  186. package/src/workflow/structuredOutput.ts +0 -123
  187. package/src/workflow/types.ts +0 -101
  188. package/src/workflow/workflowApis.ts +0 -410
  189. package/vendor/ripgrep/linux-aarch64/rg +0 -0
  190. package/vendor/ripgrep/linux-x86_64/rg +0 -0
  191. package/vendor/ripgrep/macos-aarch64/rg +0 -0
  192. package/vendor/ripgrep/macos-x86_64/rg +0 -0
  193. package/vendor/ripgrep/windows-aarch64/rg.exe +0 -0
  194. package/vendor/ripgrep/windows-x86_64/rg.exe +0 -0
@@ -1,34 +0,0 @@
1
- export class BudgetTracker {
2
- private totalSpent = 0;
3
-
4
- constructor(private _total: number | null = null) {}
5
-
6
- addUsage(tokens: number): void {
7
- this.totalSpent += tokens;
8
- }
9
-
10
- spent(): number {
11
- return this.totalSpent;
12
- }
13
-
14
- remaining(): number {
15
- if (this._total === null) return Infinity;
16
- return Math.max(0, this._total - this.totalSpent);
17
- }
18
-
19
- isExceeded(): boolean {
20
- return this._total !== null && this.totalSpent >= this._total;
21
- }
22
-
23
- get total(): number | null {
24
- return this._total;
25
- }
26
-
27
- toBudgetInfo(): import("./types.js").BudgetInfo {
28
- return {
29
- total: this._total,
30
- spent: () => this.totalSpent,
31
- remaining: () => this.remaining(),
32
- };
33
- }
34
- }
@@ -1,47 +0,0 @@
1
- export class ConcurrencyLimiter {
2
- private running = 0;
3
- private queue: Array<() => void> = [];
4
- private readonly maxConcurrency: number;
5
- private activeSet = new Set<Promise<unknown>>();
6
-
7
- constructor(maxConcurrency: number) {
8
- this.maxConcurrency = Math.max(1, maxConcurrency);
9
- }
10
-
11
- async acquire(): Promise<void> {
12
- if (this.running < this.maxConcurrency) {
13
- this.running++;
14
- return;
15
- }
16
- return new Promise<void>((resolve) => {
17
- this.queue.push(resolve);
18
- });
19
- }
20
-
21
- release(): void {
22
- this.running--;
23
- if (this.queue.length > 0) {
24
- this.running++;
25
- const next = this.queue.shift()!;
26
- next();
27
- }
28
- }
29
-
30
- track<T>(promise: Promise<T>): Promise<T> {
31
- this.activeSet.add(promise);
32
- promise.finally(() => this.activeSet.delete(promise));
33
- return promise;
34
- }
35
-
36
- async drain(): Promise<void> {
37
- await Promise.allSettled(this.activeSet);
38
- }
39
-
40
- get activeCount(): number {
41
- return this.running;
42
- }
43
-
44
- get pendingCount(): number {
45
- return this.queue.length;
46
- }
47
- }
@@ -1,95 +0,0 @@
1
- import * as fs from "fs";
2
- import * as path from "path";
3
- import type { JournalLine } from "./types.js";
4
-
5
- export class Journal {
6
- private entries: JournalLine[] = [];
7
- private stream: fs.WriteStream | null = null;
8
-
9
- constructor(public readonly filePath: string) {}
10
-
11
- async init(): Promise<void> {
12
- // Ensure directory exists
13
- await fs.promises.mkdir(path.dirname(this.filePath), { recursive: true });
14
- // Create append-only write stream
15
- this.stream = fs.createWriteStream(this.filePath, { flags: "a" });
16
- }
17
-
18
- append(entry: JournalLine): void {
19
- this.entries.push(entry);
20
- if (this.stream && !this.stream.destroyed && !this.stream.writableEnded) {
21
- this.stream.write(JSON.stringify(entry) + "\n");
22
- }
23
- }
24
-
25
- appendLog(message: string): void {
26
- this.append({ type: "log", message });
27
- }
28
-
29
- getCachedResult(agentIndex: number): unknown | undefined {
30
- const agentEntries = this.entries.filter(
31
- (e): e is import("./types.js").JournalEntry => !("type" in e),
32
- );
33
- // Check if this agent was marked as failed
34
- const failedEntry = this.entries.find(
35
- (e): e is import("./types.js").AgentFailedEntry =>
36
- "type" in e && e.type === "agent_failed" && e.agentIndex === agentIndex,
37
- );
38
- if (failedEntry) {
39
- return undefined; // Failed agents don't have cached results
40
- }
41
- if (agentIndex < agentEntries.length) {
42
- return agentEntries[agentIndex].result;
43
- }
44
- return undefined;
45
- }
46
-
47
- get length(): number {
48
- return this.entries.length;
49
- }
50
-
51
- /** Count of agent entries (excluding log entries) */
52
- get agentEntryCount(): number {
53
- return this.entries.filter(
54
- (e): e is import("./types.js").JournalEntry => !("type" in e),
55
- ).length;
56
- }
57
-
58
- /** Remove the agent_failed entry for a given agent index (for retry support) */
59
- removeFailedEntry(agentIndex: number): void {
60
- this.entries = this.entries.filter(
61
- (e) =>
62
- !(
63
- "type" in e &&
64
- e.type === "agent_failed" &&
65
- e.agentIndex === agentIndex
66
- ),
67
- );
68
- }
69
-
70
- async close(): Promise<void> {
71
- const s = this.stream;
72
- this.stream = null;
73
- if (s) {
74
- await new Promise<void>((resolve) => {
75
- s.end(() => resolve());
76
- });
77
- }
78
- }
79
-
80
- static async load(filePath: string): Promise<Journal> {
81
- const journal = new Journal(filePath);
82
- try {
83
- const content = await fs.promises.readFile(filePath, "utf-8");
84
- for (const line of content.split("\n")) {
85
- const trimmed = line.trim();
86
- if (trimmed) {
87
- journal.entries.push(JSON.parse(trimmed));
88
- }
89
- }
90
- } catch {
91
- // File doesn't exist yet — empty journal
92
- }
93
- return journal;
94
- }
95
- }
@@ -1,141 +0,0 @@
1
- import type {
2
- WorkflowPhaseState,
3
- WorkflowMeta,
4
- WorkflowProgressEvent,
5
- } from "./types.js";
6
-
7
- export class ProgressReporter {
8
- private phases: WorkflowPhaseState[] = [];
9
- private currentPhaseIndex = -1;
10
- private agentCounter = 0;
11
- private listeners: Array<(event: WorkflowProgressEvent) => void> = [];
12
- private runId: string;
13
-
14
- constructor(
15
- private meta: WorkflowMeta,
16
- runId?: string,
17
- ) {
18
- this.runId = runId || "";
19
- // Pre-initialize phases from meta so they appear even if the script
20
- // doesn't call phase() explicitly
21
- if (meta.phases?.length) {
22
- for (const p of meta.phases) {
23
- this.phases.push({
24
- title: p.title,
25
- agentCount: 0,
26
- tokens: 0,
27
- elapsed: 0,
28
- startTime: Date.now(),
29
- });
30
- }
31
- // Default to the first phase so agentStarted/agentCompleted
32
- // track into it even without an explicit phase() call
33
- this.currentPhaseIndex = 0;
34
- }
35
- }
36
-
37
- setPhase(title: string): void {
38
- // Emit phase_completed for the previous phase
39
- if (this.currentPhaseIndex >= 0) {
40
- this.emit({
41
- type: "phase_completed",
42
- phaseIndex: this.currentPhaseIndex,
43
- });
44
- }
45
-
46
- const existing = this.phases.findIndex((p) => p.title === title);
47
- if (existing >= 0) {
48
- this.currentPhaseIndex = existing;
49
- } else {
50
- this.phases.push({
51
- title,
52
- agentCount: 0,
53
- tokens: 0,
54
- elapsed: 0,
55
- startTime: Date.now(),
56
- });
57
- this.currentPhaseIndex = this.phases.length - 1;
58
- }
59
-
60
- this.emit({
61
- type: "phase_started",
62
- phaseIndex: this.currentPhaseIndex,
63
- });
64
- }
65
-
66
- agentStarted(): void {
67
- this.agentCounter++;
68
- if (this.currentPhaseIndex >= 0) {
69
- this.phases[this.currentPhaseIndex].agentCount++;
70
- }
71
- this.emit({
72
- type: "agent_started",
73
- phaseIndex: this.currentPhaseIndex,
74
- agentIndex: this.agentCounter - 1,
75
- });
76
- }
77
-
78
- agentCompleted(tokens: number): void {
79
- if (this.currentPhaseIndex >= 0) {
80
- this.phases[this.currentPhaseIndex].tokens += tokens;
81
- this.phases[this.currentPhaseIndex].elapsed =
82
- Date.now() - this.phases[this.currentPhaseIndex].startTime;
83
- }
84
- this.emit({
85
- type: "agent_completed",
86
- phaseIndex: this.currentPhaseIndex,
87
- agentIndex: this.agentCounter - 1,
88
- });
89
- }
90
-
91
- agentFailed(agentIndex: number): void {
92
- this.emit({
93
- type: "agent_failed",
94
- phaseIndex: this.currentPhaseIndex,
95
- agentIndex,
96
- });
97
- }
98
-
99
- formatSummary(): string {
100
- const phaseInfo =
101
- this.currentPhaseIndex >= 0
102
- ? `Phase ${this.currentPhaseIndex + 1}/${this.phases.length}: ${this.phases[this.currentPhaseIndex].title}`
103
- : "Initializing";
104
- const totalTokens = this.phases.reduce((sum, p) => sum + p.tokens, 0);
105
- const elapsed = this.phases.reduce((sum, p) => sum + p.elapsed, 0);
106
- return `${phaseInfo} | ${this.agentCounter} agents | ${(totalTokens / 1000).toFixed(1)}k tokens | ${Math.round(elapsed / 1000)}s`;
107
- }
108
-
109
- getPhaseStates(): WorkflowPhaseState[] {
110
- return [...this.phases];
111
- }
112
-
113
- get totalAgents(): number {
114
- return this.agentCounter;
115
- }
116
-
117
- get totalTokens(): number {
118
- return this.phases.reduce((sum, p) => sum + p.tokens, 0);
119
- }
120
-
121
- onEvent(listener: (event: WorkflowProgressEvent) => void): void {
122
- this.listeners.push(listener);
123
- }
124
-
125
- private emit(
126
- event: Omit<WorkflowProgressEvent, "runId" | "timestamp">,
127
- ): void {
128
- const fullEvent: WorkflowProgressEvent = {
129
- ...event,
130
- runId: this.runId,
131
- timestamp: Date.now(),
132
- };
133
- for (const listener of this.listeners) {
134
- try {
135
- listener(fullEvent);
136
- } catch {
137
- // Swallow listener errors
138
- }
139
- }
140
- }
141
- }
@@ -1,65 +0,0 @@
1
- import * as fs from "fs";
2
- import * as path from "path";
3
- import type { WorkflowRun } from "./types.js";
4
-
5
- /**
6
- * Persists WorkflowRun state to <runDir>/run-state.json.
7
- * Enables run recovery across process restarts.
8
- */
9
- export class RunStateStore {
10
- constructor(private baseDir: string) {}
11
-
12
- /** Persist a run's state to disk */
13
- async save(run: WorkflowRun): Promise<void> {
14
- const runDir = path.join(this.baseDir, run.runId);
15
- await fs.promises.mkdir(runDir, { recursive: true });
16
- const statePath = path.join(runDir, "run-state.json");
17
- // Omit non-serializable fields
18
- const { completionPromise, ...serializable } = run;
19
- void completionPromise;
20
- await fs.promises.writeFile(
21
- statePath,
22
- JSON.stringify(serializable, null, 2),
23
- "utf-8",
24
- );
25
- }
26
-
27
- /** Load a single run's state from disk */
28
- async load(runId: string): Promise<WorkflowRun | null> {
29
- const statePath = path.join(this.baseDir, runId, "run-state.json");
30
- try {
31
- const content = await fs.promises.readFile(statePath, "utf-8");
32
- return JSON.parse(content) as WorkflowRun;
33
- } catch {
34
- return null;
35
- }
36
- }
37
-
38
- /** List all persisted run IDs */
39
- async listRuns(): Promise<string[]> {
40
- try {
41
- const entries = await fs.promises.readdir(this.baseDir, {
42
- withFileTypes: true,
43
- });
44
- const runIds: string[] = [];
45
- for (const entry of entries) {
46
- if (entry.isDirectory() && entry.name.startsWith("wf_")) {
47
- const statePath = path.join(
48
- this.baseDir,
49
- entry.name,
50
- "run-state.json",
51
- );
52
- try {
53
- await fs.promises.access(statePath);
54
- runIds.push(entry.name);
55
- } catch {
56
- // Directory exists but no run-state.json — skip
57
- }
58
- }
59
- }
60
- return runIds;
61
- } catch {
62
- return [];
63
- }
64
- }
65
- }
@@ -1,274 +0,0 @@
1
- import type { WorkflowMeta } from "./types.js";
2
- import { logger } from "../utils/globalLogger.js";
3
-
4
- /** Patterns that are banned in workflow scripts for safety and determinism */
5
- const BANNED_PATTERNS = [
6
- {
7
- pattern: /\brequire\s*\(/,
8
- message: "require() is not available in workflow scripts",
9
- },
10
- {
11
- pattern: /\bprocess\./,
12
- message: "process.* is not available in workflow scripts",
13
- },
14
- {
15
- pattern: /\beval\s*\(/,
16
- message: "eval() is not available in workflow scripts",
17
- },
18
- {
19
- pattern: /\bimport\s+/,
20
- message: "import statements are not available in workflow scripts",
21
- },
22
- {
23
- pattern: /\bDate\.now\s*\(/,
24
- message: "Date.now() would break resume determinism",
25
- },
26
- {
27
- pattern: /\bMath\.random\s*\(/,
28
- message: "Math.random() would break resume determinism",
29
- },
30
- {
31
- pattern: /\bnew\s+Date\s*\(\s*\)/,
32
- message: "argless new Date() would break resume determinism",
33
- },
34
- {
35
- pattern: /\brequire\s*\(\s*['"]fs['"]\s*\)/,
36
- message: "filesystem access is not available in workflow scripts",
37
- },
38
- {
39
- pattern: /\bfs\.\w+/,
40
- message: "filesystem access is not available in workflow scripts",
41
- },
42
- {
43
- pattern: /\bchild_process\b/,
44
- message: "child_process is not available in workflow scripts",
45
- },
46
- {
47
- pattern: /\b__dirname\b/,
48
- message: "__dirname is not available in workflow scripts",
49
- },
50
- {
51
- pattern: /\b__filename\b/,
52
- message: "__filename is not available in workflow scripts",
53
- },
54
- {
55
- pattern: /\bglobal\./,
56
- message: "global.* is not available in workflow scripts",
57
- },
58
- {
59
- pattern: /\bglobalThis\b/,
60
- message: "globalThis is not available in workflow scripts",
61
- },
62
- ];
63
-
64
- export interface ParsedScript {
65
- meta: WorkflowMeta;
66
- body: string;
67
- }
68
-
69
- export interface ScriptValidationResult {
70
- valid: boolean;
71
- errors: string[];
72
- }
73
-
74
- /**
75
- * Validate a workflow script for banned patterns and required structure
76
- */
77
- export function validateScript(script: string): ScriptValidationResult {
78
- const errors: string[] = [];
79
-
80
- // Check for export const meta = {...}
81
- if (!/export\s+const\s+meta\s*=/.test(script)) {
82
- errors.push("Script must start with 'export const meta = {...}'");
83
- }
84
-
85
- // Check banned patterns
86
- for (const { pattern, message } of BANNED_PATTERNS) {
87
- if (pattern.test(script)) {
88
- errors.push(message);
89
- }
90
- }
91
-
92
- return { valid: errors.length === 0, errors };
93
- }
94
-
95
- /**
96
- * Parse a workflow script into meta and body.
97
- * Extracts the `export const meta = {...}` declaration and the rest of the script.
98
- */
99
- export function parseScript(script: string): ParsedScript {
100
- // Match: export const meta = { ... }
101
- // We need to find the matching closing brace for the meta object
102
- const metaStartMatch = script.match(/export\s+const\s+meta\s*=\s*/);
103
- if (!metaStartMatch) {
104
- throw new Error("Script must contain 'export const meta = {...}'");
105
- }
106
-
107
- const metaStartIndex = metaStartMatch.index! + metaStartMatch[0].length;
108
-
109
- // Find the matching closing brace for the meta object
110
- let depth = 0;
111
- let metaEndIndex = -1;
112
- let inString: string | null = null;
113
- let escaped = false;
114
-
115
- for (let i = metaStartIndex; i < script.length; i++) {
116
- const char = script[i];
117
-
118
- if (escaped) {
119
- escaped = false;
120
- continue;
121
- }
122
-
123
- if (char === "\\") {
124
- escaped = true;
125
- continue;
126
- }
127
-
128
- if (inString) {
129
- if (char === inString) {
130
- inString = null;
131
- }
132
- continue;
133
- }
134
-
135
- if (char === '"' || char === "'" || char === "`") {
136
- inString = char;
137
- continue;
138
- }
139
-
140
- if (char === "{") {
141
- depth++;
142
- } else if (char === "}") {
143
- depth--;
144
- if (depth === 0) {
145
- metaEndIndex = i + 1;
146
- break;
147
- }
148
- }
149
- }
150
-
151
- if (metaEndIndex === -1) {
152
- throw new Error("Could not find matching closing brace for meta object");
153
- }
154
-
155
- const metaString = script.slice(metaStartIndex, metaEndIndex);
156
-
157
- // Parse meta as JSON (allowing JS object syntax — unquoted keys, trailing commas)
158
- // We use a safe approach: wrap in parentheses and use Function to evaluate
159
- let meta: WorkflowMeta;
160
- try {
161
- // Remove trailing comma before closing brace/bracket (common in JS objects)
162
- const cleaned = metaString.replace(/,\s*([}\]])/g, "$1");
163
- // Wrap unquoted keys with double quotes
164
- const quoted = cleaned.replace(/(\w+)\s*:/g, '"$1":');
165
- meta = JSON.parse(quoted);
166
- } catch {
167
- // Fallback: use Function constructor for safe evaluation
168
- try {
169
- const fn = new Function(`"use strict"; return (${metaString});`);
170
- meta = fn();
171
- } catch (e) {
172
- throw new Error(
173
- `Failed to parse meta object: ${e instanceof Error ? e.message : String(e)}`,
174
- );
175
- }
176
- }
177
-
178
- // Validate required meta fields
179
- if (!meta.name || typeof meta.name !== "string") {
180
- throw new Error("meta.name is required and must be a string");
181
- }
182
- if (!meta.description || typeof meta.description !== "string") {
183
- throw new Error("meta.description is required and must be a string");
184
- }
185
-
186
- // Body is everything after the meta declaration (and any trailing semicolons/newlines)
187
- const bodyStart = metaEndIndex;
188
- const body = script.slice(bodyStart).replace(/^\s*;?\s*/, "");
189
-
190
- return { meta, body };
191
- }
192
-
193
- /**
194
- * Execute a workflow script with the given API closures.
195
- * The script body is wrapped in an async IIFE and executed via new Function().
196
- */
197
- export async function executeScript(
198
- script: string,
199
- apis: {
200
- agent: (prompt: string, opts?: Record<string, unknown>) => Promise<unknown>;
201
- parallel: (thunks: Array<() => Promise<unknown>>) => Promise<unknown[]>;
202
- pipeline: (
203
- items: unknown[],
204
- ...stages: Array<
205
- (prev: unknown, item: unknown, index: number) => Promise<unknown>
206
- >
207
- ) => Promise<unknown[]>;
208
- phase: (title: string) => void;
209
- log: (message: string) => void;
210
- args: unknown;
211
- budget: unknown;
212
- },
213
- abortSignal?: AbortSignal,
214
- ): Promise<{ meta: WorkflowMeta; result: unknown }> {
215
- // Validate first
216
- const validation = validateScript(script);
217
- if (!validation.valid) {
218
- throw new Error(
219
- `Script validation failed:\n${validation.errors.join("\n")}`,
220
- );
221
- }
222
-
223
- // Parse script
224
- const { meta, body } = parseScript(script);
225
-
226
- logger.info(
227
- `[Workflow] Executing script "${meta.name}": ${meta.description}`,
228
- );
229
-
230
- // Wrap the body in an async function and inject API closures
231
- const fn = new Function(
232
- "agent",
233
- "parallel",
234
- "pipeline",
235
- "phase",
236
- "log",
237
- "args",
238
- "budget",
239
- `"use strict"; return (async () => { ${body} })();`,
240
- );
241
-
242
- // Execute with abort support
243
- const scriptPromise = fn(
244
- apis.agent,
245
- apis.parallel,
246
- apis.pipeline,
247
- apis.phase,
248
- apis.log,
249
- apis.args,
250
- apis.budget,
251
- );
252
-
253
- let result: unknown;
254
- if (abortSignal) {
255
- result = await Promise.race([
256
- scriptPromise,
257
- new Promise<never>((_, reject) => {
258
- if (abortSignal.aborted) {
259
- reject(new Error("Workflow aborted"));
260
- return;
261
- }
262
- abortSignal.addEventListener(
263
- "abort",
264
- () => reject(new Error("Workflow aborted")),
265
- { once: true },
266
- );
267
- }),
268
- ]);
269
- } else {
270
- result = await scriptPromise;
271
- }
272
-
273
- return { meta, result };
274
- }