@synergenius/flow-weaver 0.24.0 → 0.24.1

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.
@@ -4,7 +4,8 @@
4
4
  * Provider-agnostic agent loop with MCP bridge for tool execution.
5
5
  * Built-in providers: Anthropic API, Claude CLI, OpenAI-compatible (GPT-4o, Groq, Ollama, etc).
6
6
  */
7
- export type { StreamEvent, AgentMessage, AgentProvider, ToolDefinition, ToolExecutor, ToolEvent, McpBridge, AgentLoopOptions, AgentLoopResult, StreamOptions, SpawnFn, ClaudeCliProviderOptions, CliSessionOptions, Logger, } from './types.js';
7
+ export type { SplitPrompt, StreamEvent, AgentMessage, AgentProvider, ToolDefinition, ToolExecutor, ToolEvent, McpBridge, AgentLoopOptions, AgentLoopResult, StreamOptions, SpawnFn, ClaudeCliProviderOptions, CliSessionOptions, Logger, } from './types.js';
8
+ export { joinSplitPrompt } from './types.js';
8
9
  export { runAgentLoop } from './agent-loop.js';
9
10
  export { AnthropicProvider, createAnthropicProvider } from './providers/anthropic.js';
10
11
  export type { AnthropicProviderOptions } from './providers/anthropic.js';
@@ -4,6 +4,8 @@
4
4
  * Provider-agnostic agent loop with MCP bridge for tool execution.
5
5
  * Built-in providers: Anthropic API, Claude CLI, OpenAI-compatible (GPT-4o, Groq, Ollama, etc).
6
6
  */
7
+ // Prompt utilities
8
+ export { joinSplitPrompt } from './types.js';
7
9
  // Agent loop
8
10
  export { runAgentLoop } from './agent-loop.js';
9
11
  // Providers
@@ -4,6 +4,19 @@
4
4
  *
5
5
  * Adapted from pack-weaver's streamAnthropicWithTools.
6
6
  */
7
+ /**
8
+ * Convert a SplitPrompt to Anthropic's system content block array.
9
+ * The prefix gets cache_control for prompt caching; the suffix does not.
10
+ */
11
+ function buildSystemBlocks(prompt) {
12
+ const blocks = [
13
+ { type: 'text', text: prompt.prefix, cache_control: { type: 'ephemeral' } },
14
+ ];
15
+ if (prompt.suffix) {
16
+ blocks.push({ type: 'text', text: prompt.suffix });
17
+ }
18
+ return blocks;
19
+ }
7
20
  export class AnthropicProvider {
8
21
  apiKey;
9
22
  model;
@@ -49,7 +62,7 @@ export class AnthropicProvider {
49
62
  model,
50
63
  max_tokens: maxTokens,
51
64
  stream: true,
52
- ...(options?.systemPrompt ? { system: options.systemPrompt } : {}),
65
+ ...(options?.systemPrompt ? { system: buildSystemBlocks(options.systemPrompt) } : {}),
53
66
  messages: apiMessages,
54
67
  ...(apiTools.length > 0 ? { tools: apiTools } : {}),
55
68
  });
@@ -5,7 +5,7 @@
5
5
  * Adapted from platform's streamClaudeCliChat. Platform-specific dependencies
6
6
  * (spawnSandboxed, getBinPath, config) are replaced with injectable options.
7
7
  */
