agentgui 1.0.994 → 1.0.996

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.
package/AGENTS.md CHANGED
@@ -116,8 +116,7 @@ When `PASSWORD` env var is set, every HTTP route is gated by `lib/http-handler.j
116
116
  - `site/app/js/app.js` — webjsx view + state; renders the `AgentChat` kit (from `anentrypoint-design`) for the chat surface and wires agentgui's WS/ccsniff state as kit callbacks; history/settings remain agentgui-local. Exposes `window.__agentgui`
117
117
 
118
118
  The chat GUI lives in the design kit, not in agentgui. The reusable multi-agent chat surface is the `AgentChat` component in `anentrypoint-design` (`src/components/agent-chat.js`); agentgui keeps only the transport glue (WS `backend.js`, ccsniff history wiring, agent orchestration) and passes state + callbacks into the kit. To change chat UI, edit the kit and push it (CI publishes to npm -> unpkg `@latest`), not agentgui.
119
- - `server.js` — boots ACP/agents/websocket plugins, mounts `createHistoryRouter()` from `ccsniff` at `/`, serves `site/app/` as static root
120
- - Plugins kept (lib/plugins/): acp, agents, database, files, stream, websocket, workflow
119
+ - `server.js` — initializes the ACP-SDK manager + agent registry, registers WS handlers (`ws-handlers-util.js`), mounts `createHistoryRouter()` from `ccsniff` at `/`, serves `site/app/` as static root. The `lib/plugins/` system was removed (2026-06-20, 20th run) — it was dead scaffolding (routes 404, wsHandlers never wired, api unconsumed); all server logic lives directly in `server.js` + `lib/ws-handlers-util.js` + `lib/http-handler.js` + `lib/routes-upload.js` + `database.js`.
121
120
 
122
121
  Dependencies:
123
122
  - `ccsniff` (>=1.1.0) — exports `createHistoryRouter({projectsDir})` mountable on Express; serves `/v1/history/{sessions,sessions/:sid/events,search,snapshot,reindex,stream}`. Reads `~/.claude/projects` (override via `CLAUDE_PROJECTS_DIR`).
@@ -157,15 +156,9 @@ Why it works: `process.execPath` becomes bun, so the spawned child server also r
157
156
 
158
157
  Rule: any CI step that spawns the agentgui server (directly or via a script that inherits `process.execPath`) must invoke it with `bun`.
159
158
 
160
- ## Plugin Dependencies
159
+ ## Plugin system removed (2026-06-20 — 20th run)
161
160
 
162
- **`lib/plugins/stream-plugin.js` must not import packages missing from package.json.**
163
-
164
- The plugin loader runs at startup and logs failures silently if a plugin import fails. If stream plugin fails to load due to missing dependency (e.g., `uuid`), the error cascades: `agents` and `websocket` plugins both declare `stream` as a dependency and fail with "Plugin stream not found in registry", cascading to boot failure.
165
-
166
- Fix for uuid: replaced `import { v4 as uuidv4 } from 'uuid'` with `import { randomUUID as uuidv4 } from 'crypto'` (Node.js built-in, zero deps).
167
-
168
- Pattern: all imports in lib/plugins/* must be either built-in (crypto, fs, path, etc.) or already in package.json. No new npm packages should be added to plugins without adding them to dependencies.
161
+ The entire `lib/plugins/` system + `plugin-loader.js` was deleted as dead scaffolding (commits up to e73ed0e): plugin routes were unreachable (http-handler forwarded only a few prefixes -> 404), plugin `wsHandlers` were never wired (the real WS handlers come from `registerWsHandlers` in `ws-handlers-util.js`), plugin `api` objects were never consumed, and the inits were no-ops or managed local Maps nobody read. Removed across 7 commits (acp, files, agents, then database/stream/workflow/websocket + loader). The general lesson survives: any import under `lib/` must be a built-in (crypto, fs, path) or already in package.json — a missing dep crashes boot.
169
162
 
170
163
  ## Plugin Tool Provisioning on Windows
171
164
 
package/README.md CHANGED
@@ -133,14 +133,12 @@ lib/ws-optimizer.js Per-client priority queue for WS event batching
133
133
  lib/ws-handlers-*.js WebSocket RPC handlers (conv, msg, queue, session, run, util, oauth, scripts)
134
134
  lib/speech.js Speech-to-text and text-to-speech via @huggingface/transformers
135
135
  lib/tool-manager.js Tool detection, install, update facade
136
- lib/plugins/ Server plugins (acp, agents, auth, database, files, git, speech, stream, tools, websocket, workflow)
137
- static/index.html Main HTML shell
138
- static/app.js App initialization
139
- static/js/client.js Main client logic
140
- static/js/conversations.js Conversation management
141
- static/js/streaming-renderer.js Renders agent streaming events as HTML
142
- static/js/websocket-manager.js WebSocket connection handling
143
- static/js/*.machine.js XState v5 state machines (ws, conv, tool-install, voice, conv-list, prompt)
136
+ lib/ws-handlers-util.js WS RPC handler registration (agents.list, chat.sendMessage, ...)
137
+ lib/http-handler.js Static serving + /api/* routes + auth gate + path confinement
138
+ lib/routes-upload.js Express app for multipart upload + fsbrowse file serving
139
+ site/app/index.html SPA shell; imports the anentrypoint-design kit
140
+ site/app/js/app.js webjsx view + state; renders the AgentChat kit, wires WS callbacks
141
+ site/app/js/backend.js Same-origin WS/HTTP client (transport glue)
144
142
  ```
