@blockrun/franklin 3.0.0

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 (138) hide show
  1. package/LICENSE +190 -0
  2. package/README.md +256 -0
  3. package/dist/agent/commands.d.ts +27 -0
  4. package/dist/agent/commands.js +659 -0
  5. package/dist/agent/compact.d.ts +31 -0
  6. package/dist/agent/compact.js +366 -0
  7. package/dist/agent/context.d.ts +11 -0
  8. package/dist/agent/context.js +184 -0
  9. package/dist/agent/error-classifier.d.ts +10 -0
  10. package/dist/agent/error-classifier.js +61 -0
  11. package/dist/agent/llm.d.ts +63 -0
  12. package/dist/agent/llm.js +448 -0
  13. package/dist/agent/loop.d.ts +12 -0
  14. package/dist/agent/loop.js +346 -0
  15. package/dist/agent/optimize.d.ts +53 -0
  16. package/dist/agent/optimize.js +262 -0
  17. package/dist/agent/permissions.d.ts +39 -0
  18. package/dist/agent/permissions.js +226 -0
  19. package/dist/agent/reduce.d.ts +49 -0
  20. package/dist/agent/reduce.js +317 -0
  21. package/dist/agent/streaming-executor.d.ts +36 -0
  22. package/dist/agent/streaming-executor.js +149 -0
  23. package/dist/agent/tokens.d.ts +53 -0
  24. package/dist/agent/tokens.js +185 -0
  25. package/dist/agent/types.d.ts +125 -0
  26. package/dist/agent/types.js +5 -0
  27. package/dist/banner.d.ts +1 -0
  28. package/dist/banner.js +27 -0
  29. package/dist/commands/balance.d.ts +1 -0
  30. package/dist/commands/balance.js +40 -0
  31. package/dist/commands/config.d.ts +14 -0
  32. package/dist/commands/config.js +107 -0
  33. package/dist/commands/daemon.d.ts +3 -0
  34. package/dist/commands/daemon.js +117 -0
  35. package/dist/commands/history.d.ts +5 -0
  36. package/dist/commands/history.js +31 -0
  37. package/dist/commands/init.d.ts +3 -0
  38. package/dist/commands/init.js +92 -0
  39. package/dist/commands/logs.d.ts +5 -0
  40. package/dist/commands/logs.js +89 -0
  41. package/dist/commands/models.d.ts +1 -0
  42. package/dist/commands/models.js +56 -0
  43. package/dist/commands/plugin.d.ts +14 -0
  44. package/dist/commands/plugin.js +176 -0
  45. package/dist/commands/proxy.d.ts +13 -0
  46. package/dist/commands/proxy.js +106 -0
  47. package/dist/commands/setup.d.ts +1 -0
  48. package/dist/commands/setup.js +49 -0
  49. package/dist/commands/start.d.ts +8 -0
  50. package/dist/commands/start.js +292 -0
  51. package/dist/commands/stats.d.ts +10 -0
  52. package/dist/commands/stats.js +94 -0
  53. package/dist/commands/uninit.d.ts +1 -0
  54. package/dist/commands/uninit.js +63 -0
  55. package/dist/config.d.ts +9 -0
  56. package/dist/config.js +41 -0
  57. package/dist/index.d.ts +2 -0
  58. package/dist/index.js +179 -0
  59. package/dist/mcp/client.d.ts +44 -0
  60. package/dist/mcp/client.js +147 -0
  61. package/dist/mcp/config.d.ts +20 -0
  62. package/dist/mcp/config.js +138 -0
  63. package/dist/plugin-sdk/channel.d.ts +100 -0
  64. package/dist/plugin-sdk/channel.js +10 -0
  65. package/dist/plugin-sdk/index.d.ts +14 -0
  66. package/dist/plugin-sdk/index.js +9 -0
  67. package/dist/plugin-sdk/plugin.d.ts +87 -0
  68. package/dist/plugin-sdk/plugin.js +7 -0
  69. package/dist/plugin-sdk/search.d.ts +13 -0
  70. package/dist/plugin-sdk/search.js +4 -0
  71. package/dist/plugin-sdk/tracker.d.ts +27 -0
  72. package/dist/plugin-sdk/tracker.js +5 -0
  73. package/dist/plugin-sdk/workflow.d.ts +126 -0
  74. package/dist/plugin-sdk/workflow.js +11 -0
  75. package/dist/plugins/registry.d.ts +33 -0
  76. package/dist/plugins/registry.js +155 -0
  77. package/dist/plugins/runner.d.ts +21 -0
  78. package/dist/plugins/runner.js +453 -0
  79. package/dist/plugins-bundled/social/index.d.ts +10 -0
  80. package/dist/plugins-bundled/social/index.js +363 -0
  81. package/dist/plugins-bundled/social/plugin.json +14 -0
  82. package/dist/plugins-bundled/social/prompts.d.ts +19 -0
  83. package/dist/plugins-bundled/social/prompts.js +67 -0
  84. package/dist/plugins-bundled/social/types.d.ts +58 -0
  85. package/dist/plugins-bundled/social/types.js +16 -0
  86. package/dist/pricing.d.ts +21 -0
  87. package/dist/pricing.js +91 -0
  88. package/dist/proxy/fallback.d.ts +38 -0
  89. package/dist/proxy/fallback.js +144 -0
  90. package/dist/proxy/server.d.ts +18 -0
  91. package/dist/proxy/server.js +576 -0
  92. package/dist/proxy/sse-translator.d.ts +29 -0
  93. package/dist/proxy/sse-translator.js +270 -0
  94. package/dist/router/index.d.ts +22 -0
  95. package/dist/router/index.js +269 -0
  96. package/dist/session/search.d.ts +33 -0
  97. package/dist/session/search.js +229 -0
  98. package/dist/session/storage.d.ts +48 -0
  99. package/dist/session/storage.js +173 -0
  100. package/dist/stats/insights.d.ts +55 -0
  101. package/dist/stats/insights.js +195 -0
  102. package/dist/stats/tracker.d.ts +54 -0
  103. package/dist/stats/tracker.js +165 -0
  104. package/dist/tools/askuser.d.ts +6 -0
  105. package/dist/tools/askuser.js +76 -0
  106. package/dist/tools/bash.d.ts +5 -0
  107. package/dist/tools/bash.js +336 -0
  108. package/dist/tools/edit.d.ts +5 -0
  109. package/dist/tools/edit.js +148 -0
  110. package/dist/tools/glob.d.ts +5 -0
  111. package/dist/tools/glob.js +158 -0
  112. package/dist/tools/grep.d.ts +5 -0
  113. package/dist/tools/grep.js +194 -0
  114. package/dist/tools/imagegen.d.ts +6 -0
  115. package/dist/tools/imagegen.js +172 -0
  116. package/dist/tools/index.d.ts +17 -0
  117. package/dist/tools/index.js +30 -0
  118. package/dist/tools/read.d.ts +11 -0
  119. package/dist/tools/read.js +90 -0
  120. package/dist/tools/subagent.d.ts +5 -0
  121. package/dist/tools/subagent.js +116 -0
  122. package/dist/tools/task.d.ts +5 -0
  123. package/dist/tools/task.js +91 -0
  124. package/dist/tools/webfetch.d.ts +5 -0
  125. package/dist/tools/webfetch.js +166 -0
  126. package/dist/tools/websearch.d.ts +5 -0
  127. package/dist/tools/websearch.js +103 -0
  128. package/dist/tools/write.d.ts +5 -0
  129. package/dist/tools/write.js +114 -0
  130. package/dist/ui/app.d.ts +26 -0
  131. package/dist/ui/app.js +545 -0
  132. package/dist/ui/model-picker.d.ts +14 -0
  133. package/dist/ui/model-picker.js +161 -0
  134. package/dist/ui/terminal.d.ts +35 -0
  135. package/dist/ui/terminal.js +337 -0
  136. package/dist/wallet/manager.d.ts +10 -0
  137. package/dist/wallet/manager.js +23 -0
  138. package/package.json +79 -0