8
- import type { AgentProvider, AgentMessage, ToolDefinition, StreamEvent, StreamOptions, ClaudeCliProviderOptions } from '../types.js';
8
+ import { type AgentProvider, type AgentMessage, type ToolDefinition, type StreamEvent, type StreamOptions, type ClaudeCliProviderOptions } from '../types.js';
9
9
  export declare class ClaudeCliProvider implements AgentProvider {
10
10
  private binPath;
11
11
  private cwd;
@@ -6,6 +6,7 @@
6
6
  * (spawnSandboxed, getBinPath, config) are replaced with injectable options.
7
7
  */
8
8
  import { spawn as nodeSpawn } from 'node:child_process';
9
+ import { joinSplitPrompt, } from '../types.js';
9
10
  import { StreamJsonParser } from '../streaming.js';
10
11
  import { createMcpBridge } from '../mcp-bridge.js';
11
12
  export class ClaudeCliProvider {
@@ -31,7 +32,9 @@ export class ClaudeCliProvider {
31
32
  const model = options?.model ?? this.model;
32
33
  // Format messages into a single prompt for -p mode
33
34
  const prompt = formatPrompt(messages);
34
- const systemPrompt = options?.systemPrompt;
35
+ const systemPrompt = options?.systemPrompt
36
+ ? joinSplitPrompt(options.systemPrompt)
37
+ : undefined;
35
38
  // Set up MCP bridge for tool access if tools are provided and no config given
36
39
  let bridge = null;
37
40
  let mcpConfigPath = this.mcpConfigPath;
@@ -5,7 +5,7 @@
5
5
  * No SDK dependency. Uses only Node.js native fetch + SSE parsing.
6
6
  * Converts OpenAI's delta format to the canonical StreamEvent union.
7
7
  */
8
- import type { AgentProvider, AgentMessage, ToolDefinition, StreamEvent, StreamOptions } from '../types.js';
8
+ import { type AgentProvider, type AgentMessage, type ToolDefinition, type StreamEvent, type StreamOptions } from '../types.js';
9
9
  export interface OpenAICompatProviderOptions {
10
10
  apiKey: string;
11
11
  model?: string;
@@ -5,6 +5,7 @@
5
5
  * No SDK dependency. Uses only Node.js native fetch + SSE parsing.
6
6
  * Converts OpenAI's delta format to the canonical StreamEvent union.
7
7
  */
8
+ import { joinSplitPrompt } from '../types.js';
8
9
  export class OpenAICompatProvider {
9
10
  apiKey;
10
11
  model;
@@ -38,7 +39,7 @@ export class OpenAICompatProvider {
38
39
  const body = {
39
40
  model,
40
41
  messages: [
41
- ...(options?.systemPrompt ? [{ role: 'system', content: options.systemPrompt }] : []),
42
+ ...(options?.systemPrompt ? [{ role: 'system', content: joinSplitPrompt(options.systemPrompt) }] : []),
42
43
  ...apiMessages,
43
44
  ],
44
45
  max_tokens: maxTokens,
@@ -3,7 +3,7 @@
3
3
  * Uses the platform's AI credits, no local API key needed.
4
4
  * Connects to POST /ai-chat/stream and parses SSE events.
5
5
  */
6
- import type { AgentProvider, AgentMessage, ToolDefinition, StreamEvent, StreamOptions } from '../types.js';
6
+ import { type AgentProvider, type AgentMessage, type ToolDefinition, type StreamEvent, type StreamOptions } from '../types.js';
7
7
  export interface PlatformProviderOptions {
8
8
  /** JWT token or API key for platform auth */
9
9
  token: string;
@@ -3,6 +3,7 @@
3
3
  * Uses the platform's AI credits, no local API key needed.
4
4
  * Connects to POST /ai-chat/stream and parses SSE events.
5
5
  */
6
+ import { joinSplitPrompt } from '../types.js';
6
7
  export class PlatformProvider {
7
8
  token;
8
9
  baseUrl;
@@ -37,7 +38,8 @@ export class PlatformProvider {
37
38
  const body = { message };
38
39
  if (options?.systemPrompt) {
39
40
  // Platform doesn't accept system prompt directly via API — embed in message
40
- body.message = `[System context: ${options.systemPrompt.slice(0, 2000)}]\n\n${message}`;
41
+ const systemStr = joinSplitPrompt(options.systemPrompt);
42
+ body.message = `[System context: ${systemStr.slice(0, 2000)}]\n\n${message}`;
41
43
  }
42
44
  const response = await fetch(`${this.baseUrl}/ai-chat/stream`, {
43
45
  method: 'POST',
@@ -65,8 +65,25 @@ export interface ToolEvent {
65
65
  result?: string;
66
66
  isError?: boolean;
67
67
  }
68
+ /**
69
+ * Split system prompt for Anthropic API cache optimization.
70
+ *
71
+ * The prefix (stable FW knowledge) is cached across calls via cache_control.
72
+ * The suffix (per-task context) varies per call but rides on the cached prefix.
73
+ *
74
+ * Providers that support structured system blocks (Anthropic) use both parts.
75
+ * Providers that only accept strings (CLI, OpenAI, platform) concatenate them.
76
+ */
77
+ export interface SplitPrompt {
78
+ /** Stable prefix — identical across calls. Cacheable. */
79
+ prefix: string;
80
+ /** Dynamic suffix — varies per task/call. Not cached. */
81
+ suffix: string;
82
+ }
83
+ /** Convert a SplitPrompt to a single string (for providers that don't support blocks). */
84
+ export declare function joinSplitPrompt(prompt: SplitPrompt): string;
68
85
  export interface StreamOptions {
69
- systemPrompt?: string;
86
+ systemPrompt?: SplitPrompt;
70
87
  model?: string;
71
88
  maxTokens?: number;
72
89
  signal?: AbortSignal;
@@ -89,7 +106,7 @@ export interface McpBridge {
89
106
  cleanup: () => void;
90
107
  }
91
108
  export interface AgentLoopOptions {
92
- systemPrompt?: string;
109
+ systemPrompt?: SplitPrompt;
93
110
  maxIterations?: number;
94
111
  maxTokens?: number;
95
112
  model?: string;
@@ -145,6 +162,8 @@ export interface CliSessionOptions {
145
162
  model: string;
146
163
  /** Pre-configured MCP config path. */
147
164
  mcpConfigPath?: string;
165
+ /** Disable specific built-in tools (e.g. ['Read', 'Edit', 'Write', 'Bash'] to force MCP tools). */
166
+ disallowedTools?: string[];
148
167
  /** Custom spawn function. Defaults to child_process.spawn. */
149
168
  spawnFn?: SpawnFn;
150
169
  /** Idle timeout in milliseconds. Defaults to 600000 (10 minutes). */
@@ -3,5 +3,10 @@
3
3
  *
4
4
  * All types are pure — no runtime imports, no side effects.
5
5
  */
6
- export {};
6
+ /** Convert a SplitPrompt to a single string (for providers that don't support blocks). */
7
+ export function joinSplitPrompt(prompt) {
8
+ if (!prompt.suffix)
9
+ return prompt.prefix;
10
+ return prompt.prefix + '\n\n' + prompt.suffix;
11
+ }
7
12
  //# sourceMappingURL=types.js.map
@@ -9886,7 +9886,7 @@ var VERSION;
9886
9886
  var init_generated_version = __esm({
9887
9887
  "src/generated-version.ts"() {
9888
9888
  "use strict";
9889
- VERSION = "0.24.0";
9889
+ VERSION = "0.24.1";
9890
9890
  }
9891
9891
  });
9892
9892
 
@@ -91095,8 +91095,8 @@ var init_debug_session = __esm({
91095
91095
  import * as path36 from "path";
91096
91096
  import * as fs31 from "fs";
91097
91097
  async function getExecutionOrder(filePath, workflowName) {
91098
- const source = fs31.readFileSync(path36.resolve(filePath), "utf8");
91099
- const parsed = await parseWorkflow(source, { workflowName });
91098
+ const resolvedPath = path36.resolve(filePath);
91099
+ const parsed = await parseWorkflow(resolvedPath, { workflowName });
91100
91100
  if (parsed.errors.length > 0) {
91101
91101
  throw new Error(`Failed to parse workflow: ${parsed.errors.join(", ")}`);
91102
91102
  }
@@ -95943,7 +95943,7 @@ function parseIntStrict(value) {
95943
95943
  // src/cli/index.ts
95944
95944
  init_logger();
95945
95945
  init_error_utils();
95946
- var version2 = true ? "0.24.0" : "0.0.0-dev";
95946
+ var version2 = true ? "0.24.1" : "0.0.0-dev";
95947
95947
  var program2 = new Command();
95948
95948
  program2.name("fw").description("Flow Weaver Annotations - Compile and validate workflow files").option("-v, --version", "Output the current version").option("--no-color", "Disable colors").option("--color", "Force colors").on("option:version", () => {
95949
95949
  logger.banner(version2);
@@ -1,2 +1,2 @@
1
- export declare const VERSION = "0.24.0";
1
+ export declare const VERSION = "0.24.1";
2
2
  //# sourceMappingURL=generated-version.d.ts.map
@@ -1,3 +1,3 @@
1
1
  // Auto-generated by scripts/generate-version.ts — do not edit manually
2
- export const VERSION = '0.24.0';
2
+ export const VERSION = '0.24.1';
3
3
  //# sourceMappingURL=generated-version.js.map
@@ -13,8 +13,8 @@ import { makeToolResult, makeErrorResult } from './response-utils.js';
13
13
  * Helper: get execution order for a workflow file by parsing its annotations.
14
14
  */
15
15
  async function getExecutionOrder(filePath, workflowName) {
16
- const source = fs.readFileSync(path.resolve(filePath), 'utf8');
17
- const parsed = await parseWorkflow(source, { workflowName });
16
+ const resolvedPath = path.resolve(filePath);
17
+ const parsed = await parseWorkflow(resolvedPath, { workflowName });
18
18
  if (parsed.errors.length > 0) {
19
19
  throw new Error(`Failed to parse workflow: ${parsed.errors.join(', ')}`);
20
20
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@synergenius/flow-weaver",
3
- "version": "0.24.0",
3
+ "version": "0.24.1",
4
4
  "description": "Deterministic workflow compiler for AI agents. Compiles to standalone TypeScript, no runtime dependencies.",
5
5
  "private": false,
6
6
  "type": "module",