wave-agent-sdk 1.0.5 → 1.0.7

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