145
143
 
146
144
  ### Key Details
@@ -148,7 +146,7 @@ static/js/*.machine.js XState v5 state machines (ws, conv, tool-instal
148
146
  - Agent discovery scans PATH for known CLI binaries at startup
149
147
  - Database lives at `~/.gmgui/data.db` (WAL mode for concurrent access)
150
148
  - WebSocket endpoint at `/gm/sync` for real-time updates
151
- - ACP tools (OpenCode, Kilo) auto-launch as HTTP servers on startup with health checks
149
+ - ACP tools (OpenCode, Kilo, Codex) start on demand as HTTP servers with health checks + restart backoff
152
150
 
153
151
  ## Use Cases
154
152
 
@@ -1,7 +1,6 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
3
  import os from 'os';
4
- import PluginLoader from './plugin-loader.js';
5
4
 
6
5
  export function createAutoImport({ queries, broadcastSync }) {
7
6
  const importMtimeCache = new Map();
@@ -57,28 +56,3 @@ export function createDbRecovery({ queries, debugLog }) {
57
56
 
58
57
  return { performDbRecovery };
59
58
  }
60
-
61
- export function createPluginLoader({ pluginsDir, expressApp, BASE_URL }) {
62
- const pluginLoader = new PluginLoader(pluginsDir);
63
-
64
- async function loadPluginExtensions() {
65
- try {
66
- await pluginLoader.loadAllPlugins({ router: expressApp, baseUrl: BASE_URL, logger: console, env: process.env });
67
- const names = Array.from(pluginLoader.registry.keys());
68
- if (names.length > 0) {
69
- for (const name of names) {
70
- const state = pluginLoader.get(name);
71
- if (!state || !state.routes) continue;
72
- for (const route of state.routes) {
73
- const fullPath = BASE_URL + route.path;
74
- const method = (route.method || 'GET').toLowerCase();
75
- if (expressApp[method]) expressApp[method](fullPath, route.handler);
76
- }
77
- }
78
- console.log(`[PLUGINS] Loaded extensions: ${names.join(', ')}`);
79
- }
80
- } catch (err) { console.error('[PLUGINS] Extension loading failed (non-fatal):', err.message); }
81
- }
82
-
83
- return { loadPluginExtensions };
84
- }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentgui",
3
- "version": "1.0.994",
3
+ "version": "1.0.996",
4
4
  "description": "Multi-agent ACP client with real-time communication",
5
5
  "type": "module",
6
6
  "main": "electron/main.js",
package/server.js CHANGED
@@ -19,7 +19,7 @@ import { parseBody, acceptsEncoding, compressAndSend, sendJSON } from './lib/htt
19
19
  import { createWsSetup } from './lib/ws-setup.js';
20
20
  import { createHttpHandler } from './lib/http-handler.js';
21
21
  import { createOnServerReady } from './lib/server-startup.js';
22
- import { createAutoImport, createDbRecovery, createPluginLoader } from './lib/server-startup2.js';
22
+ import { createAutoImport, createDbRecovery } from './lib/server-startup2.js';
23
23
  const sendWs = (ws, obj) => { if (ws.readyState === 1) ws.send(wsEncode(obj)); };
24
24
  import { startAll as startACPTools, stopAll as stopACPTools, getStatus as getACPStatus, getPort as getACPPort, ensureRunning, queryModels as queryACPModels, touch as touchACP } from './lib/acp-sdk-manager.js';
25
25
  import * as execMachine from './lib/execution-machine.js';
@@ -195,7 +195,6 @@ const onServerListenStart = () => {
195
195
  if (_serverReadyFired) return;
196
196
  _serverReadyFired = true;
197
197
  onServerReady();
198
- loadPluginExtensions();
199
198
  };
200
199
 
201
200
  let _portRetries = 0;
@@ -218,7 +217,6 @@ server.on('error', (err) => {
218
217
 
219
218
  const { performAutoImport } = createAutoImport({ queries, broadcastSync });
220
219
  const { performDbRecovery } = createDbRecovery({ queries, debugLog });
221
- const { loadPluginExtensions } = createPluginLoader({ pluginsDir: path.join(__dirname, 'lib', 'plugins'), expressApp, BASE_URL });
222
220
 
223
221
  setInterval(performDbRecovery, 300000);
224
222
 
package/test.js CHANGED
@@ -76,9 +76,7 @@ await ok('machines: execution + acp-server lifecycle', () => {
76
76
  assert.equal(asm.isHealthy('test-tool'), true);
77
77
  asm.stopAll();
78
78
  });
79
- await ok('workflow-plugin + agent-registry hermes', async () => {
80
- const wp = await import('./lib/plugins/workflow-plugin.js');
81
- assert.deepEqual(wp.default.dependencies, ['database']);
79
+ await ok('agent-registry: hermes ACP descriptor', async () => {
82
80
  const { registry } = await import('./lib/claude-runner-agents.js');
83
81
  const h = registry.get('hermes');
84
82
  assert.equal(h.protocol, 'acp'); assert.deepEqual(h.buildArgs(), ['acp']);
@@ -1,162 +0,0 @@
1
- import fs from 'fs';
2
- import path from 'path';
3
- import { EventEmitter } from 'events';
4
-
5
- class PluginLoader extends EventEmitter {
6
- constructor(pluginDir) {
7
- super();
8
- this.pluginDir = pluginDir;
9
- this.registry = new Map();
10
- this.instances = new Map();
11
- this.states = new Map();
12
- this.watchers = new Map();
13
- this.errorCounts = new Map();
14
- this.fileMap = new Map();
15
- }
16
-
17
- async loadPlugin(fileName) {
18
- const filePath = path.join(this.pluginDir, `${fileName}.js`);
19
- if (!fs.existsSync(filePath)) {
20
- throw new Error(`Plugin file not found: ${filePath}`);
21
- }
22
- const fileUrl = `file://${filePath}?v=${Date.now()}`;
23
- try {
24
- const plugin = await import(fileUrl);
25
- const mod = plugin.default || plugin;
26
- const regName = mod.name || fileName;
27
- this.registry.set(regName, mod);
28
- this.fileMap.set(regName, fileName);
29
- return mod;
30
- } catch (error) {
31
- console.error(`Failed to load plugin ${fileName}:`, error.message);
32
- throw error;
33
- }
34
- }
35
-
36
- async initializePlugin(name, config) {
37
- const plugin = this.registry.get(name);
38
- if (!plugin) {
39
- throw new Error(`Plugin ${name} not found in registry`);
40
- }
41
- if (this.instances.has(name)) {
42
- return this.instances.get(name);
43
- }
44
- for (const depName of (plugin.dependencies || [])) {
45
- if (!this.instances.has(depName)) {
46
- await this.initializePlugin(depName, config);
47
- }
48
- }
49
- try {
50
- const result = await plugin.init(config, this.instances);
51
- this.instances.set(name, result);
52
- return result;
53
- } catch (error) {
54
- console.error(`[PluginLoader] Error initializing ${name}:`, error.message);
55
- throw error;
56
- }
57
- }
58
-
59
- get(name) {
60
- return this.instances.get(name);
61
- }
62
-
63
- async reloadPlugin(name) {
64
- const plugin = this.registry.get(name);
65
- if (!plugin) {
66
- console.warn(`[PluginLoader] Cannot reload ${name}: not found`);
67
- return;
68
- }
69
- const state = this.instances.get(name);
70
- if (!state) {
71
- console.warn(`[PluginLoader] Cannot reload ${name}: not initialized`);
72
- return;
73
- }
74
- try {
75
- if (state.stop) await state.stop();
76
- const fileName = this.fileMap.get(name) || name;
77
- await this.loadPlugin(fileName);
78
- const reloadedPlugin = this.registry.get(name);
79
- const newState = await reloadedPlugin.reload(state);
80
- this.instances.set(name, newState);
81
- this.emit('reload', { name, success: true });
82
- console.log(`[PluginLoader] Reloaded plugin: ${name}`);
83
- } catch (error) {
84
- console.error(`[PluginLoader] Error reloading ${name}:`, error.message);
85
- this.emit('reload', { name, success: false, error: error.message });
86
- }
87
- }
88
-
89
- watchPlugin(name, callback) {
90
- const fileName = this.fileMap.get(name) || name;
91
- const filePath = path.join(this.pluginDir, `${fileName}.js`);
92
- if (this.watchers.has(name)) return;
93
- const watcher = fs.watch(filePath, async (eventType) => {
94
- if (eventType === 'change') {
95
- setTimeout(() => callback(name), 100);
96
- }
97
- });
98
- this.watchers.set(name, watcher);
99
- }
100
-
101
- unwatchPlugin(name) {
102
- const watcher = this.watchers.get(name);
103
- if (watcher) {
104
- watcher.close();
105
- this.watchers.delete(name);
106
- }
107
- }
108
-
109
- async loadAllPlugins(config) {
110
- if (!fs.existsSync(this.pluginDir)) {
111
- fs.mkdirSync(this.pluginDir, { recursive: true });
112
- return;
113
- }
114
- const files = fs.readdirSync(this.pluginDir).filter(f => f.endsWith('.js'));
115
- for (const file of files) {
116
- const fileName = file.replace('.js', '');
117
- try {
118
- await this.loadPlugin(fileName);
119
- } catch (error) {
120
- console.error(`[PluginLoader] Failed to load ${fileName}:`, error.message);
121
- }
122
- }
123
- const sorted = this.topologicalSort();
124
- for (const name of sorted) {
125
- try {
126
- await this.initializePlugin(name, config);
127
- } catch (error) {
128
- console.error(`[PluginLoader] Failed to initialize ${name}:`, error.message);
129
- }
130
- }
131
- }
132
-
133
- topologicalSort() {
134
- const visited = new Set(), result = [];
135
- const visit = (name) => {
136
- if (visited.has(name)) return;
137
- visited.add(name);
138
- for (const dep of (this.registry.get(name)?.dependencies || [])) { if (this.registry.has(dep)) visit(dep); }
139
- result.push(name);
140
- };
141
- for (const name of this.registry.keys()) visit(name);
142
- return result;
143
- }
144
-
145
- async shutdown() {
146
- const sorted = this.topologicalSort().reverse();
147
- for (const name of sorted) {
148
- const state = this.instances.get(name);
149
- if (state && state.stop) {
150
- try {
151
- await state.stop();
152
- } catch (error) {
153
- console.error(`[PluginLoader] Error stopping ${name}:`, error.message);
154
- }
155
- }
156
- this.unwatchPlugin(name);
157
- }
158
- this.instances.clear();
159
- this.registry.clear();
160
- }
161
- }
162
- export default PluginLoader;
@@ -1,43 +0,0 @@
1
- // Database plugin - SQLite init, schema
2
-
3
- import * as dbModule from '../../database.js';
4
-
5
- export default {
6
- name: 'database',
7
- version: '1.0.0',
8
- dependencies: [],
9
-
10
- async init(config, plugins) {
11
- // Initialize database schema
12
- if (dbModule.initializeSchema) dbModule.initializeSchema();
13
-
14
- // Return API for other plugins
15
- return {
16
- routes: [],
17
- wsHandlers: {},
18
- api: {
19
- // Query functions from database.js
20
- queries: dbModule.queries || {},
21
-
22
- // Checkpoint/recovery
23
- checkpoint: (label) => {
24
- console.log(`[Database] Checkpoint: ${label}`);
25
- },
26
-
27
- recover: async (label) => {
28
- console.log(`[Database] Recover from: ${label}`);
29
- },
30
-
31
- // Direct DB access
32
- db: dbModule.dataDir,
33
- },
34
- stop: async () => {},
35
- };
36
- },
37
-
38
- async reload(state) {
39
- return state;
40
- },
41
-
42
- async stop() {},
43
- };
@@ -1,89 +0,0 @@
1
- // Stream plugin - session management, streaming execution, rate limiting
2
-
3
- import { randomUUID as uuidv4 } from 'crypto';
4
-
5
- export default {
6
- name: 'stream',
7
- version: '1.0.0',
8
- dependencies: ['database'],
9
-
10
- async init(config, plugins) {
11
- const dbPlugin = plugins.get('database');
12
- const db = dbPlugin?.api || {};
13
- const activeSessions = new Map();
14
- const pendingMessages = new Map();
15
- const rateLimitState = new Map();
16
- const recoveryCheckpoints = new Map();
17
-
18
- return {
19
- routes: [
20
- {
21
- method: 'GET',
22
- path: '/api/sessions/:id',
23
- handler: async (req, res) => {
24
- const { id } = req.params;
25
- const session = activeSessions.get(id);
26
- if (!session) return res.status(404).json({ error: 'Session not found' });
27
- res.json(session);
28
- },
29
- },
30
- {
31
- method: 'GET',
32
- path: '/api/sessions/:id/chunks',
33
- handler: async (req, res) => {
34
- const { id } = req.params;
35
- const { since } = req.query;
36
- const chunks = db.queries.getStreamChunks(id, since ? parseInt(since) : 0);
37
- res.json({ chunks });
38
- },
39
- },
40
- {
41
- method: 'GET',
42
- path: '/api/sessions/:id/execution',
43
- handler: async (req, res) => {
44
- const { id } = req.params;
45
- const { limit, offset, filterType } = req.query;
46
- const events = db.queries.getExecutionEvents(id, parseInt(limit) || 100, parseInt(offset) || 0);
47
- res.json({ events });
48
- },
49
- },
50
- {
51
- method: 'GET',
52
- path: '/api/conversations/:id/sessions/latest',
53
- handler: async (req, res) => {
54
- const { id } = req.params;
55
- const sessions = Array.from(activeSessions.values()).filter(s => s.conversationId === id);
56
- const latest = sessions[sessions.length - 1];
57
- res.json(latest || { error: 'No sessions' });
58
- },
59
- },
60
- ],
61
- wsHandlers: {
62
- streaming_start: (data, clients) => {},
63
- streaming_progress: (data, clients) => {},
64
- streaming_complete: (data, clients) => {},
65
- streaming_error: (data, clients) => {},
66
- rate_limit_hit: (data, clients) => {},
67
- },
68
- api: {
69
- createSession: (conversationId) => {
70
- const session = { id: uuidv4(), conversationId, createdAt: Date.now() };
71
- activeSessions.set(session.id, session);
72
- return session;
73
- },
74
- getSession: (id) => activeSessions.get(id),
75
- closeSession: (id) => activeSessions.delete(id),
76
- },
77
- stop: async () => {
78
- activeSessions.clear();
79
- pendingMessages.clear();
80
- },
81
- };
82
- },
83
-
84
- async reload(state) {
85
- return state;
86
- },
87
-
88
- async stop() {},
89
- };
@@ -1,58 +0,0 @@
1
- // WebSocket plugin - message routing, optimization, real-time sync
2
-
3
- import { WebSocketServer } from 'ws';
4
-
5
- export default {
6
- name: 'websocket',
7
- version: '1.0.0',
8
- dependencies: ['database', 'stream'],
9
-
10
- async init(config, plugins) {
11
- const subscribers = new Map(); // sessionId/conversationId => Set<client>
12
- const routingTable = new Map(); // eventType => handler
13
-
14
- const broadcast = (eventType, data) => {
15
- // Broadcast to all subscribed clients
16
- };
17
-
18
- const subscribe = (client, id, type) => {
19
- if (!subscribers.has(id)) {
20
- subscribers.set(id, new Set());
21
- }
22
- subscribers.get(id).add(client);
23
- };
24
-
25
- const unsubscribe = (client, id) => {
26
- const set = subscribers.get(id);
27
- if (set) set.delete(client);
28
- };
29
-
30
- return {
31
- routes: [],
32
- wsHandlers: {
33
- // Routing handlers for all conversation/session events
34
- subscribe: (data, clients) => {},
35
- unsubscribe: (data, clients) => {},
36
- ping: (data, clients) => {},
37
- },
38
- api: {
39
- broadcast,
40
- subscribe,
41
- unsubscribe,
42
- addHandler: (eventType, handler) => {
43
- routingTable.set(eventType, handler);
44
- },
45
- },
46
- stop: async () => {
47
- subscribers.clear();
48
- routingTable.clear();
49
- },
50
- };
51
- },
52
-
53
- async reload(state) {
54
- return state;
55
- },
56
-
57
- async stop() {},
58
- };
@@ -1,104 +0,0 @@
1
- import fs from 'fs';
2
- import path from 'path';
3
-
4
- export default {
5
- name: 'workflow',
6
- version: '1.0.0',
7
- dependencies: ['database'],
8
-
9
- async init(config, plugins) {
10
- plugins.get('database');
11
- const workflowPolls = new Map();
12
-
13
- const getWorkflows = () => {
14
- const repoRoot = process.cwd();
15
- const workflowDir = path.join(repoRoot, '.github', 'workflows');
16
- if (!fs.existsSync(workflowDir)) return [];
17
- return fs.readdirSync(workflowDir)
18
- .filter(f => f.endsWith('.yml') || f.endsWith('.yaml'))
19
- .map(name => ({ name, path: path.join(workflowDir, name) }));
20
- };
21
-
22
- const resolvedWorkflowDir = path.resolve(process.cwd(), '.github', 'workflows');
23
-
24
- const isNameSafe = (name) => !/[/\\]/.test(name);
25
-
26
- const parseWorkflow = (filePath) => {
27
- try {
28
- // Confine to workflowDir: resolve and verify the path stays within the allowed directory
29
- const resolved = path.resolve(filePath);
30
- if (!resolved.startsWith(resolvedWorkflowDir + path.sep) && resolved !== resolvedWorkflowDir) {
31
- return null;
32
- }
33
- const content = fs.readFileSync(resolved, 'utf8');
34
- // Parse YAML manually or return raw content
35
- return { name: path.basename(resolved), content };
36
- } catch {
37
- return null;
38
- }
39
- };
40
-
41
- return {
42
- routes: [
43
- {
44
- method: 'GET',
45
- path: '/api/workflows',
46
- handler: (req, res) => {
47
- const workflows = getWorkflows();
48
- res.json({ workflows });
49
- },
50
- },
51
- {
52
- method: 'GET',
53
- path: '/api/workflows/:name/history',
54
- handler: (req, res) => {
55
- if (!isNameSafe(req.params.name)) {
56
- return res.status(400).json({ error: 'Invalid workflow name' });
57
- }
58
- res.json({ history: [] });
59
- },
60
- },
61
- {
62
- method: 'POST',
63
- path: '/api/workflows/:name/trigger',
64
- handler: async (req, res) => {
65
- if (!isNameSafe(req.params.name)) {
66
- return res.status(400).json({ error: 'Invalid workflow name' });
67
- }
68
- res.json({ success: true, message: 'Workflow trigger requires GitHub API' });
69
- },
70
- },
71
- {
72
- method: 'GET',
73
- path: '/api/workflows/:name/status',
74
- handler: (req, res) => {
75
- if (!isNameSafe(req.params.name)) {
76
- return res.status(400).json({ error: 'Invalid workflow name' });
77
- }
78
- res.json({ status: 'unknown' });
79
- },
80
- },
81
- ],
82
- wsHandlers: {
83
- workflow_triggered: (data, clients) => {},
84
- workflow_progress: (data, clients) => {},
85
- workflow_complete: (data, clients) => {},
86
- },
87
- api: {
88
- getWorkflows,
89
- parseWorkflow,
90
- },
91
- stop: async () => {
92
- for (const interval of workflowPolls.values()) {
93
- clearInterval(interval);
94
- }
95
- },
96
- };
97
- },
98
-
99
- async reload(state) {
100
- return state;
101
- },
102
-
103
- async stop() {},
104
- };