wave-agent-sdk 1.0.5 → 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 (193) 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 +13 -6
  7. package/dist/managers/slashCommandManager.js +28 -0
  8. package/dist/managers/subagentManager.d.ts +0 -2
  9. package/dist/telemetry/instrumentation.d.ts +1 -1
  10. package/dist/telemetry/instrumentation.js +57 -28
  11. package/dist/tools/bashTool.js +16 -6
  12. package/dist/tools/grepTool.js +11 -3
  13. package/dist/types/index.d.ts +3 -0
  14. package/dist/types/messaging.d.ts +2 -0
  15. package/dist/utils/containerSetup.js +0 -3
  16. package/dist/utils/messageOperations.d.ts +2 -1
  17. package/dist/utils/messageOperations.js +2 -1
  18. package/dist/utils/notificationXml.js +5 -0
  19. package/dist/utils/ripgrep.d.ts +4 -4
  20. package/dist/utils/ripgrep.js +4 -27
  21. package/package.json +20 -8
  22. package/bin/rg +0 -79
  23. package/dist/managers/forkedAgentManager.d.ts +0 -50
  24. package/dist/managers/forkedAgentManager.js +0 -130
  25. package/scripts/install_ripgrep.js +0 -111
  26. package/scripts/postinstall.js +0 -38
  27. package/src/agent.ts +0 -1271
  28. package/src/constants/subagents.ts +0 -4
  29. package/src/constants/toolLimits.ts +0 -15
  30. package/src/constants/tools.ts +0 -24
  31. package/src/core/plugin.ts +0 -237
  32. package/src/core/session.ts +0 -9
  33. package/src/index.ts +0 -38
  34. package/src/managers/MemoryRuleManager.ts +0 -198
  35. package/src/managers/aiManager.ts +0 -2429
  36. package/src/managers/backgroundTaskManager.ts +0 -480
  37. package/src/managers/bangManager.ts +0 -119
  38. package/src/managers/cronManager.ts +0 -401
  39. package/src/managers/foregroundTaskManager.ts +0 -30
  40. package/src/managers/forkedAgentManager.ts +0 -216
  41. package/src/managers/hookManager.ts +0 -1089
  42. package/src/managers/liveConfigManager.ts +0 -445
  43. package/src/managers/lspManager.ts +0 -470
  44. package/src/managers/mcpManager.ts +0 -1010
  45. package/src/managers/messageManager.ts +0 -1184
  46. package/src/managers/messageQueue.ts +0 -157
  47. package/src/managers/permissionManager.ts +0 -1140
  48. package/src/managers/planManager.ts +0 -112
  49. package/src/managers/pluginManager.ts +0 -330
  50. package/src/managers/pluginScopeManager.ts +0 -124
  51. package/src/managers/reversionManager.ts +0 -178
  52. package/src/managers/skillManager.ts +0 -578
  53. package/src/managers/slashCommandManager.ts +0 -564
  54. package/src/managers/subagentManager.ts +0 -915
  55. package/src/managers/toolManager.ts +0 -489
  56. package/src/managers/workflowManager.ts +0 -493
  57. package/src/prompts/autoMemory.ts +0 -33
  58. package/src/prompts/autoMemoryExtraction.ts +0 -146
  59. package/src/prompts/index.ts +0 -506
  60. package/src/prompts/planModeReminders.ts +0 -126
  61. package/src/services/GitService.ts +0 -131
  62. package/src/services/MarketplaceService.ts +0 -1052
  63. package/src/services/MemoryRuleService.ts +0 -71
  64. package/src/services/aiService.ts +0 -919
  65. package/src/services/authService.ts +0 -565
  66. package/src/services/autoMemoryService.ts +0 -280
  67. package/src/services/configurationService.ts +0 -1520
  68. package/src/services/fileWatcher.ts +0 -319
  69. package/src/services/hook.ts +0 -348
  70. package/src/services/initializationService.ts +0 -357
  71. package/src/services/interactionService.ts +0 -229
  72. package/src/services/jsonlHandler.ts +0 -295
  73. package/src/services/memory.ts +0 -234
  74. package/src/services/pluginLoader.ts +0 -252
  75. package/src/services/remoteSettingsService.ts +0 -366
  76. package/src/services/reversionService.ts +0 -122
  77. package/src/services/session.ts +0 -917
  78. package/src/services/taskManager.ts +0 -321
  79. package/src/telemetry/events.ts +0 -65
  80. package/src/telemetry/instrumentation.ts +0 -499
  81. package/src/telemetry/sessionTracing.ts +0 -348
  82. package/src/tools/agentTool.ts +0 -322
  83. package/src/tools/askUserQuestion.ts +0 -160
  84. package/src/tools/bashTool.ts +0 -621
  85. package/src/tools/buildTool.ts +0 -61
  86. package/src/tools/cronCreateTool.ts +0 -161
  87. package/src/tools/cronDeleteTool.ts +0 -51
  88. package/src/tools/cronListTool.ts +0 -47
  89. package/src/tools/editTool.ts +0 -313
  90. package/src/tools/enterPlanMode.ts +0 -124
  91. package/src/tools/enterWorktreeTool.ts +0 -190
  92. package/src/tools/exitPlanMode.ts +0 -139
  93. package/src/tools/exitWorktreeTool.ts +0 -236
  94. package/src/tools/globTool.ts +0 -167
  95. package/src/tools/grepTool.ts +0 -399
  96. package/src/tools/lspTool.ts +0 -883
  97. package/src/tools/readTool.ts +0 -426
  98. package/src/tools/skillTool.ts +0 -248
  99. package/src/tools/taskManagementTools.ts +0 -574
  100. package/src/tools/taskStopTool.ts +0 -78
  101. package/src/tools/types.ts +0 -137
  102. package/src/tools/webFetchTool.ts +0 -373
  103. package/src/tools/workflowTool.ts +0 -205
  104. package/src/tools/writeTool.ts +0 -267
  105. package/src/types/agent.ts +0 -128
  106. package/src/types/auth.ts +0 -19
  107. package/src/types/commands.ts +0 -30
  108. package/src/types/config.ts +0 -43
  109. package/src/types/configuration.ts +0 -168
  110. package/src/types/core.ts +0 -105
  111. package/src/types/cron.ts +0 -13
  112. package/src/types/environment.ts +0 -106
  113. package/src/types/fileSearch.ts +0 -4
  114. package/src/types/history.ts +0 -7
  115. package/src/types/hooks.ts +0 -256
  116. package/src/types/index.ts +0 -41
  117. package/src/types/lsp.ts +0 -98
  118. package/src/types/marketplace.ts +0 -71
  119. package/src/types/mcp.ts +0 -43
  120. package/src/types/memoryRule.ts +0 -31
  121. package/src/types/messaging.ts +0 -114
  122. package/src/types/permissions.ts +0 -80
  123. package/src/types/plugins.ts +0 -39
  124. package/src/types/processes.ts +0 -99
  125. package/src/types/reversion.ts +0 -29
  126. package/src/types/session.ts +0 -12
  127. package/src/types/skills.ts +0 -108
  128. package/src/types/tasks.ts +0 -13
  129. package/src/types/telemetry.ts +0 -98
  130. package/src/types/tools.ts +0 -44
  131. package/src/types/workflow.ts +0 -6
  132. package/src/utils/abortUtils.ts +0 -118
  133. package/src/utils/atomicWrite.ts +0 -61
  134. package/src/utils/bashParser.ts +0 -899
  135. package/src/utils/cacheControlUtils.ts +0 -426
  136. package/src/utils/commandArgumentParser.ts +0 -161
  137. package/src/utils/commandPathResolver.ts +0 -88
  138. package/src/utils/configPaths.ts +0 -220
  139. package/src/utils/configValidator.ts +0 -100
  140. package/src/utils/constants.ts +0 -39
  141. package/src/utils/container.ts +0 -92
  142. package/src/utils/containerSetup.ts +0 -371
  143. package/src/utils/convertMessagesForAPI.ts +0 -326
  144. package/src/utils/cronTasks.ts +0 -128
  145. package/src/utils/cronTasksLock.ts +0 -182
  146. package/src/utils/cronToHuman.ts +0 -99
  147. package/src/utils/customCommands.ts +0 -95
  148. package/src/utils/editUtils.ts +0 -20
  149. package/src/utils/fileFormat.ts +0 -40
  150. package/src/utils/fileSearch.ts +0 -151
  151. package/src/utils/fileUtils.ts +0 -223
  152. package/src/utils/gitUtils.ts +0 -315
  153. package/src/utils/globalLogger.ts +0 -128
  154. package/src/utils/groupMessagesByApiRound.ts +0 -121
  155. package/src/utils/hookMatcher.ts +0 -176
  156. package/src/utils/markdownParser.ts +0 -317
  157. package/src/utils/mcpUtils.ts +0 -138
  158. package/src/utils/messageOperations.ts +0 -659
  159. package/src/utils/modelCapabilities.ts +0 -30
  160. package/src/utils/nameGenerator.ts +0 -99
  161. package/src/utils/notificationXml.ts +0 -57
  162. package/src/utils/openaiClient.ts +0 -251
  163. package/src/utils/parseCronExpression.ts +0 -78
  164. package/src/utils/path.ts +0 -72
  165. package/src/utils/pathEncoder.ts +0 -400
  166. package/src/utils/pathSafety.ts +0 -40
  167. package/src/utils/promptHistory.ts +0 -168
  168. package/src/utils/ripgrep.ts +0 -35
  169. package/src/utils/shellResolver.ts +0 -190
  170. package/src/utils/skillParser.ts +0 -246
  171. package/src/utils/stringUtils.ts +0 -156
  172. package/src/utils/subagentParser.ts +0 -279
  173. package/src/utils/taskReminder.ts +0 -96
  174. package/src/utils/tokenCalculation.ts +0 -43
  175. package/src/utils/tokenEstimate.ts +0 -34
  176. package/src/utils/toolResultStorage.ts +0 -117
  177. package/src/utils/worktreeSession.ts +0 -26
  178. package/src/utils/worktreeUtils.ts +0 -786
  179. package/src/workflow/budgetTracker.ts +0 -34
  180. package/src/workflow/concurrencyLimiter.ts +0 -47
  181. package/src/workflow/journal.ts +0 -95
  182. package/src/workflow/progressReporter.ts +0 -141
  183. package/src/workflow/runState.ts +0 -65
  184. package/src/workflow/scriptRuntime.ts +0 -274
  185. package/src/workflow/structuredOutput.ts +0 -123
  186. package/src/workflow/types.ts +0 -101
  187. package/src/workflow/workflowApis.ts +0 -410
  188. package/vendor/ripgrep/linux-aarch64/rg +0 -0
  189. package/vendor/ripgrep/linux-x86_64/rg +0 -0
  190. package/vendor/ripgrep/macos-aarch64/rg +0 -0
  191. package/vendor/ripgrep/macos-x86_64/rg +0 -0
  192. package/vendor/ripgrep/windows-aarch64/rg.exe +0 -0
  193. 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
- }