@dalmasonto/taskflow-mcp 1.0.35 → 2.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 (101) hide show
  1. package/README.md +138 -193
  2. package/dist/attachment-download.d.ts +74 -0
  3. package/dist/attachment-download.js +193 -0
  4. package/dist/attachment-download.js.map +1 -0
  5. package/dist/attachments.d.ts +23 -0
  6. package/dist/attachments.js +66 -0
  7. package/dist/attachments.js.map +1 -0
  8. package/dist/client.d.ts +206 -0
  9. package/dist/client.js +279 -0
  10. package/dist/client.js.map +1 -0
  11. package/dist/config.d.ts +137 -18
  12. package/dist/config.js +187 -106
  13. package/dist/config.js.map +1 -0
  14. package/dist/connect.d.ts +89 -0
  15. package/dist/connect.js +269 -0
  16. package/dist/connect.js.map +1 -0
  17. package/dist/doctor.d.ts +24 -0
  18. package/dist/doctor.js +120 -0
  19. package/dist/doctor.js.map +1 -0
  20. package/dist/events.d.ts +186 -0
  21. package/dist/events.js +407 -0
  22. package/dist/events.js.map +1 -0
  23. package/dist/index.d.ts +7 -1
  24. package/dist/index.js +121 -218
  25. package/dist/index.js.map +1 -0
  26. package/dist/instructions.d.ts +12 -0
  27. package/dist/instructions.js +114 -0
  28. package/dist/instructions.js.map +1 -0
  29. package/dist/mint.d.ts +62 -0
  30. package/dist/mint.js +135 -0
  31. package/dist/mint.js.map +1 -0
  32. package/dist/mirror.d.ts +68 -0
  33. package/dist/mirror.js +103 -0
  34. package/dist/mirror.js.map +1 -0
  35. package/dist/pane-queue.d.ts +29 -0
  36. package/dist/pane-queue.js +35 -0
  37. package/dist/pane-queue.js.map +1 -0
  38. package/dist/prompts.d.ts +79 -0
  39. package/dist/prompts.js +211 -0
  40. package/dist/prompts.js.map +1 -0
  41. package/dist/resolve.d.ts +72 -0
  42. package/dist/resolve.js +89 -0
  43. package/dist/resolve.js.map +1 -0
  44. package/dist/runtime.d.ts +54 -0
  45. package/dist/runtime.js +321 -0
  46. package/dist/runtime.js.map +1 -0
  47. package/dist/server.d.ts +56 -0
  48. package/dist/server.js +793 -0
  49. package/dist/server.js.map +1 -0
  50. package/dist/session-identifier.d.ts +48 -0
  51. package/dist/session-identifier.js +44 -0
  52. package/dist/session-identifier.js.map +1 -0
  53. package/dist/sessions-store.d.ts +38 -0
  54. package/dist/sessions-store.js +88 -0
  55. package/dist/sessions-store.js.map +1 -0
  56. package/dist/tmux.d.ts +200 -0
  57. package/dist/tmux.js +580 -0
  58. package/dist/tmux.js.map +1 -0
  59. package/hooks/metadata.mjs +99 -0
  60. package/hooks/permission-prompt.mjs +100 -0
  61. package/hooks/taskflow-hook.mjs +499 -0
  62. package/hooks/tool-logging.mjs +63 -0
  63. package/package.json +38 -29
  64. package/dist/agent-registry.d.ts +0 -28
  65. package/dist/agent-registry.js +0 -158
  66. package/dist/db.d.ts +0 -5
  67. package/dist/db.js +0 -220
  68. package/dist/helpers.d.ts +0 -21
  69. package/dist/helpers.js +0 -27
  70. package/dist/resources.d.ts +0 -2
  71. package/dist/resources.js +0 -89
  72. package/dist/retry.d.ts +0 -34
  73. package/dist/retry.js +0 -94
  74. package/dist/sse.d.ts +0 -10
  75. package/dist/sse.js +0 -824
  76. package/dist/tmux-bridge.d.ts +0 -13
  77. package/dist/tmux-bridge.js +0 -217
  78. package/dist/tools/activity.d.ts +0 -39
  79. package/dist/tools/activity.js +0 -152
  80. package/dist/tools/agent-inbox.d.ts +0 -12
  81. package/dist/tools/agent-inbox.js +0 -272
  82. package/dist/tools/agent.d.ts +0 -14
  83. package/dist/tools/agent.js +0 -168
  84. package/dist/tools/analytics.d.ts +0 -21
  85. package/dist/tools/analytics.js +0 -191
  86. package/dist/tools/checkpoint.d.ts +0 -27
  87. package/dist/tools/checkpoint.js +0 -105
  88. package/dist/tools/notifications.d.ts +0 -31
  89. package/dist/tools/notifications.js +0 -59
  90. package/dist/tools/projects.d.ts +0 -55
  91. package/dist/tools/projects.js +0 -112
  92. package/dist/tools/settings.d.ts +0 -19
  93. package/dist/tools/settings.js +0 -73
  94. package/dist/tools/tasks.d.ts +0 -105
  95. package/dist/tools/tasks.js +0 -403
  96. package/dist/tools/terminal.d.ts +0 -4
  97. package/dist/tools/terminal.js +0 -98
  98. package/dist/tools/timer.d.ts +0 -37
  99. package/dist/tools/timer.js +0 -154
  100. package/dist/types.d.ts +0 -83
  101. package/dist/types.js +0 -30
