wave-agent-sdk 0.17.1 → 0.17.3

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 (173) hide show
  1. package/builtin/skills/deep-research/SKILL.md +90 -0
  2. package/builtin/skills/settings/ENV.md +6 -3
  3. package/dist/agent.d.ts +28 -1
  4. package/dist/agent.d.ts.map +1 -1
  5. package/dist/agent.js +128 -34
  6. package/dist/constants/goalPrompts.d.ts +2 -0
  7. package/dist/constants/goalPrompts.d.ts.map +1 -0
  8. package/dist/constants/goalPrompts.js +10 -0
  9. package/dist/constants/tools.d.ts +1 -0
  10. package/dist/constants/tools.d.ts.map +1 -1
  11. package/dist/constants/tools.js +1 -0
  12. package/dist/managers/aiManager.d.ts +7 -0
  13. package/dist/managers/aiManager.d.ts.map +1 -1
  14. package/dist/managers/aiManager.js +77 -41
  15. package/dist/managers/backgroundTaskManager.d.ts.map +1 -1
  16. package/dist/managers/backgroundTaskManager.js +10 -2
  17. package/dist/managers/goalManager.d.ts +43 -0
  18. package/dist/managers/goalManager.d.ts.map +1 -0
  19. package/dist/managers/goalManager.js +177 -0
  20. package/dist/managers/messageManager.d.ts +2 -2
  21. package/dist/managers/messageManager.d.ts.map +1 -1
  22. package/dist/managers/messageQueue.d.ts +10 -0
  23. package/dist/managers/messageQueue.d.ts.map +1 -1
  24. package/dist/managers/messageQueue.js +53 -1
  25. package/dist/managers/pluginManager.d.ts.map +1 -1
  26. package/dist/managers/pluginManager.js +7 -1
  27. package/dist/managers/skillManager.d.ts +2 -0
  28. package/dist/managers/skillManager.d.ts.map +1 -1
  29. package/dist/managers/skillManager.js +19 -9
  30. package/dist/managers/slashCommandManager.d.ts +6 -0
  31. package/dist/managers/slashCommandManager.d.ts.map +1 -1
  32. package/dist/managers/slashCommandManager.js +105 -0
  33. package/dist/managers/toolManager.d.ts.map +1 -1
  34. package/dist/managers/toolManager.js +5 -0
  35. package/dist/managers/workflowManager.d.ts +65 -0
  36. package/dist/managers/workflowManager.d.ts.map +1 -0
  37. package/dist/managers/workflowManager.js +380 -0
  38. package/dist/prompts/index.d.ts +2 -1
  39. package/dist/prompts/index.d.ts.map +1 -1
  40. package/dist/prompts/index.js +3 -3
  41. package/dist/services/MarketplaceService.d.ts +2 -2
  42. package/dist/services/MarketplaceService.d.ts.map +1 -1
  43. package/dist/services/MarketplaceService.js +11 -32
  44. package/dist/services/aiService.d.ts +23 -0
  45. package/dist/services/aiService.d.ts.map +1 -1
  46. package/dist/services/aiService.js +102 -9
  47. package/dist/services/configurationService.d.ts +1 -1
  48. package/dist/services/configurationService.d.ts.map +1 -1
  49. package/dist/services/configurationService.js +3 -16
  50. package/dist/services/hook.d.ts.map +1 -1
  51. package/dist/services/hook.js +4 -0
  52. package/dist/services/session.d.ts +9 -1
  53. package/dist/services/session.d.ts.map +1 -1
  54. package/dist/services/session.js +28 -1
  55. package/dist/tools/bashTool.d.ts.map +1 -1
  56. package/dist/tools/bashTool.js +49 -7
  57. package/dist/tools/readTool.d.ts.map +1 -1
  58. package/dist/tools/readTool.js +1 -1
  59. package/dist/tools/taskManagementTools.d.ts.map +1 -1
  60. package/dist/tools/taskManagementTools.js +103 -157
  61. package/dist/tools/types.d.ts +2 -0
  62. package/dist/tools/types.d.ts.map +1 -1
  63. package/dist/tools/webFetchTool.d.ts.map +1 -1
  64. package/dist/tools/webFetchTool.js +0 -9
  65. package/dist/tools/workflowTool.d.ts +11 -0
  66. package/dist/tools/workflowTool.d.ts.map +1 -0
  67. package/dist/tools/workflowTool.js +190 -0
  68. package/dist/types/agent.d.ts +2 -0
  69. package/dist/types/agent.d.ts.map +1 -1
  70. package/dist/types/commands.d.ts +4 -0
  71. package/dist/types/commands.d.ts.map +1 -1
  72. package/dist/types/config.d.ts +2 -2
  73. package/dist/types/config.d.ts.map +1 -1
  74. package/dist/types/core.d.ts +1 -1
  75. package/dist/types/core.d.ts.map +1 -1
  76. package/dist/types/hooks.d.ts +2 -0
  77. package/dist/types/hooks.d.ts.map +1 -1
  78. package/dist/types/index.d.ts +1 -0
  79. package/dist/types/index.d.ts.map +1 -1
  80. package/dist/types/index.js +1 -0
  81. package/dist/types/messaging.d.ts +2 -2
  82. package/dist/types/messaging.d.ts.map +1 -1
  83. package/dist/types/processes.d.ts +6 -2
  84. package/dist/types/processes.d.ts.map +1 -1
  85. package/dist/types/workflow.d.ts +2 -0
  86. package/dist/types/workflow.d.ts.map +1 -0
  87. package/dist/types/workflow.js +1 -0
  88. package/dist/utils/cacheControlUtils.d.ts +13 -8
  89. package/dist/utils/cacheControlUtils.d.ts.map +1 -1
  90. package/dist/utils/cacheControlUtils.js +73 -102
  91. package/dist/utils/containerSetup.d.ts.map +1 -1
  92. package/dist/utils/containerSetup.js +7 -0
  93. package/dist/utils/markdownParser.d.ts.map +1 -1
  94. package/dist/utils/markdownParser.js +21 -6
  95. package/dist/utils/messageOperations.d.ts +2 -2
  96. package/dist/utils/messageOperations.d.ts.map +1 -1
  97. package/dist/utils/notificationXml.d.ts.map +1 -1
  98. package/dist/workflow/budgetTracker.d.ts +12 -0
  99. package/dist/workflow/budgetTracker.d.ts.map +1 -0
  100. package/dist/workflow/budgetTracker.js +30 -0
  101. package/dist/workflow/concurrencyLimiter.d.ts +14 -0
  102. package/dist/workflow/concurrencyLimiter.d.ts.map +1 -0
  103. package/dist/workflow/concurrencyLimiter.js +39 -0
  104. package/dist/workflow/journal.d.ts +19 -0
  105. package/dist/workflow/journal.d.ts.map +1 -0
  106. package/dist/workflow/journal.js +74 -0
  107. package/dist/workflow/progressReporter.d.ts +21 -0
  108. package/dist/workflow/progressReporter.d.ts.map +1 -0
  109. package/dist/workflow/progressReporter.js +118 -0
  110. package/dist/workflow/runState.d.ts +16 -0
  111. package/dist/workflow/runState.d.ts.map +1 -0
  112. package/dist/workflow/runState.js +57 -0
  113. package/dist/workflow/scriptRuntime.d.ts +35 -0
  114. package/dist/workflow/scriptRuntime.d.ts.map +1 -0
  115. package/dist/workflow/scriptRuntime.js +196 -0
  116. package/dist/workflow/structuredOutput.d.ts +27 -0
  117. package/dist/workflow/structuredOutput.d.ts.map +1 -0
  118. package/dist/workflow/structuredOutput.js +106 -0
  119. package/dist/workflow/types.d.ts +81 -0
  120. package/dist/workflow/types.d.ts.map +1 -0
  121. package/dist/workflow/types.js +1 -0
  122. package/dist/workflow/workflowApis.d.ts +46 -0
  123. package/dist/workflow/workflowApis.d.ts.map +1 -0
  124. package/dist/workflow/workflowApis.js +280 -0
  125. package/package.json +1 -1
  126. package/src/agent.ts +144 -34
  127. package/src/constants/goalPrompts.ts +10 -0
  128. package/src/constants/tools.ts +1 -0
  129. package/src/managers/aiManager.ts +91 -47
  130. package/src/managers/backgroundTaskManager.ts +16 -4
  131. package/src/managers/goalManager.ts +232 -0
  132. package/src/managers/messageManager.ts +2 -2
  133. package/src/managers/messageQueue.ts +59 -1
  134. package/src/managers/pluginManager.ts +8 -1
  135. package/src/managers/skillManager.ts +20 -9
  136. package/src/managers/slashCommandManager.ts +119 -0
  137. package/src/managers/toolManager.ts +7 -0
  138. package/src/managers/workflowManager.ts +491 -0
  139. package/src/prompts/index.ts +4 -2
  140. package/src/services/MarketplaceService.ts +14 -38
  141. package/src/services/aiService.ts +166 -12
  142. package/src/services/configurationService.ts +2 -22
  143. package/src/services/hook.ts +5 -0
  144. package/src/services/session.ts +42 -2
  145. package/src/tools/bashTool.ts +64 -9
  146. package/src/tools/readTool.ts +1 -2
  147. package/src/tools/taskManagementTools.ts +146 -195
  148. package/src/tools/types.ts +2 -0
  149. package/src/tools/webFetchTool.ts +0 -12
  150. package/src/tools/workflowTool.ts +205 -0
  151. package/src/types/agent.ts +6 -0
  152. package/src/types/commands.ts +4 -0
  153. package/src/types/config.ts +2 -2
  154. package/src/types/core.ts +3 -3
  155. package/src/types/hooks.ts +2 -0
  156. package/src/types/index.ts +1 -0
  157. package/src/types/messaging.ts +2 -2
  158. package/src/types/processes.ts +10 -2
  159. package/src/types/workflow.ts +5 -0
  160. package/src/utils/cacheControlUtils.ts +106 -131
  161. package/src/utils/containerSetup.ts +9 -0
  162. package/src/utils/markdownParser.ts +26 -8
  163. package/src/utils/messageOperations.ts +2 -2
  164. package/src/utils/notificationXml.ts +6 -1
  165. package/src/workflow/budgetTracker.ts +34 -0
  166. package/src/workflow/concurrencyLimiter.ts +47 -0
  167. package/src/workflow/journal.ts +95 -0
  168. package/src/workflow/progressReporter.ts +141 -0
  169. package/src/workflow/runState.ts +65 -0
  170. package/src/workflow/scriptRuntime.ts +274 -0
  171. package/src/workflow/structuredOutput.ts +123 -0
  172. package/src/workflow/types.ts +95 -0
  173. package/src/workflow/workflowApis.ts +412 -0
