flow-agent-bridge 0.2.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.
@@ -0,0 +1,45 @@
1
+ /** "~" / "~/x" → the user's home (shells don't expand ~ inside JSON configs or wizard answers). */
2
+ export declare function expandHome(p: string): string;
3
+ export type ProgressMode = 'thinking' | 'typing' | 'silent';
4
+ export type EventScope = 'mentions' | 'all';
5
+ export type RuntimeKind = 'claude' | 'codex' | 'demo';
6
+ export interface RuntimeConfig {
7
+ /**
8
+ * 'claude' (rich: sessions, stream-json thinking steps, MCP),
9
+ * 'codex' (stub: baseline stdout contract), or
10
+ * 'demo' (no CLI at all — always replies "Your message was received";
11
+ * exercises the full event→reply pipeline for local testing).
12
+ */
13
+ kind: RuntimeKind;
14
+ /** Executable override (default: the kind's CLI name). A fake runtime script works here for tests. */
15
+ command: string;
16
+ /** Extra args appended verbatim before the prompt. */
17
+ extraArgs: string[];
18
+ /** Working directory the CLI runs in — the agent's identity (a repo checkout). */
19
+ cwd: string;
20
+ /** --permission-mode passthrough (claude). Headless runs use pre-granted permissions — scope them. */
21
+ permissionMode?: string | undefined;
22
+ /** --allowedTools passthrough (claude), e.g. ["Read", "Bash(pnpm test)"]. */
23
+ allowedTools: string[];
24
+ /** --max-turns cap (claude). */
25
+ maxTurns: number;
26
+ /** Wall-clock timeout per run, seconds. */
27
+ timeoutSec: number;
28
+ /** MCP rich mode: expose the flow MCP server to the runtime (claude only). */
29
+ mcp: boolean;
30
+ /** Extra text appended to the Flow system prompt. */
31
+ systemPromptExtra?: string | undefined;
32
+ }
33
+ export interface BridgeConfig {
34
+ serverUrl: string;
35
+ agentToken: string;
36
+ runtime: RuntimeConfig;
37
+ /** Default 'mentions': @-mentions + DMs only. 'all' adds full traffic of channels the agent is in. */
38
+ eventScope: EventScope;
39
+ /** Never respond to other agents (loop safety). Default false. */
40
+ respondToAgents: boolean;
41
+ /** Max conversations processed concurrently (serial within one conversation). */
42
+ concurrency: number;
43
+ progress: ProgressMode;
44
+ }
45
+ export declare function loadConfig(configPath: string): BridgeConfig;
package/dist/config.js ADDED
@@ -0,0 +1,65 @@
1
+ // Bridge configuration (AGENTS_DESIGN.md): JSON file, one agent per config.
2
+ // (The spec allows "agent.toml or JSON"; the bridge ships JSON — no TOML
3
+ // parser in Node's stdlib, and one less dependency.)
4
+ import fs from 'node:fs';
5
+ import os from 'node:os';
6
+ import path from 'node:path';
7
+ /** "~" / "~/x" → the user's home (shells don't expand ~ inside JSON configs or wizard answers). */
8
+ export function expandHome(p) {
9
+ if (p === '~')
10
+ return os.homedir();
11
+ return p.startsWith('~/') ? path.join(os.homedir(), p.slice(2)) : p;
12
+ }
13
+ export function loadConfig(configPath) {
14
+ const abs = path.resolve(configPath);
15
+ let raw;
16
+ try {
17
+ raw = JSON.parse(fs.readFileSync(abs, 'utf8'));
18
+ }
19
+ catch (err) {
20
+ throw new Error(`could not read config ${abs}: ${err.message}`);
21
+ }
22
+ const serverUrl = (raw.serverUrl ?? process.env.FLOW_SERVER_URL ?? '').replace(/\/+$/, '');
23
+ if (!serverUrl)
24
+ throw new Error('config: serverUrl is required (or FLOW_SERVER_URL)');
25
+ const agentToken = raw.agentToken ?? process.env.FLOW_AGENT_TOKEN ?? '';
26
+ if (!agentToken)
27
+ throw new Error('config: agentToken is required (or FLOW_AGENT_TOKEN)');
28
+ const kind = (raw.runtime?.kind ?? 'claude');
29
+ if (kind !== 'claude' && kind !== 'codex' && kind !== 'demo')
30
+ throw new Error(`config: unknown runtime.kind "${kind}"`);
31
+ const r = raw.runtime ?? {};
32
+ const runtime = {
33
+ kind,
34
+ command: r.command ?? (kind === 'codex' ? 'codex' : 'claude'),
35
+ extraArgs: r.extraArgs ?? [],
36
+ cwd: path.resolve(path.dirname(abs), expandHome(r.cwd ?? '.')),
37
+ permissionMode: r.permissionMode,
38
+ allowedTools: r.allowedTools ?? [],
39
+ maxTurns: r.maxTurns ?? 25,
40
+ timeoutSec: r.timeoutSec ?? 300,
41
+ mcp: r.mcp ?? (kind === 'claude'),
42
+ systemPromptExtra: r.systemPromptExtra,
43
+ };
44
+ // A missing cwd would otherwise surface as a misleading "spawn <cli> ENOENT"
45
+ // (node reports ENOENT for a bad working directory too). Demo never spawns.
46
+ if (kind !== 'demo' && !fs.existsSync(runtime.cwd)) {
47
+ throw new Error(`config: runtime.cwd does not exist: ${runtime.cwd}`);
48
+ }
49
+ const eventScope = (raw.eventScope ?? 'mentions');
50
+ if (eventScope !== 'mentions' && eventScope !== 'all')
51
+ throw new Error(`config: bad eventScope "${eventScope}"`);
52
+ const progress = (raw.progress ?? 'thinking');
53
+ if (!['thinking', 'typing', 'silent'].includes(progress))
54
+ throw new Error(`config: bad progress "${progress}"`);
55
+ return {
56
+ serverUrl,
57
+ agentToken,
58
+ runtime,
59
+ eventScope,
60
+ respondToAgents: raw.respondToAgents ?? false,
61
+ concurrency: Math.max(1, raw.concurrency ?? 4),
62
+ progress,
63
+ };
64
+ }
65
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ import type { Event } from '@flow/shared';
2
+ export interface FlowSocketOpts {
3
+ serverUrl: string;
4
+ token: string;
5
+ onEvent(event: Event): void;
6
+ onOpen?(): void;
7
+ log(msg: string): void;
8
+ }
9
+ export declare class FlowSocket {
10
+ private readonly opts;
11
+ private ws;
12
+ private closed;
13
+ private attempt;
14
+ constructor(opts: FlowSocketOpts);
15
+ connect(): void;
16
+ private scheduleReconnect;
17
+ sendTyping(channelId: string): void;
18
+ private send;
19
+ close(): void;
20
+ }
@@ -0,0 +1,68 @@
1
+ // Agent-token WS connection — real presence: the agent shows online while
2
+ // the daemon runs. Auto-reconnects with backoff; answers heartbeat pings.
3
+ import WebSocket from 'ws';
4
+ const BACKOFF_MS = [1000, 2000, 5000, 10_000, 30_000];
5
+ export class FlowSocket {
6
+ opts;
7
+ ws = null;
8
+ closed = false;
9
+ attempt = 0;
10
+ constructor(opts) {
11
+ this.opts = opts;
12
+ }
13
+ connect() {
14
+ if (this.closed)
15
+ return;
16
+ const wsUrl = this.opts.serverUrl.replace(/^http/, 'ws') + '/v1/ws';
17
+ const ws = new WebSocket(wsUrl);
18
+ this.ws = ws;
19
+ ws.on('open', () => {
20
+ this.send({ op: 'auth', token: this.opts.token });
21
+ });
22
+ ws.on('message', (raw) => {
23
+ let frame;
24
+ try {
25
+ frame = JSON.parse(String(raw));
26
+ }
27
+ catch {
28
+ return;
29
+ }
30
+ if (frame.op === 'ping')
31
+ return this.send({ op: 'pong' });
32
+ if (frame.op === 'hello') {
33
+ this.attempt = 0;
34
+ this.opts.log('connected (presence online)');
35
+ this.opts.onOpen?.();
36
+ return;
37
+ }
38
+ if (frame.op === 'event')
39
+ return this.opts.onEvent(frame.event);
40
+ if (frame.op === 'error')
41
+ this.opts.log(`ws error: ${frame.code} ${frame.message}`);
42
+ });
43
+ ws.on('close', () => this.scheduleReconnect());
44
+ ws.on('error', (err) => this.opts.log(`ws error: ${err.message}`));
45
+ }
46
+ scheduleReconnect() {
47
+ if (this.closed)
48
+ return;
49
+ const delay = BACKOFF_MS[Math.min(this.attempt, BACKOFF_MS.length - 1)];
50
+ this.attempt += 1;
51
+ this.opts.log(`disconnected — reconnecting in ${delay / 1000}s`);
52
+ // NOT unref'd: after a disconnect this timer may be the only handle left
53
+ // on the event loop — an unref'd timer let the whole daemon exit(0) here.
54
+ setTimeout(() => this.connect(), delay);
55
+ }
56
+ sendTyping(channelId) {
57
+ this.send({ op: 'typing', channelId });
58
+ }
59
+ send(frame) {
60
+ if (this.ws?.readyState === WebSocket.OPEN)
61
+ this.ws.send(JSON.stringify(frame));
62
+ }
63
+ close() {
64
+ this.closed = true;
65
+ this.ws?.close();
66
+ }
67
+ }
68
+ //# sourceMappingURL=gateway.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gateway.js","sourceRoot":"","sources":["../src/gateway.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAC1E,0EAA0E;AAC1E,OAAO,SAAS,MAAM,IAAI,CAAC;AAW3B,MAAM,UAAU,GAAG,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;AAEtD,MAAM,OAAO,UAAU;IAKQ;IAJrB,EAAE,GAAqB,IAAI,CAAC;IAC5B,MAAM,GAAG,KAAK,CAAC;IACf,OAAO,GAAG,CAAC,CAAC;IAEpB,YAA6B,IAAoB;QAApB,SAAI,GAAJ,IAAI,CAAgB;IAAG,CAAC;IAErD,OAAO;QACL,IAAI,IAAI,CAAC,MAAM;YAAE,OAAO;QACxB,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC,GAAG,QAAQ,CAAC;QACpE,MAAM,EAAE,GAAG,IAAI,SAAS,CAAC,KAAK,CAAC,CAAC;QAChC,IAAI,CAAC,EAAE,GAAG,EAAE,CAAC;QACb,EAAE,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE;YACjB,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC;QACpD,CAAC,CAAC,CAAC;QACH,EAAE,CAAC,EAAE,CAAC,SAAS,EAAE,CAAC,GAAsB,EAAE,EAAE;YAC1C,IAAI,KAAkB,CAAC;YACvB,IAAI,CAAC;gBACH,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAgB,CAAC;YACjD,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO;YACT,CAAC;YACD,IAAI,KAAK,CAAC,EAAE,KAAK,MAAM;gBAAE,OAAO,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC;YAC1D,IAAI,KAAK,CAAC,EAAE,KAAK,OAAO,EAAE,CAAC;gBACzB,IAAI,CAAC,OAAO,GAAG,CAAC,CAAC;gBACjB,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,6BAA6B,CAAC,CAAC;gBAC7C,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC;gBACrB,OAAO;YACT,CAAC;YACD,IAAI,KAAK,CAAC,EAAE,KAAK,OAAO;gBAAE,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;YAChE,IAAI,KAAK,CAAC,EAAE,KAAK,OAAO;gBAAE,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,aAAa,KAAK,CAAC,IAAI,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;QACtF,CAAC,CAAC,CAAC;QACH,EAAE,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,iBAAiB,EAAE,CAAC,CAAC;QAC/C,EAAE,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAU,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,aAAa,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;IAC5E,CAAC;IAEO,iBAAiB;QACvB,IAAI,IAAI,CAAC,MAAM;YAAE,OAAO;QACxB,MAAM,KAAK,GAAG,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,EAAE,UAAU,CAAC,MAAM,GAAG,CAAC,CAAC,CAAE,CAAC;QACzE,IAAI,CAAC,OAAO,IAAI,CAAC,CAAC;QAClB,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,kCAAkC,KAAK,GAAG,IAAI,GAAG,CAAC,CAAC;QACjE,yEAAyE;QACzE,0EAA0E;QAC1E,UAAU,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,KAAK,CAAC,CAAC;IAC1C,CAAC;IAED,UAAU,CAAC,SAAiB;QAC1B,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC,CAAC;IACzC,CAAC;IAEO,IAAI,CAAC,KAAkB;QAC7B,IAAI,IAAI,CAAC,EAAE,EAAE,UAAU,KAAK,SAAS,CAAC,IAAI;YAAE,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC;IAClF,CAAC;IAED,KAAK;QACH,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;QACnB,IAAI,CAAC,EAAE,EAAE,KAAK,EAAE,CAAC;IACnB,CAAC;CACF"}
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,75 @@
1
+ #!/usr/bin/env node
2
+ // flow-agent-bridge CLI:
3
+ // flow-agent-bridge [config.json] the one command: if the config
4
+ // exists, run the daemon; if not,
5
+ // interactive setup (invite key →
6
+ // token exchange → save) then run.
7
+ // Default config path: ./agent.json
8
+ // flow-agent-bridge run <config.json> start the daemon (no setup)
9
+ // flow-agent-bridge register --server <url> --invite <key> [--name <name>]
10
+ // [--description <text>] [--avatar <url>]
11
+ // (--name optional: falls back to the invite's nameHint server-side)
12
+ // flow-agent-bridge mcp (internal) the flow MCP stdio server
13
+ import fs from 'node:fs';
14
+ import { loadConfig } from './config.js';
15
+ import { AgentBridge } from './bridge.js';
16
+ import { registerAgent } from './api.js';
17
+ import { runMcpServer } from './mcp-server.js';
18
+ import { runSetup } from './setup.js';
19
+ function usage() {
20
+ console.error('usage:\n' +
21
+ ' flow-agent-bridge [config.json] setup (if missing) + run — default ./agent.json\n' +
22
+ ' flow-agent-bridge run <config.json>\n' +
23
+ ' flow-agent-bridge register --server <url> --invite <key> [--name <name>] [--description <text>] [--avatar <url>]\n');
24
+ process.exit(2);
25
+ }
26
+ function flag(args, name) {
27
+ const i = args.indexOf(`--${name}`);
28
+ return i >= 0 ? args[i + 1] : undefined;
29
+ }
30
+ async function main() {
31
+ const [cmd, ...rest] = process.argv.slice(2);
32
+ if (cmd === 'mcp')
33
+ return runMcpServer();
34
+ if (cmd === 'register') {
35
+ const server = flag(rest, 'server');
36
+ const invite = flag(rest, 'invite');
37
+ const name = flag(rest, 'name');
38
+ if (!server || !invite)
39
+ usage();
40
+ const res = await registerAgent(server, {
41
+ inviteKey: invite,
42
+ ...(name ? { name } : {}),
43
+ ...(flag(rest, 'description') ? { description: flag(rest, 'description') } : {}),
44
+ ...(flag(rest, 'avatar') ? { avatarUrl: flag(rest, 'avatar') } : {}),
45
+ });
46
+ console.log(`registered ${res.user.displayName} <@${res.user.id}> in workspace "${res.workspace.name}"`);
47
+ console.log('');
48
+ console.log('agent token (shown ONCE — put it in your bridge config as "agentToken"):');
49
+ console.log(` ${res.agentToken}`);
50
+ return;
51
+ }
52
+ if (cmd === 'help' || cmd === '--help' || cmd === '-h')
53
+ usage();
54
+ // `run <config>` requires the config; a bare path (or no args → ./agent.json)
55
+ // runs interactive setup first when the file doesn't exist.
56
+ let configPath = cmd === 'run' ? rest[0] : cmd ?? 'agent.json';
57
+ if (!configPath)
58
+ usage();
59
+ if (cmd !== 'run' && !fs.existsSync(configPath))
60
+ configPath = await runSetup(configPath);
61
+ const cfg = loadConfig(configPath);
62
+ const bridge = new AgentBridge(cfg);
63
+ await bridge.start();
64
+ const shutdown = () => {
65
+ bridge.stop();
66
+ setTimeout(() => process.exit(0), 200);
67
+ };
68
+ process.on('SIGINT', shutdown);
69
+ process.on('SIGTERM', shutdown);
70
+ }
71
+ main().catch((err) => {
72
+ console.error(`fatal: ${err.message}`);
73
+ process.exit(1);
74
+ });
75
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export declare function runMcpServer(): Promise<void>;
@@ -0,0 +1,186 @@
1
+ // The `flow` MCP stdio server (rich mode, operator ruling 6): send_message,
2
+ // react, upload_file, search_history against /v1 with the agent token.
3
+ //
4
+ // Hand-rolled newline-delimited JSON-RPC (the MCP stdio transport) — the
5
+ // surface is four tools; no SDK dependency needed. Conversation context
6
+ // (channel/thread) arrives via env from the bridge's per-run --mcp-config.
7
+ import fs from 'node:fs';
8
+ import path from 'node:path';
9
+ import readline from 'node:readline';
10
+ import { FlowApi } from './api.js';
11
+ const TOOLS = [
12
+ {
13
+ name: 'send_message',
14
+ description: 'Send a message to a Flow channel or thread immediately. Defaults to the current conversation. Mention users as <@userId>.',
15
+ inputSchema: {
16
+ type: 'object',
17
+ properties: {
18
+ text: { type: 'string', description: 'Message body (markdown).' },
19
+ channelId: { type: 'string', description: 'Target channel id (default: current conversation).' },
20
+ threadRootId: { type: 'string', description: 'Thread root message id (default: current thread, if any).' },
21
+ },
22
+ required: ['text'],
23
+ },
24
+ },
25
+ {
26
+ name: 'react',
27
+ description: 'Add an emoji reaction to a message.',
28
+ inputSchema: {
29
+ type: 'object',
30
+ properties: {
31
+ messageId: { type: 'string', description: 'Message id to react to.' },
32
+ emoji: { type: 'string', description: 'A single unicode emoji, e.g. 👍' },
33
+ },
34
+ required: ['messageId', 'emoji'],
35
+ },
36
+ },
37
+ {
38
+ name: 'upload_file',
39
+ description: 'Upload a local file and post it to a Flow channel (defaults to the current conversation).',
40
+ inputSchema: {
41
+ type: 'object',
42
+ properties: {
43
+ path: { type: 'string', description: 'Path to the file on disk.' },
44
+ channelId: { type: 'string', description: 'Target channel id (default: current conversation).' },
45
+ threadRootId: { type: 'string', description: 'Thread root message id (default: current thread, if any).' },
46
+ comment: { type: 'string', description: 'Optional message text to accompany the file.' },
47
+ },
48
+ required: ['path'],
49
+ },
50
+ },
51
+ {
52
+ name: 'search_history',
53
+ description: 'Search recent messages in a Flow channel for a substring (case-insensitive).',
54
+ inputSchema: {
55
+ type: 'object',
56
+ properties: {
57
+ query: { type: 'string', description: 'Text to search for.' },
58
+ channelId: { type: 'string', description: 'Channel to search (default: current conversation).' },
59
+ limit: { type: 'number', description: 'Max matches to return (default 10).' },
60
+ },
61
+ required: ['query'],
62
+ },
63
+ },
64
+ ];
65
+ const MIME_BY_EXT = {
66
+ '.png': 'image/png',
67
+ '.jpg': 'image/jpeg',
68
+ '.jpeg': 'image/jpeg',
69
+ '.gif': 'image/gif',
70
+ '.webp': 'image/webp',
71
+ '.pdf': 'application/pdf',
72
+ '.txt': 'text/plain',
73
+ '.md': 'text/markdown',
74
+ '.json': 'application/json',
75
+ };
76
+ export async function runMcpServer() {
77
+ const serverUrl = process.env.FLOW_SERVER_URL ?? '';
78
+ const token = process.env.FLOW_AGENT_TOKEN ?? '';
79
+ const workspaceId = process.env.FLOW_WORKSPACE_ID ?? '';
80
+ const defaultChannelId = process.env.FLOW_CHANNEL_ID ?? '';
81
+ const defaultThreadRootId = process.env.FLOW_THREAD_ROOT_ID || undefined;
82
+ if (!serverUrl || !token) {
83
+ process.stderr.write('flow mcp: FLOW_SERVER_URL and FLOW_AGENT_TOKEN are required\n');
84
+ process.exit(1);
85
+ }
86
+ const api = new FlowApi(serverUrl, token);
87
+ const write = (msg) => {
88
+ process.stdout.write(`${JSON.stringify(msg)}\n`);
89
+ };
90
+ const reply = (id, result) => {
91
+ write({ jsonrpc: '2.0', id, result });
92
+ };
93
+ const replyError = (id, code, message) => {
94
+ write({ jsonrpc: '2.0', id, error: { code, message } });
95
+ };
96
+ const toolText = (text, isError = false) => ({ content: [{ type: 'text', text }], isError });
97
+ async function callTool(name, args) {
98
+ const channelId = args.channelId || defaultChannelId;
99
+ const threadRootId = args.threadRootId || defaultThreadRootId;
100
+ switch (name) {
101
+ case 'send_message': {
102
+ const msg = await api.sendMessage(channelId, String(args.text ?? ''), threadRootId);
103
+ return toolText(`sent (message id ${msg.id})`);
104
+ }
105
+ case 'react': {
106
+ await api.addReaction(String(args.messageId ?? ''), String(args.emoji ?? ''));
107
+ return toolText('reaction added');
108
+ }
109
+ case 'upload_file': {
110
+ const p = path.resolve(String(args.path ?? ''));
111
+ const data = fs.readFileSync(p);
112
+ const filename = path.basename(p);
113
+ const mime = MIME_BY_EXT[path.extname(p).toLowerCase()] ?? 'application/octet-stream';
114
+ const file = await api.uploadFile(workspaceId, filename, mime, data);
115
+ const msg = await api.sendMessage(channelId, String(args.comment ?? filename), threadRootId, [file.id]);
116
+ return toolText(`uploaded ${filename} (message id ${msg.id})`);
117
+ }
118
+ case 'search_history': {
119
+ const q = String(args.query ?? '').toLowerCase();
120
+ const limit = Math.min(Number(args.limit ?? 10), 50);
121
+ const page = await api.listMessages(channelId, 200);
122
+ const hits = page.messages
123
+ .filter((m) => !m.deletedAt && m.body.toLowerCase().includes(q))
124
+ .slice(-limit)
125
+ .map((m) => `[${m.createdAt} <@${m.userId}> msg ${m.id}] ${m.body.slice(0, 300)}`);
126
+ return toolText(hits.length ? hits.join('\n') : 'no matches');
127
+ }
128
+ default:
129
+ return toolText(`unknown tool: ${name}`, true);
130
+ }
131
+ }
132
+ const rl = readline.createInterface({ input: process.stdin, terminal: false });
133
+ rl.on('line', (line) => {
134
+ void (async () => {
135
+ const trimmed = line.trim();
136
+ if (!trimmed)
137
+ return;
138
+ let req;
139
+ try {
140
+ req = JSON.parse(trimmed);
141
+ }
142
+ catch {
143
+ return; // not for us
144
+ }
145
+ const id = req.id ?? null;
146
+ try {
147
+ switch (req.method) {
148
+ case 'initialize':
149
+ return reply(id, {
150
+ protocolVersion: req.params?.protocolVersion ?? '2025-06-18',
151
+ capabilities: { tools: {} },
152
+ serverInfo: { name: 'flow', version: '0.1.0' },
153
+ });
154
+ case 'notifications/initialized':
155
+ case 'notifications/cancelled':
156
+ return; // notifications get no response
157
+ case 'ping':
158
+ return reply(id, {});
159
+ case 'tools/list':
160
+ return reply(id, { tools: TOOLS });
161
+ case 'tools/call': {
162
+ const name = String(req.params?.name ?? '');
163
+ const args = (req.params?.arguments ?? {});
164
+ try {
165
+ return reply(id, await callTool(name, args));
166
+ }
167
+ catch (err) {
168
+ return reply(id, toolText(`error: ${err.message}`, true));
169
+ }
170
+ }
171
+ default:
172
+ if (id !== null)
173
+ replyError(id, -32601, `method not found: ${req.method}`);
174
+ return;
175
+ }
176
+ }
177
+ catch (err) {
178
+ if (id !== null)
179
+ replyError(id, -32603, err.message);
180
+ }
181
+ })();
182
+ });
183
+ // stay alive until stdin closes
184
+ await new Promise((resolve) => rl.on('close', resolve));
185
+ }
186
+ //# sourceMappingURL=mcp-server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp-server.js","sourceRoot":"","sources":["../src/mcp-server.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ import type { FlowApi } from './api.js';
2
+ import type { FlowSocket } from './gateway.js';
3
+ import type { ProgressMode } from './config.js';
4
+ export declare class ProgressReporter {
5
+ private readonly api;
6
+ private readonly socket;
7
+ private readonly mode;
8
+ private readonly channelId;
9
+ private readonly threadRootId;
10
+ private readonly log;
11
+ private typingTimer;
12
+ private statusMessageId;
13
+ private inFlight;
14
+ private pendingStep;
15
+ private finished;
16
+ constructor(api: FlowApi, socket: FlowSocket, mode: ProgressMode, channelId: string, threadRootId: string | undefined, log: (msg: string) => void);
17
+ start(): void;
18
+ /** Latest step wins; edits are serialized (a step landing mid-edit is applied after). */
19
+ onStep(step: string): void;
20
+ private drain;
21
+ /** Stop typing and delete the status message (if any). */
22
+ finish(): Promise<void>;
23
+ }
@@ -0,0 +1,78 @@
1
+ const TYPING_INTERVAL_MS = 4000;
2
+ export class ProgressReporter {
3
+ api;
4
+ socket;
5
+ mode;
6
+ channelId;
7
+ threadRootId;
8
+ log;
9
+ typingTimer = null;
10
+ statusMessageId = null;
11
+ inFlight = false;
12
+ pendingStep = null;
13
+ finished = false;
14
+ constructor(api, socket, mode, channelId, threadRootId, log) {
15
+ this.api = api;
16
+ this.socket = socket;
17
+ this.mode = mode;
18
+ this.channelId = channelId;
19
+ this.threadRootId = threadRootId;
20
+ this.log = log;
21
+ }
22
+ start() {
23
+ if (this.mode === 'silent')
24
+ return;
25
+ this.socket.sendTyping(this.channelId);
26
+ this.typingTimer = setInterval(() => this.socket.sendTyping(this.channelId), TYPING_INTERVAL_MS);
27
+ this.typingTimer.unref();
28
+ }
29
+ /** Latest step wins; edits are serialized (a step landing mid-edit is applied after). */
30
+ onStep(step) {
31
+ if (this.mode !== 'thinking' || this.finished)
32
+ return;
33
+ this.pendingStep = step;
34
+ if (!this.inFlight)
35
+ void this.drain();
36
+ }
37
+ async drain() {
38
+ this.inFlight = true;
39
+ try {
40
+ while (this.pendingStep !== null && !this.finished) {
41
+ const step = this.pendingStep;
42
+ this.pendingStep = null;
43
+ const body = `🤖 *thinking…* — ${step}`;
44
+ try {
45
+ if (this.statusMessageId === null) {
46
+ const msg = await this.api.sendMessage(this.channelId, body, this.threadRootId);
47
+ this.statusMessageId = msg.id;
48
+ }
49
+ else {
50
+ await this.api.editMessage(this.statusMessageId, body);
51
+ }
52
+ }
53
+ catch (err) {
54
+ this.log(`status message failed: ${err.message}`);
55
+ }
56
+ }
57
+ }
58
+ finally {
59
+ this.inFlight = false;
60
+ }
61
+ }
62
+ /** Stop typing and delete the status message (if any). */
63
+ async finish() {
64
+ this.finished = true;
65
+ if (this.typingTimer)
66
+ clearInterval(this.typingTimer);
67
+ // wait out an in-flight post/edit so the delete can't race message creation
68
+ while (this.inFlight)
69
+ await new Promise((r) => setTimeout(r, 25));
70
+ if (this.statusMessageId !== null) {
71
+ await this.api.deleteMessage(this.statusMessageId).catch((err) => {
72
+ this.log(`status delete failed: ${err.message}`);
73
+ });
74
+ this.statusMessageId = null;
75
+ }
76
+ }
77
+ }
78
+ //# sourceMappingURL=progress.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"progress.js","sourceRoot":"","sources":["../src/progress.ts"],"names":[],"mappings":"AAOA,MAAM,kBAAkB,GAAG,IAAI,CAAC;AAEhC,MAAM,OAAO,gBAAgB;IAQR;IACA;IACA;IACA;IACA;IACA;IAZX,WAAW,GAA0B,IAAI,CAAC;IAC1C,eAAe,GAAkB,IAAI,CAAC;IACtC,QAAQ,GAAG,KAAK,CAAC;IACjB,WAAW,GAAkB,IAAI,CAAC;IAClC,QAAQ,GAAG,KAAK,CAAC;IAEzB,YACmB,GAAY,EACZ,MAAkB,EAClB,IAAkB,EAClB,SAAiB,EACjB,YAAgC,EAChC,GAA0B;QAL1B,QAAG,GAAH,GAAG,CAAS;QACZ,WAAM,GAAN,MAAM,CAAY;QAClB,SAAI,GAAJ,IAAI,CAAc;QAClB,cAAS,GAAT,SAAS,CAAQ;QACjB,iBAAY,GAAZ,YAAY,CAAoB;QAChC,QAAG,GAAH,GAAG,CAAuB;IAC1C,CAAC;IAEJ,KAAK;QACH,IAAI,IAAI,CAAC,IAAI,KAAK,QAAQ;YAAE,OAAO;QACnC,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACvC,IAAI,CAAC,WAAW,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,kBAAkB,CAAC,CAAC;QACjG,IAAI,CAAC,WAAW,CAAC,KAAK,EAAE,CAAC;IAC3B,CAAC;IAED,yFAAyF;IACzF,MAAM,CAAC,IAAY;QACjB,IAAI,IAAI,CAAC,IAAI,KAAK,UAAU,IAAI,IAAI,CAAC,QAAQ;YAAE,OAAO;QACtD,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC;QACxB,IAAI,CAAC,IAAI,CAAC,QAAQ;YAAE,KAAK,IAAI,CAAC,KAAK,EAAE,CAAC;IACxC,CAAC;IAEO,KAAK,CAAC,KAAK;QACjB,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;QACrB,IAAI,CAAC;YACH,OAAO,IAAI,CAAC,WAAW,KAAK,IAAI,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC;gBACnD,MAAM,IAAI,GAAG,IAAI,CAAC,WAAW,CAAC;gBAC9B,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC;gBACxB,MAAM,IAAI,GAAG,oBAAoB,IAAI,EAAE,CAAC;gBACxC,IAAI,CAAC;oBACH,IAAI,IAAI,CAAC,eAAe,KAAK,IAAI,EAAE,CAAC;wBAClC,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,WAAW,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC;wBAChF,IAAI,CAAC,eAAe,GAAG,GAAG,CAAC,EAAE,CAAC;oBAChC,CAAC;yBAAM,CAAC;wBACN,MAAM,IAAI,CAAC,GAAG,CAAC,WAAW,CAAC,IAAI,CAAC,eAAe,EAAE,IAAI,CAAC,CAAC;oBACzD,CAAC;gBACH,CAAC;gBAAC,OAAO,GAAG,EAAE,CAAC;oBACb,IAAI,CAAC,GAAG,CAAC,0BAA2B,GAAa,CAAC,OAAO,EAAE,CAAC,CAAC;gBAC/D,CAAC;YACH,CAAC;QACH,CAAC;gBAAS,CAAC;YACT,IAAI,CAAC,QAAQ,GAAG,KAAK,CAAC;QACxB,CAAC;IACH,CAAC;IAED,0DAA0D;IAC1D,KAAK,CAAC,MAAM;QACV,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;QACrB,IAAI,IAAI,CAAC,WAAW;YAAE,aAAa,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QACtD,4EAA4E;QAC5E,OAAO,IAAI,CAAC,QAAQ;YAAE,MAAM,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;QAClE,IAAI,IAAI,CAAC,eAAe,KAAK,IAAI,EAAE,CAAC;YAClC,MAAM,IAAI,CAAC,GAAG,CAAC,aAAa,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC,KAAK,CAAC,CAAC,GAAU,EAAE,EAAE;gBACtE,IAAI,CAAC,GAAG,CAAC,yBAAyB,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;YACnD,CAAC,CAAC,CAAC;YACH,IAAI,CAAC,eAAe,GAAG,IAAI,CAAC;QAC9B,CAAC;IACH,CAAC;CACF"}
@@ -0,0 +1,36 @@
1
+ import type { RuntimeConfig } from './config.js';
2
+ export interface RunOpts {
3
+ sessionId: string;
4
+ /** false → --session-id (new session); true → --resume. */
5
+ resume: boolean;
6
+ prompt: string;
7
+ systemPrompt: string;
8
+ /** Path to an MCP config JSON to pass via --mcp-config (claude only). */
9
+ mcpConfigPath?: string | undefined;
10
+ onToolStep(step: string): void;
11
+ log(msg: string): void;
12
+ }
13
+ export interface RunResult {
14
+ ok: boolean;
15
+ text: string;
16
+ error?: string;
17
+ }
18
+ /** One line per tool call, latest step shown: "Bash: pnpm test". */
19
+ export declare function formatToolStep(name: string, input: unknown): string;
20
+ /**
21
+ * Feed stream-json stdout lines; emits tool steps and captures the final
22
+ * result. Tolerant of non-JSON noise on stdout.
23
+ */
24
+ export declare class StreamJsonParser {
25
+ private readonly onToolStep;
26
+ private buf;
27
+ finalText: string;
28
+ isError: boolean;
29
+ sawResult: boolean;
30
+ constructor(onToolStep: (step: string) => void);
31
+ feed(chunk: string): void;
32
+ private handleLine;
33
+ }
34
+ /** Demo mode: static canned reply, no CLI spawn. */
35
+ export declare const DEMO_REPLY = "Your message was received";
36
+ export declare function runRuntime(cfg: RuntimeConfig, opts: RunOpts): Promise<RunResult>;