package/dist/index.js CHANGED
@@ -1,232 +1,135 @@
1
1
  #!/usr/bin/env node
2
- import 'dotenv/config';
3
- import { getConfig } from './config.js';
4
- import { startSSEServer } from './sse.js';
5
- import { getDb } from './db.js';
6
- import { broadcast } from './sse.js';
7
- const httpOnly = process.argv.includes('--http-only');
8
- // Close orphaned sessions left from a previous crash.
9
- // Uses a grace period so that sessions from the current conversation
10
- // (stdio transport restarts the server on each tool call) are preserved.
11
- const ORPHAN_GRACE_MS = 5 * 60 * 1000; // 5 minutes
12
- function cleanupOrphanedSessions() {
13
- const db = getDb();
14
- const nowMs = Date.now();
15
- const nowIso = new Date(nowMs).toISOString();
16
- const cutoff = new Date(nowMs - ORPHAN_GRACE_MS).toISOString();
17
- // Only close sessions that started more than ORPHAN_GRACE_MS ago
18
- const orphaned = db.prepare('SELECT * FROM sessions WHERE end IS NULL AND start < ?').all(cutoff);
19
- if (orphaned.length === 0)
2
+ /**
3
+ * Bin entry: load `.taskflow.json`, build the MCP server, and serve over stdio.
4
+ *
5
+ * A configuration failure prints a clear message to stderr and exits non-zero so
6
+ * the human fixes the credential file rather than getting a silent dead server.
7
+ */
8
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
9
+ import { buildServer } from "./server.js";
10
+ import { ConfigError } from "./config.js";
11
+ import { runDoctor } from "./doctor.js";
12
+ import { runTmuxMirror } from "./tmux.js";
13
+ import { runMint } from "./mint.js";
14
+ import { startAgent } from "./runtime.js";
15
+ const USAGE = `taskflow-mcp — TaskFlow MCP server
16
+
17
+ taskflow-mcp Serve over stdio (how an MCP client runs it).
18
+ taskflow-mcp --check Verify config + backend auth, then exit.
19
+ taskflow-mcp --tmux [target] Mirror a tmux pane into the dashboard terminal.
20
+ taskflow-mcp --mint <name> Create a NEW agent identity + profile, then exit.
21
+ taskflow-mcp --help This message.
22
+
23
+ GIVING A SECOND TERMINAL ITS OWN IDENTITY. An agent is identified by
24
+ project + profile, so two terminals sharing the default \`main\` profile are ONE
25
+ agent: one row in the dashboard, one DM inbox, one shared read cursor. --mint
26
+ creates a separate identity and writes it to .taskflow.json:
27
+
28
+ taskflow-mcp --mint bear --display-name "Claude (bear)"
29
+ export TASKFLOW_PROFILE=bear # then start that terminal's agent
30
+
31
+ It needs YOUR user token (--token, or TASKFLOW_USER_TOKEN), not an agent key:
32
+ linking an agent is human-authorized and records who vouched for it, which is
33
+ why this is a command you run and not a tool the agent can call. An existing
34
+ profile is never overwritten and \`default_profile\` never moves, so terminals
35
+ already running keep the identity they have.
36
+
37
+ The MCP tools (whoami, create_task, ...) are called by the MODEL through an MCP
38
+ client, not typed as commands. To check the setup yourself, use --check.
39
+
40
+ TERMINAL MIRRORING IS AUTOMATIC. When the agent runs inside tmux, the server
41
+ finds its own pane and streams it to the dashboard — nothing to launch, no pane
42
+ id to look up. Set TASKFLOW_MIRROR=off to disable it. The agent still connects
43
+ and appears online without tmux; only the streamed terminal needs a pane.
44
+
45
+ WHICH IDENTITY AM I? With one profile in .taskflow.json the server connects as
46
+ it silently. With several, it connects as NONE of them and the agent must ask
47
+ you which this terminal is, then call select_profile — the pick is remembered
48
+ per terminal in .taskflow/sessions.json. Set TASKFLOW_PROFILE to skip the ask.
49
+
50
+ --tmux is only for mirroring a pane the agent is NOT running in (say, watching a
51
+ build in another window). It runs in the foreground until Ctrl-C; the target
52
+ defaults to tmux's active pane, or pass one from \`tmux list-panes -a\`. Options:
53
+ --interval=<ms> Capture cadence (default 2000).
54
+ --profile=<name> Act as a non-default .taskflow.json profile.
55
+ --notify Type a one-line unread notice INTO the pane when messages
56
+ arrive. Off by default: this writes to a live session.
57
+ --notify-submit Also press Enter, so the agent acts on it unprompted.
58
+ Anyone who can post to a channel can then wake the agent —
59
+ enable it only for sessions you control.`;
60
+ async function main() {
61
+ const argv = process.argv.slice(2);
62
+ if (argv.includes("--help") || argv.includes("-h")) {
63
+ process.stdout.write(`${USAGE}\n`);
20
64
  return;
21
- // Close each orphaned session with end = now (preserves the real elapsed time)
22
- for (const session of orphaned) {
23
- db.prepare('UPDATE sessions SET end = ? WHERE id = ?').run(nowIso, session.id);
24
65
  }
25
- // Set orphaned in_progress tasks back to paused
26
- const taskIds = [...new Set(orphaned.map(s => s.task_id))];
27
- for (const taskId of taskIds) {
28
- const task = db.prepare('SELECT status FROM tasks WHERE id = ?').get(taskId);
29
- if (task?.status === 'in_progress') {
30
- db.prepare("UPDATE tasks SET status = 'paused', updated_at = ? WHERE id = ?").run(nowIso, taskId);
31
- broadcast('task_updated', { entity: 'task', action: 'task_status_changed', payload: db.prepare('SELECT * FROM tasks WHERE id = ?').get(taskId) });
32
- }
33
- }
34
- }
35
- cleanupOrphanedSessions();
36
- // Always start the HTTP/SSE server (probes for existing instances, finds fallback port)
37
- await startSSEServer();
38
- // Liveness checker — periodically check registered agents and mark dead ones
39
- const cfg = getConfig();
40
- setInterval(async () => {
41
- try {
42
- const { checkAgentLiveness } = await import('./agent-registry.js');
43
- checkAgentLiveness();
66
+ // --check is a human-facing diagnostic, so it prints to stdout and exits;
67
+ // it never opens the MCP transport.
68
+ if (argv.includes("--check") || argv.includes("--doctor")) {
69
+ process.exit(await runDoctor());
44
70
  }
45
- catch { /* ignore */ }
46
- }, cfg.agentLivenessInterval);
47
- // Only start MCP stdio transport when not in http-only mode
48
- if (!httpOnly) {
49
- const { McpServer } = await import('@modelcontextprotocol/sdk/server/mcp.js');
50
- const { StdioServerTransport } = await import('@modelcontextprotocol/sdk/server/stdio.js');
51
- const { registerTaskTools } = await import('./tools/tasks.js');
52
- const { registerProjectTools } = await import('./tools/projects.js');
53
- const { registerTimerTools } = await import('./tools/timer.js');
54
- const { registerAnalyticsTools } = await import('./tools/analytics.js');
55
- const { registerActivityTools } = await import('./tools/activity.js');
56
- const { registerNotificationTools } = await import('./tools/notifications.js');
57
- const { registerSettingsTools } = await import('./tools/settings.js');
58
- const { registerAgentTools } = await import('./tools/agent.js');
59
- const { registerAgentInboxTools } = await import('./tools/agent-inbox.js');
60
- const { registerTerminalTools } = await import('./tools/terminal.js');
61
- const { registerResources } = await import('./resources.js');
62
- const { registerCheckpointTools } = await import('./tools/checkpoint.js');
63
- const server = new McpServer({ name: 'taskflow', version: '1.0.25' }, {
64
- instructions: [
65
- 'You MUST call bootstrap (preferred) or get_agent_instructions before doing any work. This loads your task management rules and project context.',
66
- 'You MUST create a task in TaskFlow before editing files or running commands. search_tasks first to avoid duplicates.',
67
- 'You MUST call start_timer when beginning work and stop_timer when done. Timer lifecycle: start_timer → work → stop_timer(final_status).',
68
- 'Never silently fix bugs — always create or reopen a task first, then track the fix.',
69
- 'When you need user input, call ask_user to post it to the Agent Inbox so the user can respond remotely.',
70
- 'Use log_debug to document your work — it is shared memory visible to the user and other agents.',
71
- ].join('\n'),
72
- });
73
- // Filled in after agent registration — used by the inbox drain below
74
- let agentNameGetter = () => 'unknown';
75
- // Drain any undelivered inbox messages for this agent and prepend them to a
76
- // tool result. This is the non-tmux delivery path: since we can't inject into
77
- // stdin, we piggyback pending messages onto the next tool response so Claude
78
- // sees them in its context without any terminal tricks.
79
- function drainInboxNotice() {
71
+ // --mint is a human-facing command for the same reason it is not an MCP tool:
72
+ // `POST /agents/link` is RequireAuth-gated, so it needs a USER token that the
73
+ // agent does not have. Prints and exits; never opens the MCP transport.
74
+ if (argv.includes("--mint")) {
80
75
  try {
81
- const name = agentNameGetter();
82
- if (name === 'unknown')
83
- return '';
84
- const db = getDb();
85
- const pending = db.prepare(`SELECT id, sender_name, question FROM agent_messages
86
- WHERE recipient_name = ? AND status = 'pending' AND delivered IS NULL
87
- ORDER BY created_at ASC`).all(name);
88
- if (pending.length === 0)
89
- return '';
90
- for (const msg of pending) {
91
- db.prepare('UPDATE agent_messages SET delivered = 1 WHERE id = ?').run(msg.id);
92
- }
93
- const lines = pending.map(m => ` [Inbox #${m.id} from ${m.sender_name}]: ${m.question}`).join('\n');
94
- return `\n⚠️ INBOX MESSAGE(S) — respond before continuing:\n${lines}\n`;
76
+ for (const line of await runMint(argv))
77
+ process.stdout.write(`${line}\n`);
78
+ process.exit(0);
95
79
  }
96
- catch {
97
- return '';
80
+ catch (err) {
81
+ // A MintError is a message written for the operator; anything else is a
82
+ // surprise and keeps its own text.
83
+ process.stderr.write(`${err.message}\n`);
84
+ process.exit(1);
98
85
  }
99
86
  }
100
- // Wrap server.tool() to track execution time and log failures
101
- const originalTool = server.tool.bind(server);
102
- server.tool = function (...args) {
103
- const toolName = args[0];
104
- // Find the callback (always the last argument, and it's a function)
105
- const cbIndex = args.length - 1;
106
- const originalCb = args[cbIndex];
107
- if (typeof originalCb === 'function') {
108
- args[cbIndex] = async (...cbArgs) => {
109
- // Check task-scoped tool allowlists if a timer is active
110
- try {
111
- const db = getDb();
112
- const activeSession = db.prepare("SELECT task_id FROM sessions WHERE end IS NULL ORDER BY start DESC LIMIT 1").get();
113
- if (activeSession) {
114
- const task = db.prepare("SELECT allowed_tools, denied_tools FROM tasks WHERE id = ?").get(activeSession.task_id);
115
- if (task) {
116
- if (task.denied_tools) {
117
- const denied = JSON.parse(task.denied_tools);
118
- if (denied.includes(toolName)) {
119
- return { isError: true, content: [{ type: 'text', text: JSON.stringify({ error: `Tool "${toolName}" is denied for task #${activeSession.task_id}`, code: 'TOOL_DENIED' }) }] };
120
- }
121
- }
122
- if (task.allowed_tools) {
123
- const allowed = JSON.parse(task.allowed_tools);
124
- if (!allowed.includes(toolName)) {
125
- return { isError: true, content: [{ type: 'text', text: JSON.stringify({ error: `Tool "${toolName}" is not in the allowlist for task #${activeSession.task_id}`, code: 'TOOL_NOT_ALLOWED' }) }] };
126
- }
127
- }
128
- }
129
- }
130
- }
131
- catch { /* don't break tool execution on allowlist check failure */ }
132
- const start = Date.now();
133
- try {
134
- const result = await originalCb(...cbArgs);
135
- const duration = Date.now() - start;
136
- // Record execution and broadcast event
137
- try {
138
- const ts = new Date().toISOString();
139
- const db = getDb();
140
- db.prepare('INSERT INTO tool_executions (tool_name, duration_ms, success, created_at) VALUES (?, ?, 1, ?)').run(toolName, duration, ts);
141
- broadcast('tool_executed', { entity: 'tool', action: 'tool_executed', payload: { tool_name: toolName, duration_ms: duration, success: true, created_at: ts } });
142
- }
143
- catch { /* don't break tool execution */ }
144
- // Non-tmux inbox delivery: prepend any pending messages to the result
145
- const notice = drainInboxNotice();
146
- if (notice && result?.content) {
147
- const first = result.content.find((c) => c.type === 'text');
148
- if (first)
149
- first.text = notice + first.text;
150
- }
151
- return result;
152
- }
153
- catch (err) {
154
- const duration = Date.now() - start;
155
- try {
156
- const ts = new Date().toISOString();
157
- const db = getDb();
158
- db.prepare('INSERT INTO tool_executions (tool_name, duration_ms, success, error_message, created_at) VALUES (?, ?, 0, ?, ?)').run(toolName, duration, err.message ?? String(err), ts);
159
- broadcast('tool_failed', { entity: 'tool', action: 'tool_failed', payload: { tool_name: toolName, duration_ms: duration, success: false, error: err.message, created_at: ts } });
160
- }
161
- catch { /* don't break tool execution */ }
162
- throw err;
163
- }
164
- };
165
- }
166
- return originalTool.apply(server, args);
167
- };
168
- registerAgentTools(server);
169
- registerTaskTools(server);
170
- registerProjectTools(server);
171
- registerTimerTools(server);
172
- registerAnalyticsTools(server);
173
- registerActivityTools(server);
174
- registerNotificationTools(server);
175
- registerSettingsTools(server);
176
- registerAgentInboxTools(server);
177
- registerTerminalTools(server);
178
- registerCheckpointTools(server);
179
- registerResources(server);
180
- const transport = new StdioServerTransport();
181
- await server.connect(transport);
182
- const { registerAgent, unregisterAgent } = await import('./agent-registry.js');
183
- const { setAgentName, getAgentName } = await import('./tools/agent-inbox.js');
184
- // Auto-register this agent and sync name to agent-inbox tools
185
- const agentName = registerAgent();
186
- setAgentName(agentName);
187
- agentNameGetter = getAgentName; // wire up inbox drain now that we have a name
188
- const agentPid = process.ppid;
189
- console.error(`[agent] registered as "${agentName}"`);
190
- let cleanup = () => { try {
191
- unregisterAgent(getAgentName());
87
+ const tmuxIndex = argv.indexOf("--tmux");
88
+ if (tmuxIndex !== -1) {
89
+ const flag = (name) => {
90
+ const hit = argv.find((a) => a.startsWith(`--${name}=`));
91
+ return hit ? hit.slice(name.length + 3) : undefined;
92
+ };
93
+ // The first non-flag argument after --tmux is the pane target.
94
+ const target = argv.slice(tmuxIndex + 1).find((a) => !a.startsWith("--"));
95
+ const interval = flag("interval");
96
+ process.exit(await runTmuxMirror({
97
+ target,
98
+ profile: flag("profile"),
99
+ notify: argv.includes("--notify") || argv.includes("--notify-submit"),
100
+ notifySubmit: argv.includes("--notify-submit"),
101
+ ...(interval ? { intervalMs: Number(interval) } : {}),
102
+ }));
192
103
  }
193
- catch { } process.exit(0); };
194
- process.on('SIGINT', cleanup);
195
- process.on('SIGTERM', cleanup);
196
- // Tmux bridge: SSE listener for instant delivery + capture for terminal→chat
197
- let tmuxTarget = null;
104
+ let server;
198
105
  try {
199
- const { execSync: exec } = await import('child_process');
200
- const ptsPath = exec(`readlink /proc/${agentPid}/fd/0`).toString().trim();
201
- const panes = exec('tmux list-panes -a -F "#{pane_id} #{pane_tty}"').toString().trim().split('\n');
202
- for (const line of panes) {
203
- const [paneId, paneTty] = line.split(' ');
204
- if (paneTty === ptsPath) {
205
- tmuxTarget = paneId;
206
- break;
207
- }
208
- }
209
- }
210
- catch {
211
- console.error('[bridge] tmux not available');
106
+ server = buildServer();
212
107
  }
213
- // Always start the bridge — tmuxPane may be null for non-tmux sessions,
214
- // in which case messages are delivered via stderr instead of tmux injection.
215
- {
216
- const { startTmuxBridge } = await import('./tmux-bridge.js');
217
- const stopBridge = startTmuxBridge({
218
- getAgentName,
219
- agentPid,
220
- tmuxPane: tmuxTarget,
221
- });
222
- if (!tmuxTarget) {
223
- console.error('[bridge] agent not in tmux — using stderr delivery for inbox messages');
108
+ catch (err) {
109
+ if (err instanceof ConfigError) {
110
+ process.stderr.write(`taskflow-mcp: ${err.message}\n`);
111
+ process.exit(1);
224
112
  }
225
- const originalCleanup = cleanup;
226
- cleanup = () => { stopBridge(); originalCleanup(); };
227
- process.removeListener('SIGINT', originalCleanup);
228
- process.removeListener('SIGTERM', originalCleanup);
229
- process.on('SIGINT', cleanup);
230
- process.on('SIGTERM', cleanup);
113
+ throw err;
231
114
  }
115
+ const transport = new StdioServerTransport();
116
+ await server.connect(transport);
117
+ // Stderr only — stdout is the MCP transport and must stay clean.
118
+ process.stderr.write("taskflow-mcp: connected (stdio)\n");
119
+ // Bring the agent online. This is NOT conditional on tmux: registering a
120
+ // session is what makes the agent visible and reachable, and it must happen
121
+ // whether or not there is a pane to mirror. Best-effort in every direction —
122
+ // no credential, or a backend that is down, degrades to retrying quietly.
123
+ //
124
+ // Not awaited, and its rejection is caught here rather than by main()'s
125
+ // handler: the transport is already serving, so a bad TASKFLOW_PROFILE must
126
+ // cost the connection, not the tool server.
127
+ void startAgent().catch((err) => {
128
+ process.stderr.write(`taskflow-mcp: could not start agent (${err.message})\n`);
129
+ });
232
130
  }
131
+ main().catch((err) => {
132
+ process.stderr.write(`taskflow-mcp: fatal: ${err.stack ?? err}\n`);
133
+ process.exit(1);
134
+ });
135
+ //# 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,12 @@
1
+ /**
2
+ * Agent-facing guidance surfaced to the model on connect.
3
+ *
4
+ * The MCP `initialize` result carries an optional `instructions` string that the
5
+ * client shows the model before it uses any tool. Without it an agent only sees
6
+ * per-tool schemas and misses what TaskFlow *is*, the workflow, and the
7
+ * conventions — e.g. attaching a file with `send_message`'s `files` array
8
+ * instead of pasting its contents inline. This is that overview; keep it
9
+ * accurate to the registered tools. Style follows the v1 taskflow-mcp
10
+ * onboarding: a plain "what this is", then imperative rules.
11
+ */
12
+ export declare const AGENT_INSTRUCTIONS = "# TaskFlow\n\nTaskFlow is a **local-first task board and realtime chat with MCP integration**,\nbuilt for **multi-agent collaboration**: several AI agents and humans work in one\nshared project, discover each other, message in channels, claim and review tasks,\nstream their terminals live, and coordinate to build software together. You are\n**one agent** in such a project \u2014 act like a good teammate and keep people in the\nloop.\n\n## Use TaskFlow by default\n**Proactively track your work in TaskFlow unless the user explicitly tells you\nnot to.** At the start of a session, confirm your identity with **whoami** and\nfind your project's board with **list_tasks**. As you work, keep the board the\nsource of truth: create a task for every substantial piece of work (features,\nbugs, refactors, and investigation/debugging alike), claim it, move it through\nits statuses, and log activity as you go. Debugging is real work \u2014 open or reopen\na task for it, never silently fix a bug. The only time you skip this is when the\nuser says something like \"don't use TaskFlow\" or \"don't track this\" \u2014 then honor\nthat for the rest of the session.\n\n## Identity & connecting\n- One credential maps to **one agent identity in one project**. The optional\n `profile` argument on every tool selects which identity to act as (use the\n `reviewer` profile for review work).\n- **Connecting is automatic.** The server registers your session and keeps\n heartbeating on its own \u2014 you do not need to call `register_session` or\n `heartbeat` to appear online. Call **whoami** to CONFIRM your identity,\n project, connection and terminal mirror. A `mirror.state` of `off` only\n means there is no tmux pane to stream; you are still connected.\n- **If a tool returns `profile_ambiguous`**, this repo defines several\n identities and nothing says which one this terminal is. Never guess a\n profile. Do not guess based on cwd, hostname, or any other assumption \u2014 ask\n your human which to use: show each `display_name`, note which is\n `recommended`, and warn that an `in_use` one is already taken by another\n terminal \u2014 then call **select_profile** with their answer. Picking wrong\n makes two terminals the same agent, sharing one inbox and one read cursor.\n You are asked once per terminal; the choice is remembered across reconnects.\n If **select_profile** returns a `warning`, repeat it to your human before you\n do anything else \u2014 another terminal is already using that identity.\n- Call **list_agents** to see who else is on the project and **list_channels** for\n the rooms you can post in. If other agents are active, coordinate rather than\n duplicate work.\n\n## Messaging \u2014 stay in the loop\n- Call **check_messages** regularly \u2014 at minimum when you finish a task, before\n you start a new one, and after a long stretch of work. It returns only what you\n have NOT marked read, across every channel you're in. After you've read AND\n acted on them, call **mark_read** for each channel with the highest message id\n you handled \u2014 otherwise you'll be handed the same messages again.\n- Messages are either **broadcast** or **directed** at specific members. If a\n message is prefixed with **\"\uD83D\uDEAB DO NOT ACT, YOU ARE NOT THE TARGET\"**, someone\n else owns that work: you may chat or add information, but must NOT execute the\n task.\n- **send_message** posts markdown to a channel (find ids with **list_channels**,\n people with **list_agents**). To share a file \u2014 a spec, log, diff, screenshot,\n or anything a teammate should open as a file \u2014 pass its path in the **`files`\n array**. Do NOT paste large file contents into the message body; attach the\n file instead.\n- **Referencing tasks vs GitHub issues.** In any message, activity note or task\n description, `#12` and `TASK#12` render as a clickable **TaskFlow task** chip.\n So write a **GitHub issue** as `#gh12` (or `#GH12`) and a **chat message**\n as `#msg12` \u2014 never a bare `#12` for either.\n A bare `#12` meaning a GitHub issue becomes a task chip pointing at TaskFlow\n task 12, which is a different thing or does not exist at all, and it misleads\n whoever reads it. `#gh12` renders as a distinct chip linking to the issue on\n the project's linked repo.\n- **download_attachment** fetches a file a teammate attached. It returns a PATH,\n not the contents \u2014 open it with your own file tools, and check `size_bytes`\n before reading anything wholesale.\n\n## Tasks \u2014 the work\n- Track real work as tasks. **list_tasks** (filter by `status`, or\n `assigned='me'`), **create_task**, **claim_task**, **update_task_status**,\n **report_review**. Search/list before creating so you don't duplicate an\n existing task.\n- Typical lifecycle: pick a ready task \u2192 **claim_task** \u2192 set **in_progress** \u2192\n do the work \u2192 set **partial_done** to request human review \u2192 a reviewer runs\n **report_review** (`approved` | `changes_requested`) \u2192 **done** on approval,\n or back to **in_progress** to address changes.\n- Statuses are a fixed set: `not_started`, `in_progress`, `paused`,\n `blocked`, `partial_done`, `done`, `archived`. Don't invent values. Never\n silently fix a bug \u2014 open or reopen a task first, then track the fix.\n\n## Activity & terminal \u2014 be transparent\n- **log_activity** records a notable action (optionally linked to a task);\n **get_activity** reads recent history. Use it as a running journal so humans and\n other agents can follow what you did and why, without reading all of chat.\n To mirror a note onto a task's linked GitHub issue, call it with a `task` and\n `post_to_github: true` \u2014 it posts as a comment under your owner's identity, but\n only if the project is GitHub-linked, the task is published as an issue, and your\n owner is connected + opted in (`post_as_me`); otherwise it just records the\n activity. Don't spam issues \u2014 reserve it for updates worth a public comment.\n- **capture_terminal** streams terminal output to the dashboard so humans can\n watch your work live.\n- Tell people what you're doing: announce significant actions in chat or activity,\n so the user can reconstruct what happened from your messages alone.\n\n## Etiquette\nKeep humans informed through messages and activity; prefer attaching files over\ndumping large content inline; always **mark_read** after handling messages;\nrespect the **\uD83D\uDEAB DO NOT ACT** marker; coordinate with other agents instead of\nstepping on their work; and act under the right **profile** for the job.";
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Agent-facing guidance surfaced to the model on connect.
3
+ *
4
+ * The MCP `initialize` result carries an optional `instructions` string that the
5
+ * client shows the model before it uses any tool. Without it an agent only sees
6
+ * per-tool schemas and misses what TaskFlow *is*, the workflow, and the
7
+ * conventions — e.g. attaching a file with `send_message`'s `files` array
8
+ * instead of pasting its contents inline. This is that overview; keep it
9
+ * accurate to the registered tools. Style follows the v1 taskflow-mcp
10
+ * onboarding: a plain "what this is", then imperative rules.
11
+ */
12
+ export const AGENT_INSTRUCTIONS = `# TaskFlow
13
+
14
+ TaskFlow is a **local-first task board and realtime chat with MCP integration**,
15
+ built for **multi-agent collaboration**: several AI agents and humans work in one
16
+ shared project, discover each other, message in channels, claim and review tasks,
17
+ stream their terminals live, and coordinate to build software together. You are
18
+ **one agent** in such a project — act like a good teammate and keep people in the
19
+ loop.
20
+
21
+ ## Use TaskFlow by default
22
+ **Proactively track your work in TaskFlow unless the user explicitly tells you
23
+ not to.** At the start of a session, confirm your identity with **whoami** and
24
+ find your project's board with **list_tasks**. As you work, keep the board the
25
+ source of truth: create a task for every substantial piece of work (features,
26
+ bugs, refactors, and investigation/debugging alike), claim it, move it through
27
+ its statuses, and log activity as you go. Debugging is real work — open or reopen
28
+ a task for it, never silently fix a bug. The only time you skip this is when the
29
+ user says something like "don't use TaskFlow" or "don't track this" — then honor
30
+ that for the rest of the session.
31
+
32
+ ## Identity & connecting
33
+ - One credential maps to **one agent identity in one project**. The optional
34
+ \`profile\` argument on every tool selects which identity to act as (use the
35
+ \`reviewer\` profile for review work).
36
+ - **Connecting is automatic.** The server registers your session and keeps
37
+ heartbeating on its own — you do not need to call \`register_session\` or
38
+ \`heartbeat\` to appear online. Call **whoami** to CONFIRM your identity,
39
+ project, connection and terminal mirror. A \`mirror.state\` of \`off\` only
40
+ means there is no tmux pane to stream; you are still connected.
41
+ - **If a tool returns \`profile_ambiguous\`**, this repo defines several
42
+ identities and nothing says which one this terminal is. Never guess a
43
+ profile. Do not guess based on cwd, hostname, or any other assumption — ask
44
+ your human which to use: show each \`display_name\`, note which is
45
+ \`recommended\`, and warn that an \`in_use\` one is already taken by another
46
+ terminal — then call **select_profile** with their answer. Picking wrong
47
+ makes two terminals the same agent, sharing one inbox and one read cursor.
48
+ You are asked once per terminal; the choice is remembered across reconnects.
49
+ If **select_profile** returns a \`warning\`, repeat it to your human before you
50
+ do anything else — another terminal is already using that identity.
51
+ - Call **list_agents** to see who else is on the project and **list_channels** for
52
+ the rooms you can post in. If other agents are active, coordinate rather than
53
+ duplicate work.
54
+
55
+ ## Messaging — stay in the loop
56
+ - Call **check_messages** regularly — at minimum when you finish a task, before
57
+ you start a new one, and after a long stretch of work. It returns only what you
58
+ have NOT marked read, across every channel you're in. After you've read AND
59
+ acted on them, call **mark_read** for each channel with the highest message id
60
+ you handled — otherwise you'll be handed the same messages again.
61
+ - Messages are either **broadcast** or **directed** at specific members. If a
62
+ message is prefixed with **"🚫 DO NOT ACT, YOU ARE NOT THE TARGET"**, someone
63
+ else owns that work: you may chat or add information, but must NOT execute the
64
+ task.
65
+ - **send_message** posts markdown to a channel (find ids with **list_channels**,
66
+ people with **list_agents**). To share a file — a spec, log, diff, screenshot,
67
+ or anything a teammate should open as a file — pass its path in the **\`files\`
68
+ array**. Do NOT paste large file contents into the message body; attach the
69
+ file instead.
70
+ - **Referencing tasks vs GitHub issues.** In any message, activity note or task
71
+ description, \`#12\` and \`TASK#12\` render as a clickable **TaskFlow task** chip.
72
+ So write a **GitHub issue** as \`#gh12\` (or \`#GH12\`) and a **chat message**
73
+ as \`#msg12\` — never a bare \`#12\` for either.
74
+ A bare \`#12\` meaning a GitHub issue becomes a task chip pointing at TaskFlow
75
+ task 12, which is a different thing or does not exist at all, and it misleads
76
+ whoever reads it. \`#gh12\` renders as a distinct chip linking to the issue on
77
+ the project's linked repo.
78
+ - **download_attachment** fetches a file a teammate attached. It returns a PATH,
79
+ not the contents — open it with your own file tools, and check \`size_bytes\`
80
+ before reading anything wholesale.
81
+
82
+ ## Tasks — the work
83
+ - Track real work as tasks. **list_tasks** (filter by \`status\`, or
84
+ \`assigned='me'\`), **create_task**, **claim_task**, **update_task_status**,
85
+ **report_review**. Search/list before creating so you don't duplicate an
86
+ existing task.
87
+ - Typical lifecycle: pick a ready task → **claim_task** → set **in_progress** →
88
+ do the work → set **partial_done** to request human review → a reviewer runs
89
+ **report_review** (\`approved\` | \`changes_requested\`) → **done** on approval,
90
+ or back to **in_progress** to address changes.
91
+ - Statuses are a fixed set: \`not_started\`, \`in_progress\`, \`paused\`,
92
+ \`blocked\`, \`partial_done\`, \`done\`, \`archived\`. Don't invent values. Never
93
+ silently fix a bug — open or reopen a task first, then track the fix.
94
+
95
+ ## Activity & terminal — be transparent
96
+ - **log_activity** records a notable action (optionally linked to a task);
97
+ **get_activity** reads recent history. Use it as a running journal so humans and
98
+ other agents can follow what you did and why, without reading all of chat.
99
+ To mirror a note onto a task's linked GitHub issue, call it with a \`task\` and
100
+ \`post_to_github: true\` — it posts as a comment under your owner's identity, but
101
+ only if the project is GitHub-linked, the task is published as an issue, and your
102
+ owner is connected + opted in (\`post_as_me\`); otherwise it just records the
103
+ activity. Don't spam issues — reserve it for updates worth a public comment.
104
+ - **capture_terminal** streams terminal output to the dashboard so humans can
105
+ watch your work live.
106
+ - Tell people what you're doing: announce significant actions in chat or activity,
107
+ so the user can reconstruct what happened from your messages alone.
108
+
109
+ ## Etiquette
110
+ Keep humans informed through messages and activity; prefer attaching files over
111
+ dumping large content inline; always **mark_read** after handling messages;
112
+ respect the **🚫 DO NOT ACT** marker; coordinate with other agents instead of
113
+ stepping on their work; and act under the right **profile** for the job.`;
114
+ //# sourceMappingURL=instructions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"instructions.js","sourceRoot":"","sources":["../src/instructions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;yEAqGuC,CAAC"}
package/dist/mint.d.ts ADDED
@@ -0,0 +1,62 @@
1
+ /**
2
+ * `taskflow-mcp --mint <name>` — create a NEW agent identity and record it as
3
+ * a profile in `.taskflow.json`.
4
+ *
5
+ * ## Why this is a CLI and not an MCP tool
6
+ *
7
+ * `POST /api/taskflow/agents/link` is gated on `RequireAuth` plus an active
8
+ * project membership, and it stamps the linking human onto the agent. An agent
9
+ * authenticates with `Authorization: Agent <key>` and would simply be refused.
10
+ *
11
+ * That gate is correct and this command deliberately does not work around it:
12
+ * an agent able to mint sibling agents could grow an unbounded roster that no
13
+ * human vouched for. Creating an identity is a human act, so a human runs this.
14
+ *
15
+ * ## What it is for
16
+ *
17
+ * Identity is `project + profile`. Two terminals sharing the `main` profile are
18
+ * ONE agent — same roster entry, and, more importantly, one shared DM inbox and
19
+ * one shared read cursor. Giving the second terminal its own profile is what
20
+ * makes them distinguishable.
21
+ */
22
+ import { type RawProfile, type TaskflowConfig } from "./config.js";
23
+ /** A clear, user-facing failure (never a raw stack). */
24
+ export declare class MintError extends Error {
25
+ constructor(message: string);
26
+ }
27
+ export interface MintArgs {
28
+ name: string;
29
+ displayName: string;
30
+ token: string | undefined;
31
+ }
32
+ /**
33
+ * Parse `--mint <name> [--display-name <s>] [--token <t>]`.
34
+ *
35
+ * The token falls back to `TASKFLOW_USER_TOKEN`, but an explicit `--token`
36
+ * wins — a one-off mint against another account must not silently pick up
37
+ * whatever is exported in the shell.
38
+ */
39
+ export declare function parseMintArgs(argv: string[], env?: NodeJS.ProcessEnv): MintArgs;
40
+ /**
41
+ * Return a copy of `config` with `profile` recorded under `name`.
42
+ *
43
+ * Pure, and deliberately non-destructive in three ways:
44
+ * - an existing profile is never overwritten (its key is unrecoverable, and it
45
+ * is what a running terminal authenticates with)
46
+ * - `default_profile` never moves, so existing terminals keep their identity
47
+ * - every other field is carried through untouched
48
+ */
49
+ export declare function addProfile(config: TaskflowConfig, name: string, profile: RawProfile): TaskflowConfig;
50
+ /** The line the operator runs to start the other terminal as the new identity. */
51
+ export declare function exportHint(name: string): string;
52
+ /** The subset of `fetch` this module uses; injectable so tests see the real request. */
53
+ export type FetchLike = (url: string, init: RequestInit) => Promise<Response>;
54
+ /** Mint against the server and return the profile block it hands back. */
55
+ export declare function requestMint(server: string, project: number, args: MintArgs, token: string, doFetch?: FetchLike): Promise<RawProfile>;
56
+ /**
57
+ * Run the command. Returns the lines to print.
58
+ *
59
+ * The write is last: a failed mint must not leave a half-edited config, and a
60
+ * successful mint must not lose its key to a formatting error.
61
+ */
62
+ export declare function runMint(argv: string[], env?: NodeJS.ProcessEnv): Promise<string[]>;