@@ -0,0 +1,138 @@
1
+ /**
2
+ * MCP configuration management for runcode.
3
+ * Loads MCP server configs from:
4
+ * 1. Global: ~/.blockrun/mcp.json
5
+ * 2. Project: .mcp.json in working directory
6
+ */
7
+ import fs from 'node:fs';
8
+ import path from 'node:path';
9
+ import { execSync } from 'node:child_process';
10
+ import { BLOCKRUN_DIR } from '../config.js';
11
+ const GLOBAL_MCP_FILE = path.join(BLOCKRUN_DIR, 'mcp.json');
12
+ /**
13
+ * Load MCP server configurations from global + project files.
14
+ * Project config overrides global for same server name.
15
+ */
16
+ // Built-in MCP server: @blockrun/mcp available when globally installed
17
+ // Uses `blockrun-mcp` binary instead of `npx` for fast startup
18
+ const BUILTIN_MCP_SERVERS = {
19
+ blockrun: {
20
+ transport: 'stdio',
21
+ command: 'blockrun-mcp',
22
+ args: [],
23
+ label: 'BlockRun (built-in)',
24
+ },
25
+ unbrowse: {
26
+ transport: 'stdio',
27
+ command: 'unbrowse',
28
+ args: ['mcp'],
29
+ label: 'Unbrowse (built-in)',
30
+ },
31
+ };
32
+ function isCommandAvailable(cmd) {
33
+ try {
34
+ execSync(`which ${cmd}`, { stdio: 'pipe' });
35
+ return true;
36
+ }
37
+ catch {
38
+ return false;
39
+ }
40
+ }
41
+ export function loadMcpConfig(workDir) {
42
+ // Start with built-in servers (only if their binary is available)
43
+ const servers = {};
44
+ for (const [name, config] of Object.entries(BUILTIN_MCP_SERVERS)) {
45
+ if (config.command && isCommandAvailable(config.command)) {
46
+ servers[name] = config;
47
+ }
48
+ }
49
+ // 1. Global config
50
+ try {
51
+ if (fs.existsSync(GLOBAL_MCP_FILE)) {
52
+ const raw = JSON.parse(fs.readFileSync(GLOBAL_MCP_FILE, 'utf-8'));
53
+ if (raw.mcpServers && typeof raw.mcpServers === 'object') {
54
+ Object.assign(servers, raw.mcpServers);
55
+ }
56
+ }
57
+ }
58
+ catch {
59
+ // Ignore corrupt global config
60
+ }
61
+ // 2. Project config (.mcp.json in working directory)
62
+ // Security: project configs can execute arbitrary commands via stdio transport.
63
+ // Only load if a trust marker exists (user has explicitly opted in).
64
+ const projectMcpFile = path.join(workDir, '.mcp.json');
65
+ const trustMarker = path.join(BLOCKRUN_DIR, 'trusted-projects.json');
66
+ try {
67
+ if (fs.existsSync(projectMcpFile)) {
68
+ // Check if this project directory is trusted
69
+ let trusted = false;
70
+ try {
71
+ if (fs.existsSync(trustMarker)) {
72
+ const trustedDirs = JSON.parse(fs.readFileSync(trustMarker, 'utf-8'));
73
+ trusted = Array.isArray(trustedDirs) && trustedDirs.includes(workDir);
74
+ }
75
+ }
76
+ catch { /* not trusted */ }
77
+ if (trusted) {
78
+ const raw = JSON.parse(fs.readFileSync(projectMcpFile, 'utf-8'));
79
+ if (raw.mcpServers && typeof raw.mcpServers === 'object') {
80
+ Object.assign(servers, raw.mcpServers);
81
+ }
82
+ }
83
+ // If not trusted, silently skip project config (user must run /mcp trust)
84
+ }
85
+ }
86
+ catch {
87
+ // Ignore corrupt project config
88
+ }
89
+ return { mcpServers: servers };
90
+ }
91
+ /**
92
+ * Save a server config to the global MCP config.
93
+ */
94
+ export function saveMcpServer(name, config) {
95
+ const existing = loadGlobalMcpConfig();
96
+ existing.mcpServers[name] = config;
97
+ fs.mkdirSync(BLOCKRUN_DIR, { recursive: true });
98
+ fs.writeFileSync(GLOBAL_MCP_FILE, JSON.stringify(existing, null, 2) + '\n');
99
+ }
100
+ /**
101
+ * Remove a server from the global MCP config.
102
+ */
103
+ export function removeMcpServer(name) {
104
+ const existing = loadGlobalMcpConfig();
105
+ if (!(name in existing.mcpServers))
106
+ return false;
107
+ delete existing.mcpServers[name];
108
+ fs.writeFileSync(GLOBAL_MCP_FILE, JSON.stringify(existing, null, 2) + '\n');
109
+ return true;
110
+ }
111
+ /**
112
+ * Trust a project directory to load its .mcp.json.
113
+ */
114
+ export function trustProjectDir(workDir) {
115
+ const trustMarker = path.join(BLOCKRUN_DIR, 'trusted-projects.json');
116
+ let trusted = [];
117
+ try {
118
+ if (fs.existsSync(trustMarker)) {
119
+ trusted = JSON.parse(fs.readFileSync(trustMarker, 'utf-8'));
120
+ }
121
+ }
122
+ catch { /* fresh */ }
123
+ if (!trusted.includes(workDir)) {
124
+ trusted.push(workDir);
125
+ fs.mkdirSync(BLOCKRUN_DIR, { recursive: true });
126
+ fs.writeFileSync(trustMarker, JSON.stringify(trusted, null, 2));
127
+ }
128
+ }
129
+ function loadGlobalMcpConfig() {
130
+ try {
131
+ if (fs.existsSync(GLOBAL_MCP_FILE)) {
132
+ const raw = JSON.parse(fs.readFileSync(GLOBAL_MCP_FILE, 'utf-8'));
133
+ return { mcpServers: raw.mcpServers || {} };
134
+ }
135
+ }
136
+ catch { /* fresh */ }
137
+ return { mcpServers: {} };
138
+ }
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Channel contract — abstraction over messaging/social platforms.
3
+ *
4
+ * Channels are platforms where messages can be searched and posted:
5
+ * Reddit, X/Twitter, Telegram, Slack, Discord, HackerNews, etc.
6
+ *
7
+ * Workflows interact with channels through this contract — never with
8
+ * platform-specific code directly.
9
+ */
10
+ /** A message to be posted on a channel */
11
+ export interface ChannelMessage {
12
+ /** Plain text content */
13
+ text: string;
14
+ /** Optional image URL or local path */
15
+ image?: string;
16
+ /** Reply to a specific post (URL or platform-specific id) */
17
+ inReplyTo?: string;
18
+ /** Platform-specific metadata (e.g. subreddit name for Reddit) */
19
+ metadata?: Record<string, unknown>;
20
+ }
21
+ /** A post discovered via channel search */
22
+ export interface ChannelPost {
23
+ /** Post URL */
24
+ url: string;
25
+ /** Post id (platform-specific) */
26
+ id: string;
27
+ /** Post title (or first line for X) */
28
+ title: string;
29
+ /** Post body */
30
+ body: string;
31
+ /** Author username */
32
+ author?: string;
33
+ /** Created timestamp (ISO) */
34
+ createdAt?: string;
35
+ /** Engagement metrics */
36
+ score?: number;
37
+ /** Reply/comment count */
38
+ commentCount?: number;
39
+ /** Platform identifier */
40
+ platform: string;
41
+ /** Platform-specific raw data */
42
+ raw?: Record<string, unknown>;
43
+ }
44
+ /** Channel search result wrapper */
45
+ export interface ChannelSearchResult {
46
+ posts: ChannelPost[];
47
+ /** Total found (may be larger than posts.length if paginated) */
48
+ total: number;
49
+ }
50
+ /** Channel context provided by core */
51
+ export interface ChannelContext {
52
+ /** Channel-specific auth/config from user settings */
53
+ auth?: ChannelAuth;
54
+ /** Logger */
55
+ log: (message: string) => void;
56
+ /** Dry-run mode — channels should not actually post */
57
+ dryRun: boolean;
58
+ }
59
+ /** Auth blob — channel-specific shape */
60
+ export interface ChannelAuth {
61
+ /** Auth method */
62
+ method: 'browser' | 'api' | 'oauth' | 'none';
63
+ /** Browser cookies (for browser auth) */
64
+ cookies?: string;
65
+ /** API token (for api auth) */
66
+ token?: string;
67
+ /** OAuth refresh token */
68
+ refreshToken?: string;
69
+ /** Username on the platform */
70
+ username?: string;
71
+ /** Platform-specific extra fields */
72
+ extra?: Record<string, unknown>;
73
+ }
74
+ /**
75
+ * Channel interface — implemented by channel plugins.
76
+ * Each channel knows how to search and post on its platform.
77
+ */
78
+ export interface Channel {
79
+ /** Channel id (e.g. "reddit", "x", "telegram") */
80
+ readonly id: string;
81
+ /** Display name */
82
+ readonly name: string;
83
+ /** Search posts on this channel */
84
+ search(query: string, ctx: ChannelContext, options?: {
85
+ maxResults?: number;
86
+ /** Platform-specific scope (e.g. subreddit for Reddit) */
87
+ scope?: string[];
88
+ }): Promise<ChannelSearchResult>;
89
+ /** Post a message (or reply) on this channel */
90
+ post(message: ChannelMessage, ctx: ChannelContext): Promise<{
91
+ /** URL of the posted message */
92
+ url: string;
93
+ /** Platform-specific id */
94
+ id: string;
95
+ }>;
96
+ /** Check if the channel is properly authenticated */
97
+ isAuthenticated(ctx: ChannelContext): Promise<boolean>;
98
+ /** Optional: rate limiting hint (min seconds between posts) */
99
+ readonly minDelaySeconds?: number;
100
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Channel contract — abstraction over messaging/social platforms.
3
+ *
4
+ * Channels are platforms where messages can be searched and posted:
5
+ * Reddit, X/Twitter, Telegram, Slack, Discord, HackerNews, etc.
6
+ *
7
+ * Workflows interact with channels through this contract — never with
8
+ * platform-specific code directly.
9
+ */
10
+ export {};
@@ -0,0 +1,14 @@
1
+ /**
2
+ * RunCode Plugin SDK — public surface for plugins.
3
+ *
4
+ * Plugins import ONLY from '@blockrun/runcode/plugin-sdk' (or this barrel).
5
+ * They MUST NOT import from src/** of core or other plugins.
6
+ *
7
+ * Core stays plugin-agnostic: adding a plugin should never require editing core.
8
+ */
9
+ export type { Plugin, PluginManifest, PluginContext, PluginCommand, PluginCommandHandler, } from './plugin.js';
10
+ export type { Workflow, WorkflowStep, WorkflowStepContext, WorkflowStepResult, WorkflowResult, WorkflowConfig, ModelTier, ModelTierConfig, WorkflowStepStatus, OnboardingQuestion, } from './workflow.js';
11
+ export { DEFAULT_MODEL_TIERS } from './workflow.js';
12
+ export type { Channel, ChannelContext, ChannelMessage, ChannelPost, ChannelSearchResult, ChannelAuth, } from './channel.js';
13
+ export type { WorkflowTracker, TrackedAction, WorkflowStats, } from './tracker.js';
14
+ export type { SearchResult } from './search.js';
@@ -0,0 +1,9 @@
1
+ /**
2
+ * RunCode Plugin SDK — public surface for plugins.
3
+ *
4
+ * Plugins import ONLY from '@blockrun/runcode/plugin-sdk' (or this barrel).
5
+ * They MUST NOT import from src/** of core or other plugins.
6
+ *
7
+ * Core stays plugin-agnostic: adding a plugin should never require editing core.
8
+ */
9
+ export { DEFAULT_MODEL_TIERS } from './workflow.js';
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Plugin contract — what every plugin must export.
3
+ *
4
+ * Plugins are discovered by their manifest file (plugin.json) and loaded
5
+ * dynamically. Core code never references plugins by name.
6
+ */
7
+ import type { Workflow } from './workflow.js';
8
+ import type { Channel } from './channel.js';
9
+ /** Plugin manifest (plugin.json) */
10
+ export interface PluginManifest {
11
+ /** Unique plugin id (e.g. "social", "trading") */
12
+ id: string;
13
+ /** Display name */
14
+ name: string;
15
+ /** Short description */
16
+ description: string;
17
+ /** Semantic version */
18
+ version: string;
19
+ /** Plugin type — what surfaces it provides */
20
+ provides: PluginProvides;
21
+ /** Entry point (relative to manifest) */
22
+ entry: string;
23
+ /** Author info */
24
+ author?: string;
25
+ /** Homepage URL */
26
+ homepage?: string;
27
+ /** License */
28
+ license?: string;
29
+ /** Required runcode version (semver range) */
30
+ runcodeVersion?: string;
31
+ }
32
+ export interface PluginProvides {
33
+ /** This plugin contributes one or more workflows (e.g. "social", "trading") */
34
+ workflows?: string[];
35
+ /** This plugin contributes channels (e.g. "reddit", "x", "telegram") */
36
+ channels?: string[];
37
+ /** This plugin contributes CLI commands */
38
+ commands?: string[];
39
+ }
40
+ /** Plugin entry point — exported as default from plugin's entry file */
41
+ export interface Plugin {
42
+ /** Manifest (loaded from plugin.json — plugin doesn't need to repeat it) */
43
+ manifest: PluginManifest;
44
+ /** Workflows this plugin provides (mapped by workflow id) */
45
+ workflows?: Record<string, () => Workflow>;
46
+ /** Channels this plugin provides (mapped by channel id) */
47
+ channels?: Record<string, () => Channel>;
48
+ /** Custom CLI commands */
49
+ commands?: PluginCommand[];
50
+ /** Called once when plugin is loaded (optional) */
51
+ onLoad?: (ctx: PluginContext) => void | Promise<void>;
52
+ /** Called when plugin is unloaded (optional) */
53
+ onUnload?: () => void | Promise<void>;
54
+ }
55
+ /** Context passed to plugin lifecycle hooks */
56
+ export interface PluginContext {
57
+ /** RunCode version */
58
+ runcodeVersion: string;
59
+ /** Plugin's own data directory (~/.blockrun/plugins/<id>/) */
60
+ dataDir: string;
61
+ /** Path to plugin's installation directory */
62
+ pluginDir: string;
63
+ /** Logger */
64
+ log: (message: string) => void;
65
+ }
66
+ /** A CLI command contributed by a plugin */
67
+ export interface PluginCommand {
68
+ /** Command name (e.g. "init", "run", "stats") */
69
+ name: string;
70
+ /** Description shown in help */
71
+ description: string;
72
+ /** Optional flags */
73
+ options?: Array<{
74
+ flag: string;
75
+ description: string;
76
+ }>;
77
+ /** Handler — receives parsed args and plugin context */
78
+ handler: PluginCommandHandler;
79
+ }
80
+ export type PluginCommandHandler = (args: {
81
+ /** Positional arguments */
82
+ positional: string[];
83
+ /** Parsed flags */
84
+ flags: Record<string, string | boolean>;
85
+ /** Plugin context */
86
+ ctx: PluginContext;
87
+ }) => Promise<void> | void;
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Plugin contract — what every plugin must export.
3
+ *
4
+ * Plugins are discovered by their manifest file (plugin.json) and loaded
5
+ * dynamically. Core code never references plugins by name.
6
+ */
7
+ export {};
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Search result type — used by both web search (Exa/WebSearch) and channels.
3
+ */
4
+ export interface SearchResult {
5
+ title: string;
6
+ url: string;
7
+ snippet: string;
8
+ source: string;
9
+ author?: string;
10
+ timestamp?: string;
11
+ score?: number;
12
+ commentCount?: number;
13
+ }
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Search result type — used by both web search (Exa/WebSearch) and channels.
3
+ */
4
+ export {};
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Tracker contract — workflows record actions for dedup, stats, and history.
3
+ * Core provides a default implementation; plugins use it via WorkflowStepContext.
4
+ */
5
+ export interface TrackedAction {
6
+ workflow: string;
7
+ action: string;
8
+ key: string;
9
+ metadata: Record<string, unknown>;
10
+ costUsd: number;
11
+ createdAt: string;
12
+ }
13
+ export interface WorkflowStats {
14
+ totalRuns: number;
15
+ totalActions: number;
16
+ totalCostUsd: number;
17
+ todayActions: number;
18
+ todayCostUsd: number;
19
+ lastRun?: string;
20
+ byAction: Record<string, number>;
21
+ }
22
+ export interface WorkflowTracker {
23
+ trackAction(action: string, key: string, metadata: Record<string, unknown>, costUsd?: number): void;
24
+ isDuplicate(key: string): boolean;
25
+ getStats(): WorkflowStats;
26
+ getByAction(action: string): TrackedAction[];
27
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Tracker contract — workflows record actions for dedup, stats, and history.
3
+ * Core provides a default implementation; plugins use it via WorkflowStepContext.
4
+ */
5
+ export {};
@@ -0,0 +1,126 @@
1
+ /**
2
+ * Workflow contract — public surface for plugins implementing workflows.
3
+ *
4
+ * A workflow is a multi-step AI process: search → filter → generate → execute → track.
5
+ * Plugins implement Workflow; core orchestrates execution and provides infrastructure.
6
+ */
7
+ import type { SearchResult } from './search.js';
8
+ import type { ChannelMessage } from './channel.js';
9
+ /** Model selection tier — workflows pick tier per step, core resolves to actual model */
10
+ export type ModelTier = 'free' | 'cheap' | 'premium' | 'none';
11
+ /** Maps tier names to actual model identifiers */
12
+ export interface ModelTierConfig {
13
+ free: string;
14
+ cheap: string;
15
+ premium: string;
16
+ }
17
+ export declare const DEFAULT_MODEL_TIERS: ModelTierConfig;
18
+ /** Context provided to each workflow step by core */
19
+ export interface WorkflowStepContext {
20
+ /** Accumulated data from previous steps (mutable across steps) */
21
+ data: Record<string, unknown>;
22
+ /** Call an LLM at the specified tier */
23
+ callModel: (tier: ModelTier, prompt: string, system?: string) => Promise<string>;
24
+ /** Generate an image (DALL-E / Flux) */
25
+ generateImage?: (prompt: string) => Promise<string>;
26
+ /** Search the web (Exa neural / WebSearch fallback) */
27
+ search: (query: string, options?: {
28
+ sources?: string[];
29
+ maxResults?: number;
30
+ }) => Promise<SearchResult[]>;
31
+ /** Send a message via a channel (e.g. reddit, x, telegram) */
32
+ sendMessage?: (channelId: string, message: ChannelMessage) => Promise<void>;
33
+ /** Log progress (visible to user) */
34
+ log: (message: string) => void;
35
+ /** Track an action in the workflow's database */
36
+ track: (action: string, metadata: Record<string, unknown>) => Promise<void>;
37
+ /** Check if a key was already processed (dedup) */
38
+ isDuplicate: (key: string) => Promise<boolean>;
39
+ /** Dry-run mode — skip side effects */
40
+ dryRun: boolean;
41
+ /** Workflow config (typed by the workflow) */
42
+ config: WorkflowConfig;
43
+ }
44
+ /** Result of executing one step */
45
+ export interface WorkflowStepResult {
46
+ /** Data to merge into shared context for subsequent steps */
47
+ data?: Record<string, unknown>;
48
+ /** Human-readable summary of what this step did */
49
+ summary?: string;
50
+ /** If true, abort the workflow */
51
+ abort?: boolean;
52
+ /** Cost of this step in USD */
53
+ cost?: number;
54
+ }
55
+ /** A single step in a workflow */
56
+ export interface WorkflowStep {
57
+ /** Step name (used for tracking and display) */
58
+ name: string;
59
+ /** Which model tier this step uses (or 'none', or 'dynamic' for runtime decision) */
60
+ modelTier: ModelTier | 'dynamic';
61
+ /** Execute this step */
62
+ execute: (ctx: WorkflowStepContext) => Promise<WorkflowStepResult>;
63
+ /** Skip this step in dry-run mode (e.g. posting) */
64
+ skipInDryRun?: boolean;
65
+ }
66
+ /** Base workflow config — workflows extend this with their own fields */
67
+ export interface WorkflowConfig {
68
+ /** Workflow id (matches plugin id) */
69
+ name: string;
70
+ /** Model tier mapping */
71
+ models: ModelTierConfig;
72
+ /** Optional schedule */
73
+ schedule?: {
74
+ cron?: string;
75
+ dailyTime?: string;
76
+ budgetCapUsd?: number;
77
+ };
78
+ /** Allow workflow-specific fields */
79
+ [key: string]: unknown;
80
+ }
81
+ export interface WorkflowResult {
82
+ steps: Array<{
83
+ name: string;
84
+ summary: string;
85
+ cost: number;
86
+ status?: WorkflowStepStatus;
87
+ }>;
88
+ totalCost: number;
89
+ itemsProcessed: number;
90
+ durationMs: number;
91
+ dryRun: boolean;
92
+ }
93
+ /** Normalized status for step rendering/reporting */
94
+ export type WorkflowStepStatus = 'ok' | 'error' | 'aborted' | 'skipped';
95
+ /**
96
+ * The Workflow interface plugins implement.
97
+ * Core's WorkflowRunner orchestrates execution of any Workflow.
98
+ */
99
+ export interface Workflow {
100
+ /** Workflow id (e.g. "social", "trading") */
101
+ readonly id: string;
102
+ /** Display name */
103
+ readonly name: string;
104
+ /** Short description */
105
+ readonly description: string;
106
+ /** Steps in execution order */
107
+ readonly steps: WorkflowStep[];
108
+ /** Default config (used when not yet configured) */
109
+ defaultConfig(): WorkflowConfig;
110
+ /** Onboarding questions for first-time setup */
111
+ readonly onboardingQuestions: OnboardingQuestion[];
112
+ /** Build config from onboarding answers (may call cheap LLM to auto-generate) */
113
+ buildConfigFromAnswers(answers: Record<string, string>, llm: (prompt: string) => Promise<string>): Promise<WorkflowConfig>;
114
+ /** Optional: lifecycle hook before workflow runs (e.g. to load state) */
115
+ beforeRun?(config: WorkflowConfig): Promise<void>;
116
+ /** Optional: lifecycle hook after workflow runs */
117
+ afterRun?(result: WorkflowResult): Promise<void>;
118
+ }
119
+ /** Question for interactive onboarding */
120
+ export interface OnboardingQuestion {
121
+ id: string;
122
+ prompt: string;
123
+ type: 'text' | 'select' | 'multi-select';
124
+ options?: string[];
125
+ default?: string;
126
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Workflow contract — public surface for plugins implementing workflows.
3
+ *
4
+ * A workflow is a multi-step AI process: search → filter → generate → execute → track.
5
+ * Plugins implement Workflow; core orchestrates execution and provides infrastructure.
6
+ */
7
+ export const DEFAULT_MODEL_TIERS = {
8
+ free: 'nvidia/nemotron-ultra-253b',
9
+ cheap: 'zai/glm-5.1',
10
+ premium: 'anthropic/claude-sonnet-4.6',
11
+ };
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Plugin Registry — discovers, loads, and manages plugins.
3
+ *
4
+ * Core stays plugin-agnostic: it knows about the *interface*, not specific plugins.
5
+ * Plugins are discovered from:
6
+ * 1. Bundled: <runcode>/plugins-bundled/* (ships with runcode)
7
+ * 2. User: ~/.blockrun/plugins/* (installed via `runcode plugin install`)
8
+ * 3. Local dev: $RUNCODE_PLUGINS_DIR/* (env var for development)
9
+ */
10
+ import type { Plugin, PluginManifest } from '../plugin-sdk/plugin.js';
11
+ export declare function getBundledPluginsDir(): string;
12
+ export declare function getUserPluginsDir(): string;
13
+ interface LoadedPlugin {
14
+ manifest: PluginManifest;
15
+ pluginDir: string;
16
+ plugin: Plugin;
17
+ }
18
+ /** Find all plugin manifests across discovery paths */
19
+ export declare function discoverPluginManifests(): Array<{
20
+ manifest: PluginManifest;
21
+ dir: string;
22
+ }>;
23
+ /** Load a single plugin from its directory */
24
+ export declare function loadPlugin(manifest: PluginManifest, pluginDir: string): Promise<Plugin | null>;
25
+ /** Discover and load all plugins. Returns the loaded registry. */
26
+ export declare function loadAllPlugins(): Promise<Map<string, LoadedPlugin>>;
27
+ export declare function getPlugin(id: string): LoadedPlugin | undefined;
28
+ export declare function listPlugins(): LoadedPlugin[];
29
+ /** Get all plugins that provide workflows */
30
+ export declare function listWorkflowPlugins(): LoadedPlugin[];
31
+ /** Get all plugins that provide channels */
32
+ export declare function listChannelPlugins(): LoadedPlugin[];
33
+ export {};