@@ -0,0 +1,205 @@
1
+ import type { ToolPlugin, ToolResult, ToolContext } from "./types.js";
2
+ import { WORKFLOW_TOOL_NAME } from "../constants/tools.js";
3
+ import { logger } from "../utils/globalLogger.js";
4
+
5
+ /**
6
+ * Workflow tool plugin for executing deterministic multi-subagent orchestration scripts.
7
+ *
8
+ * The AI model calls this tool with a JavaScript script that orchestrates
9
+ * multiple subagents via agent(), parallel(), pipeline(), phase(), log() APIs.
10
+ * Workflows run in the background — the tool returns immediately with a run ID,
11
+ * and a <task-notification> arrives when the workflow completes.
12
+ */
13
+ export const workflowTool: ToolPlugin = {
14
+ name: WORKFLOW_TOOL_NAME,
15
+ config: {
16
+ type: "function" as const,
17
+ function: {
18
+ name: WORKFLOW_TOOL_NAME,
19
+ description:
20
+ "Execute a workflow script that orchestrates multiple subagents deterministically. Workflows run in the background — this tool returns immediately with a run ID, and a <task-notification> arrives when the workflow completes. Use /workflows to watch live progress.",
21
+ parameters: {
22
+ type: "object",
23
+ properties: {
24
+ script: {
25
+ type: "string",
26
+ description:
27
+ "Inline workflow script (JavaScript). Must start with 'export const meta = {name, description, phases}'. Pass the script inline — do not Write it to a file first. Every Workflow invocation automatically persists its script.",
28
+ },
29
+ scriptPath: {
30
+ type: "string",
31
+ description:
32
+ "Path to a saved workflow script file. Use this to re-run or iterate on a previously saved script. One of 'script' or 'scriptPath' is required.",
33
+ },
34
+ args: {
35
+ description:
36
+ "Arguments passed to the workflow script as the `args` global. Pass arrays/objects as actual JSON values, NOT as a JSON-encoded string.",
37
+ },
38
+ resumeFromRunId: {
39
+ type: "string",
40
+ description:
41
+ "Resume from a previous run's journal. Cached agent results are replayed instantly; the first edited/new call and everything after it runs live.",
42
+ },
43
+ },
44
+ },
45
+ },
46
+ },
47
+
48
+ prompt: () =>
49
+ `Execute a workflow script that orchestrates multiple subagents deterministically. Workflows run in the background — this tool returns immediately with a task ID, and a <task-notification> arrives when the workflow completes. Use /workflows to watch live progress.
50
+
51
+ A workflow structures work across many agents — to be comprehensive (decompose and cover in parallel), to be confident (independent perspectives and adversarial checks before committing), or to take on scale one context can't hold (migrations, audits, broad sweeps). The script is where you encode that structure: what fans out, what verifies, what synthesizes.
52
+
53
+ ONLY call this tool when the user has explicitly opted into multi-agent orchestration. Workflows can spawn dozens of agents and consume a large amount of tokens; the user must request that scale, not have it inferred. Explicit opt-in means one of:
54
+ - The user directly asked you to run a workflow or use multi-agent orchestration in their own words ("use a workflow", "run a workflow", "fan out agents", "orchestrate this with subagents"). The ask must be in the user's words — a task that would merely benefit from a workflow does not count.
55
+ - The user invoked a skill or slash command whose instructions tell you to call Workflow.
56
+ - The user asked you to run a specific named or saved workflow.
57
+
58
+ For any other task — even one that would clearly benefit from parallelism — do NOT call this tool. Use the Agent tool for individual subagents, or briefly describe what a multi-agent workflow could do and how much it would roughly cost, and ask the user whether to run it.
59
+
60
+ When you do call it, the right move is often **hybrid**: scout inline first (list the files, find the channels, scope the diff) to discover the work-list, then call Workflow to pipeline over it.
61
+
62
+ Common single-phase workflows you can chain across turns:
63
+ - **Understand** — parallel readers over relevant subsystems → structured map
64
+ - **Design** — judge panel of N independent approaches → scored synthesis
65
+ - **Review** — dimensions → find → adversarially verify
66
+ - **Research** — multi-modal sweep → deep-read → synthesize
67
+ - **Migrate** — discover sites → transform each (worktree isolation) → verify
68
+
69
+ For larger work, run several in sequence — read each result before deciding the next phase.
70
+
71
+ Every script must begin with \`export const meta = {...}\`:
72
+ export const meta = {
73
+ name: 'find-flaky-tests',
74
+ description: 'Find flaky tests and propose fixes',
75
+ phases: [
76
+ { title: 'Scan', detail: 'grep test logs for retries' },
77
+ { title: 'Fix', detail: 'one agent per flaky test' },
78
+ ],
79
+ }
80
+ // script body starts here — use agent()/parallel()/pipeline()/phase()/log()
81
+
82
+ Script body hooks:
83
+ - **agent(prompt, opts?)**: Promise<any> — spawn a subagent. Without schema, returns its final text as a string. With schema (a JSON Schema), the subagent is forced to call a StructuredOutput tool and agent() returns the validated object — no parsing needed. Returns null if the user skips the agent mid-run or the subagent dies on a terminal API error (filter with .filter(Boolean)). opts.label overrides the display label. opts.phase assigns this agent to a progress group. opts.model overrides the model for this agent call. opts.agentType uses a custom subagent type instead of the default.
84
+ - **pipeline(items, stage1, stage2, ...)**: Promise<any[]> — run each item through all stages independently, NO barrier between stages. Item A can be in stage 3 while item B is still in stage 1. This is the DEFAULT for multi-stage work. Every stage callback receives (prevResult, originalItem, index). In the first stage prevResult is undefined; in later stages it is the return value of the previous stage. A stage that throws drops that item to null. Example single-stage: \`pipeline(files, (prev, file) => agent('Read ' + file))\`. Example two-stage: \`pipeline(files, (prev, file) => agent('Read ' + file), (prev, file) => agent('Summarize: ' + prev))\`.
85
+ - **parallel(thunks: Array<() => Promise<any>>)**: Promise<any[]> — run tasks concurrently. This is a BARRIER: awaits all thunks before returning. A thunk that throws resolves to null in the result array. Use ONLY when you genuinely need all results together.
86
+ - **log(message: string)**: void — emit a progress message
87
+ - **phase(title: string)**: void — start a new phase; subsequent agent() calls are grouped under this title
88
+ - **args**: any — the value passed as Workflow's args input, verbatim
89
+ - **budget**: {total: number|null, spent(): number, remaining(): number} — the turn's token target
90
+
91
+ Scripts are plain JavaScript, NOT TypeScript — type annotations fail to parse. The script body runs in an async context — use await directly. Standard JS built-ins (JSON, Math, Array, etc.) are available — EXCEPT Date.now()/Math.random()/argless new Date(), which throw (they would break resume). No filesystem or Node.js API access.
92
+
93
+ DEFAULT TO pipeline(). Only reach for a barrier (parallel between stages) when you genuinely need ALL prior-stage results together.
94
+
95
+ Concurrent agent() calls are capped at min(16, cpu cores - 2) per workflow. Total agent count is capped at 1000 per run. A single parallel()/pipeline() call accepts at most 4096 items.
96
+
97
+ Quality patterns:
98
+ - **Adversarial verify**: spawn N independent skeptics per finding, each prompted to REFUTE. Kill if >=majority refute.
99
+ - **Judge panel**: generate N independent approaches, score with parallel judges, synthesize from the winner.
100
+ - **Loop-until-dry**: keep spawning finders until K consecutive rounds return nothing new.
101
+ - **Multi-modal sweep**: parallel agents each searching a different way (by-container, by-content, by-entity).
102
+ - **Completeness critic**: a final agent that asks "what's missing?" — findings become next round of work.
103
+ - **No silent caps**: if a workflow bounds coverage, log() what was dropped.
104
+
105
+ ## Resume
106
+
107
+ The tool result includes a runId. To resume after a pause, kill, or script edit, relaunch with Workflow({scriptPath, resumeFromRunId}) — the longest unchanged prefix of agent() calls returns cached results instantly; the first edited/new call and everything after it runs live.
108
+
109
+ Use this tool for multi-step orchestration where control flow should be deterministic (loops, conditionals, fan-out) rather than model-driven.`,
110
+
111
+ execute: async (
112
+ args: Record<string, unknown>,
113
+ context: ToolContext,
114
+ ): Promise<ToolResult> => {
115
+ const workflowManager = context.workflowManager;
116
+ if (!workflowManager) {
117
+ return {
118
+ success: false,
119
+ content: "",
120
+ error: "Workflow manager not available in tool context",
121
+ shortResult: "Workflow execution failed",
122
+ };
123
+ }
124
+
125
+ const script = args.script as string | undefined;
126
+ const scriptPath = args.scriptPath as string | undefined;
127
+ const workflowArgs = args.args;
128
+ const resumeFromRunId = args.resumeFromRunId as string | undefined;
129
+
130
+ // Resolve script text
131
+ let scriptText: string;
132
+ if (script) {
133
+ scriptText = script;
134
+ } else if (scriptPath) {
135
+ try {
136
+ const fs = await import("fs/promises");
137
+ scriptText = await fs.readFile(scriptPath, "utf-8");
138
+ } catch (error) {
139
+ return {
140
+ success: false,
141
+ content: "",
142
+ error: `Failed to read script file: ${error instanceof Error ? error.message : String(error)}`,
143
+ shortResult: "Workflow script read failed",
144
+ };
145
+ }
146
+ } else {
147
+ return {
148
+ success: false,
149
+ content: "",
150
+ error: "Either 'script' or 'scriptPath' parameter is required",
151
+ shortResult: "Workflow execution failed",
152
+ };
153
+ }
154
+
155
+ try {
156
+ // Create run
157
+ const run = await workflowManager.createRun(scriptText, workflowArgs, {
158
+ resumeFromRunId,
159
+ });
160
+
161
+ // Start execution in background
162
+ await workflowManager.startRun(run.runId);
163
+
164
+ return {
165
+ success: true,
166
+ content: [
167
+ `Workflow started with run ID: ${run.runId}`,
168
+ `Name: ${run.meta.name}`,
169
+ `Description: ${run.meta.description}`,
170
+ run.meta.phases?.length
171
+ ? `Phases: ${run.meta.phases.map((p) => p.title).join(" → ")}`
172
+ : "",
173
+ `The workflow is running in the background. You will be notified automatically when it completes.`,
174
+ `Use /workflows to watch live progress.`,
175
+ `Script saved to: ${run.scriptPath}`,
176
+ ]
177
+ .filter(Boolean)
178
+ .join("\n"),
179
+ shortResult: `Workflow started: ${run.meta.name} (${run.runId})`,
180
+ };
181
+ } catch (error) {
182
+ const msg = error instanceof Error ? error.message : String(error);
183
+ logger.error(`[Workflow Tool] execution failed: ${msg}`);
184
+ return {
185
+ success: false,
186
+ content: `Workflow failed: ${msg}. Fix the error and try again.`,
187
+ error: `Workflow execution failed: ${msg}`,
188
+ shortResult: "Workflow execution failed",
189
+ };
190
+ }
191
+ },
192
+
193
+ formatCompactParams: (params: Record<string, unknown>) => {
194
+ if (params.scriptPath) {
195
+ return `scriptPath: ${params.scriptPath}`;
196
+ }
197
+ const script = params.script as string;
198
+ if (script) {
199
+ // Extract meta.name from the script
200
+ const nameMatch = script.match(/name:\s*['"]([^'"]+)['"]/);
201
+ return nameMatch ? nameMatch[1] : script.slice(0, 50) + "...";
202
+ }
203
+ return "workflow";
204
+ },
205
+ };
@@ -121,4 +121,10 @@ export interface AgentCallbacks
121
121
  onCommandRunningChange?: (running: boolean) => void;
122
122
  onWorkdirChange?: (newCwd: string) => void;
123
123
  onQueuedMessagesChange?: (messages: QueuedMessage[]) => void;
124
+ onGoalStateChange?: (
125
+ active: boolean,
126
+ condition?: string,
127
+ elapsed?: string,
128
+ ) => void;
129
+ onGoalEvaluating?: (evaluating: boolean) => void;
124
130
  }
@@ -8,6 +8,10 @@ export interface SlashCommand {
8
8
  name: string;
9
9
  description: string;
10
10
  handler: (args?: string, signal?: AbortSignal) => Promise<void> | void;
11
+ /** Whether this command should bypass the message queue when AI is busy.
12
+ * - `true`: always immediate
13
+ * - Function: receives args, returns true for immediate variants */
14
+ immediate?: boolean | ((args?: string) => boolean);
11
15
  }
12
16
 
13
17
  export interface CustomSlashCommandConfig {
@@ -15,8 +15,8 @@ export interface GatewayConfig {
15
15
  }
16
16
 
17
17
  export interface ModelConfig {
18
- model: string;
19
- fastModel: string;
18
+ model?: string;
19
+ fastModel?: string;
20
20
  maxTokens?: number;
21
21
  permissionMode?: PermissionMode;
22
22
  [key: string]: unknown;
package/src/types/core.ts CHANGED
@@ -25,10 +25,10 @@ export interface Usage {
25
25
  completion_tokens: number; // Tokens generated in completions
26
26
  total_tokens: number; // Sum of prompt + completion tokens
27
27
  model?: string; // Model used for the operation (e.g., "gpt-4", "gpt-3.5-turbo")
28
- operation_type?: "agent" | "compact"; // Type of operation that generated usage
28
+ operation_type?: "agent" | "compact" | "goal_evaluation"; // Type of operation that generated usage
29
29
 
30
- // Cache-related tokens (Claude models only)
31
- cache_read_input_tokens?: number; // Tokens read from cache
30
+ // Cache-related tokens (Claude top-level + OpenAI prompt_tokens_details)
31
+ cache_read_input_tokens?: number; // Tokens read from cache (Claude) or cached_tokens (OpenAI prompt_tokens_details)
32
32
  cache_creation_input_tokens?: number; // Tokens used to create cache entries
33
33
  cache_creation?: {
34
34
  ephemeral_5m_input_tokens: number; // Tokens cached for 5 minutes
@@ -51,6 +51,7 @@ export interface HookExecutionContext {
51
51
  timestamp: Date;
52
52
  worktreeName?: string; // Present for WorktreeCreate
53
53
  worktreePath?: string; // Present for WorktreeRemove
54
+ planFilePath?: string; // Present when in plan mode
54
55
  }
55
56
 
56
57
  // Result of hook execution
@@ -183,6 +184,7 @@ export interface HookJsonInput {
183
184
  tool_name?: string; // Present for PreToolUse, PostToolUse, PermissionRequest
184
185
  tool_input?: unknown; // Present for PreToolUse, PostToolUse, PermissionRequest
185
186
  tool_response?: unknown; // Present for PostToolUse only
187
+ plan_file_path?: string; // Present when in plan mode
186
188
  user_prompt?: string; // Present for UserPromptSubmit only
187
189
  subagent_type?: string; // Present when hook is executed by a subagent
188
190
  name?: string; // Present for WorktreeCreate events
@@ -38,3 +38,4 @@ export * from "./agent.js";
38
38
  export * from "./cron.js";
39
39
  export * from "./telemetry.js";
40
40
  export * from "./auth.js";
41
+ export * from "./workflow.js";
@@ -106,8 +106,8 @@ export interface FileHistoryBlock {
106
106
  export interface TaskNotificationBlock {
107
107
  type: "task_notification";
108
108
  taskId: string;
109
- taskType: "shell" | "agent";
110
- status: "completed" | "failed" | "killed";
109
+ taskType: "shell" | "agent" | "workflow";
110
+ status: "completed" | "failed" | "killed" | "aborted";
111
111
  summary: string;
112
112
  outputFile?: string;
113
113
  }
@@ -10,7 +10,7 @@ export type BackgroundTaskStatus =
10
10
  | "completed"
11
11
  | "failed"
12
12
  | "killed";
13
- export type BackgroundTaskType = "shell" | "subagent";
13
+ export type BackgroundTaskType = "shell" | "subagent" | "workflow";
14
14
 
15
15
  export interface BackgroundTaskBase {
16
16
  id: string;
@@ -49,7 +49,15 @@ export interface BackgroundSubagent extends BackgroundTaskBase {
49
49
  type: "subagent";
50
50
  }
51
51
 
52
- export type BackgroundTask = BackgroundShell | BackgroundSubagent;
52
+ export interface BackgroundWorkflow extends BackgroundTaskBase {
53
+ type: "workflow";
54
+ runId: string;
55
+ }
56
+
57
+ export type BackgroundTask =
58
+ | BackgroundShell
59
+ | BackgroundSubagent
60
+ | BackgroundWorkflow;
53
61
 
54
62
  export interface ForegroundTask {
55
63
  id: string;
@@ -0,0 +1,5 @@
1
+ export type {
2
+ WorkflowRun,
3
+ WorkflowMeta,
4
+ WorkflowPhaseState,
5
+ } from "../workflow/types.js";
@@ -11,7 +11,6 @@ import type {
11
11
  ChatCompletionContentPart,
12
12
  ChatCompletionContentPartText,
13
13
  ChatCompletionFunctionTool,
14
- ChatCompletionMessageToolCall,
15
14
  CompletionUsage,
16
15
  } from "openai/resources";
17
16
  import { logger } from "./globalLogger.js";
@@ -48,20 +47,33 @@ export interface ClaudeChatCompletionFunctionTool
48
47
  }
49
48
 
50
49
  /**
51
- * Enhanced usage metrics including Claude cache information
50
+ * Extended prompt_tokens_details with cache_creation_input_tokens
51
+ * Some models (e.g. Gemini, DeepSeek) return this field inside prompt_tokens_details
52
+ */
53
+ export interface ExtendedPromptTokensDetails
54
+ extends CompletionUsage.PromptTokensDetails {
55
+ cache_creation_input_tokens?: number;
56
+ }
57
+
58
+ /**
59
+ * Enhanced usage metrics including cache information
60
+ * Supports both Claude-specific top-level fields and OpenAI-standard prompt_tokens_details
52
61
  */
53
62
  export interface ClaudeUsage extends CompletionUsage {
54
63
  prompt_tokens: number;
55
64
  completion_tokens: number;
56
65
  total_tokens: number;
57
66
 
58
- // Claude cache extensions
59
- cache_read_input_tokens?: number;
60
- cache_creation_input_tokens?: number;
67
+ // Cache extensions (from Claude top-level or OpenAI prompt_tokens_details)
68
+ cache_read_input_tokens?: number; // Claude: cache_read_input_tokens / OpenAI: prompt_tokens_details.cached_tokens
69
+ cache_creation_input_tokens?: number; // Claude: cache_creation_input_tokens / OpenAI: prompt_tokens_details.cache_creation_input_tokens
61
70
  cache_creation?: {
62
71
  ephemeral_5m_input_tokens: number;
63
72
  ephemeral_1h_input_tokens: number;
64
73
  };
74
+
75
+ // Override prompt_tokens_details to include cache_creation_input_tokens
76
+ prompt_tokens_details?: ExtendedPromptTokensDetails;
65
77
  }
66
78
 
67
79
  // ============================================================================
@@ -122,30 +134,6 @@ export function isValidCacheControl(control: unknown): control is CacheControl {
122
134
  );
123
135
  }
124
136
 
125
- /**
126
- * Adds cache control to the last tool call in an array
127
- * @param toolCalls - Array of tool calls
128
- * @returns Tool calls array with cache control on the last tool call
129
- */
130
- function addCacheControlToLastToolCall(
131
- toolCalls: ChatCompletionMessageToolCall[],
132
- ): ChatCompletionMessageToolCall[] {
133
- if (!toolCalls || toolCalls.length === 0) {
134
- return toolCalls;
135
- }
136
-
137
- const result = [...toolCalls];
138
- const lastIndex = result.length - 1;
139
-
140
- // Add cache control to the last tool call
141
- result[lastIndex] = {
142
- ...result[lastIndex],
143
- cache_control: { type: "ephemeral" },
144
- } as ChatCompletionMessageToolCall & { cache_control: CacheControl };
145
-
146
- return result;
147
- }
148
-
149
137
  /**
150
138
  * Adds cache control markers to message content
151
139
  * @param content - Original content (string or structured)
@@ -208,19 +196,36 @@ export function addCacheControlToContent(
208
196
  return [];
209
197
  }
210
198
 
211
- // Handle structured content - preserve existing structure, add cache control to text parts
212
- return content
213
- .filter((part): part is ChatCompletionContentPartText => {
214
- if (!part || typeof part !== "object") {
215
- return false;
216
- }
217
- return part.type === "text" && typeof part.text === "string";
218
- })
219
- .map((part) => ({
220
- type: "text",
221
- text: part.text,
222
- cache_control: { type: "ephemeral" },
223
- }));
199
+ // Handle structured content - preserve all parts, add cache control to last text part only
200
+ let lastTextIndex = -1;
201
+ for (let i = content.length - 1; i >= 0; i--) {
202
+ const part = content[i];
203
+ if (
204
+ part &&
205
+ typeof part === "object" &&
206
+ part.type === "text" &&
207
+ typeof (part as ChatCompletionContentPartText).text === "string"
208
+ ) {
209
+ lastTextIndex = i;
210
+ break;
211
+ }
212
+ }
213
+
214
+ return content.map((part, index) => {
215
+ if (
216
+ index === lastTextIndex &&
217
+ part &&
218
+ typeof part === "object" &&
219
+ part.type === "text" &&
220
+ typeof (part as ChatCompletionContentPartText).text === "string"
221
+ ) {
222
+ return {
223
+ ...(part as ChatCompletionContentPartText),
224
+ cache_control: { type: "ephemeral" },
225
+ };
226
+ }
227
+ return part;
228
+ }) as ClaudeChatCompletionContentPartText[];
224
229
  }
225
230
 
226
231
  /**
@@ -272,35 +277,6 @@ export function addCacheControlToLastTool(
272
277
  return result;
273
278
  }
274
279
 
275
- /**
276
- * Finds the latest message index at 20-message intervals (sliding window approach)
277
- * @param messages - Array of chat completion messages
278
- * @returns Index of the latest interval message (20th, 40th, 60th, etc.) or -1 if none
279
- */
280
- export function findIntervalMessageIndex(
281
- messages: ChatCompletionMessageParam[],
282
- ): number {
283
- if (!Array.isArray(messages) || messages.length === 0) {
284
- return -1;
285
- }
286
-
287
- const interval = 20; // Hardcoded interval
288
- const messageCount = messages.length;
289
-
290
- // Find the largest interval that fits within the message count
291
- // Math.floor(messageCount / interval) gives us how many complete intervals we have
292
- // Multiply by interval to get the position of the latest interval message
293
- const latestIntervalPosition = Math.floor(messageCount / interval) * interval;
294
-
295
- // If no complete intervals exist, return -1
296
- if (latestIntervalPosition === 0) {
297
- return -1;
298
- }
299
-
300
- // Convert from 1-based position to 0-based index
301
- return latestIntervalPosition - 1;
302
- }
303
-
304
280
  /**
305
281
  * Transforms messages for Claude cache control with hardcoded strategy
306
282
  * @param messages - Original OpenAI message array
@@ -328,12 +304,18 @@ export function transformMessagesForClaudeCache(
328
304
  return messages;
329
305
  }
330
306
 
331
- // Find the latest interval message index (20th, 40th, 60th, etc.)
332
- const intervalMessageIndex = findIntervalMessageIndex(messages);
333
-
334
307
  // Find first system message index
335
308
  const firstSystemIndex = messages.findIndex((m) => m.role === "system");
336
309
 
310
+ // Find last user message index
311
+ let lastUserIndex = -1;
312
+ for (let i = messages.length - 1; i >= 0; i--) {
313
+ if (messages[i].role === "user") {
314
+ lastUserIndex = i;
315
+ break;
316
+ }
317
+ }
318
+
337
319
  const result = messages.map((message, index) => {
338
320
  // Validate message structure
339
321
  if (!message || typeof message !== "object" || !message.role) {
@@ -365,40 +347,16 @@ export function transformMessagesForClaudeCache(
365
347
  } as ChatCompletionMessageParam;
366
348
  }
367
349
 
368
- // Interval-based message caching: cache message at latest interval position (sliding window)
369
- if (index === intervalMessageIndex) {
370
- // If the message is a tool role, add cache control to the content block
371
- if (message.role === "tool") {
372
- const content =
373
- typeof message.content === "string" ? message.content : "";
374
- const transformedContent = addCacheControlToContent(content, true);
375
-
376
- return {
377
- ...message,
378
- content: transformedContent,
379
- } as ChatCompletionMessageParam;
380
- }
381
- // If the message has tool calls, cache the last tool call instead of content
382
- else if (
383
- message.role === "assistant" &&
384
- message.tool_calls &&
385
- message.tool_calls.length > 0
386
- ) {
387
- return {
388
- ...message,
389
- tool_calls: addCacheControlToLastToolCall(message.tool_calls),
390
- } as ChatCompletionMessageParam;
391
- } else {
392
- // For other message types without tool calls, cache the content
393
- const content =
394
- (message.content as string | ChatCompletionContentPart[]) || "";
395
- const transformedContent = addCacheControlToContent(content, true);
396
-
397
- return {
398
- ...message,
399
- content: transformedContent,
400
- } as ChatCompletionMessageParam;
401
- }
350
+ // Last user message: cache recent conversation history
351
+ if (message.role === "user" && index === lastUserIndex) {
352
+ const content =
353
+ (message.content as string | ChatCompletionContentPart[]) || "";
354
+ const transformedContent = addCacheControlToContent(content, true);
355
+
356
+ return {
357
+ ...message,
358
+ content: transformedContent,
359
+ } as ChatCompletionMessageParam;
402
360
  }
403
361
 
404
362
  // Return message unchanged
@@ -410,8 +368,10 @@ export function transformMessagesForClaudeCache(
410
368
 
411
369
  /**
412
370
  * Extends standard usage with cache metrics
371
+ * Extracts cache tokens from both Claude-specific top-level fields and
372
+ * OpenAI-standard prompt_tokens_details (used by Gemini, DeepSeek, etc.)
413
373
  * @param standardUsage - OpenAI usage response
414
- * @param cacheMetrics - Additional cache metrics from Claude
374
+ * @param cacheMetrics - Additional cache metrics from the API response
415
375
  * @returns Extended usage with cache information
416
376
  */
417
377
  export function extendUsageWithCacheMetrics(
@@ -424,30 +384,45 @@ export function extendUsageWithCacheMetrics(
424
384
  total_tokens: standardUsage.total_tokens,
425
385
  };
426
386
 
427
- // Add cache metrics if provided
428
- if (cacheMetrics) {
429
- if (typeof cacheMetrics.cache_read_input_tokens === "number") {
430
- baseUsage.cache_read_input_tokens = cacheMetrics.cache_read_input_tokens;
431
- }
387
+ if (!cacheMetrics) {
388
+ return baseUsage;
389
+ }
432
390
 
433
- if (typeof cacheMetrics.cache_creation_input_tokens === "number") {
434
- baseUsage.cache_creation_input_tokens =
435
- cacheMetrics.cache_creation_input_tokens;
436
- }
391
+ // Extract cache_read_input_tokens from Claude top-level field
392
+ if (typeof cacheMetrics.cache_read_input_tokens === "number") {
393
+ baseUsage.cache_read_input_tokens = cacheMetrics.cache_read_input_tokens;
394
+ }
395
+ // Fallback to prompt_tokens_details.cached_tokens (OpenAI standard)
396
+ else if (cacheMetrics.prompt_tokens_details?.cached_tokens != null) {
397
+ baseUsage.cache_read_input_tokens =
398
+ cacheMetrics.prompt_tokens_details.cached_tokens;
399
+ }
437
400
 
438
- if (
439
- cacheMetrics.cache_creation &&
440
- typeof cacheMetrics.cache_creation.ephemeral_5m_input_tokens ===
441
- "number" &&
442
- typeof cacheMetrics.cache_creation.ephemeral_1h_input_tokens === "number"
443
- ) {
444
- baseUsage.cache_creation = {
445
- ephemeral_5m_input_tokens:
446
- cacheMetrics.cache_creation.ephemeral_5m_input_tokens,
447
- ephemeral_1h_input_tokens:
448
- cacheMetrics.cache_creation.ephemeral_1h_input_tokens,
449
- };
450
- }
401
+ // Extract cache_creation_input_tokens from Claude top-level field
402
+ if (typeof cacheMetrics.cache_creation_input_tokens === "number") {
403
+ baseUsage.cache_creation_input_tokens =
404
+ cacheMetrics.cache_creation_input_tokens;
405
+ }
406
+ // Fallback to prompt_tokens_details.cache_creation_input_tokens
407
+ else if (
408
+ cacheMetrics.prompt_tokens_details?.cache_creation_input_tokens != null
409
+ ) {
410
+ baseUsage.cache_creation_input_tokens =
411
+ cacheMetrics.prompt_tokens_details.cache_creation_input_tokens;
412
+ }
413
+
414
+ // Extract cache_creation breakdown (Claude-specific)
415
+ if (
416
+ cacheMetrics.cache_creation &&
417
+ typeof cacheMetrics.cache_creation.ephemeral_5m_input_tokens === "number" &&
418
+ typeof cacheMetrics.cache_creation.ephemeral_1h_input_tokens === "number"
419
+ ) {
420
+ baseUsage.cache_creation = {
421
+ ephemeral_5m_input_tokens:
422
+ cacheMetrics.cache_creation.ephemeral_5m_input_tokens,
423
+ ephemeral_1h_input_tokens:
424
+ cacheMetrics.cache_creation.ephemeral_1h_input_tokens,
425
+ };
451
426
  }
452
427
 
453
428
  return baseUsage;
@@ -17,6 +17,8 @@ import { SlashCommandManager } from "../managers/slashCommandManager.js";
17
17
  import { PluginManager } from "../managers/pluginManager.js";
18
18
  import { BangManager } from "../managers/bangManager.js";
19
19
  import { CronManager } from "../managers/cronManager.js";
20
+ import { GoalManager } from "../managers/goalManager.js";
21
+ import { WorkflowManager } from "../managers/workflowManager.js";
20
22
  import { MemoryRuleManager } from "../managers/MemoryRuleManager.js";
21
23
  import { ReversionManager } from "../managers/reversionManager.js";
22
24
  import { SubagentManager } from "../managers/subagentManager.js";
@@ -224,6 +226,7 @@ export function setupAgentContainer(
224
226
  cwd: workdir,
225
227
  toolName: context.toolName,
226
228
  toolInput: context.toolInput,
229
+ planFilePath: permissionManager.getPlanFilePath(),
227
230
  env: mergedEnv,
228
231
  });
229
232
 
@@ -347,5 +350,11 @@ export function setupAgentContainer(
347
350
  container.register("CronManager", cronManager);
348
351
  cronManager.start();
349
352
 
353
+ const goalManager = new GoalManager(container);
354
+ container.register("GoalManager", goalManager);
355
+
356
+ const workflowManager = new WorkflowManager(container);
357
+ container.register("WorkflowManager", workflowManager);
358
+
350
359
  return container;
351
360
  }