daemora 1.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 (115) hide show
  1. package/README.md +666 -0
  2. package/SOUL.md +104 -0
  3. package/config/hooks.json +14 -0
  4. package/config/mcp.json +145 -0
  5. package/package.json +86 -0
  6. package/skills/.gitkeep +0 -0
  7. package/skills/apple-notes.md +193 -0
  8. package/skills/apple-reminders.md +189 -0
  9. package/skills/camsnap.md +162 -0
  10. package/skills/coding.md +14 -0
  11. package/skills/documents.md +13 -0
  12. package/skills/email.md +13 -0
  13. package/skills/gif-search.md +196 -0
  14. package/skills/healthcheck.md +225 -0
  15. package/skills/image-gen.md +147 -0
  16. package/skills/model-usage.md +182 -0
  17. package/skills/obsidian.md +207 -0
  18. package/skills/pdf.md +211 -0
  19. package/skills/research.md +13 -0
  20. package/skills/skill-creator.md +142 -0
  21. package/skills/spotify.md +149 -0
  22. package/skills/summarize.md +230 -0
  23. package/skills/things.md +199 -0
  24. package/skills/tmux.md +204 -0
  25. package/skills/trello.md +183 -0
  26. package/skills/video-frames.md +202 -0
  27. package/skills/weather.md +127 -0
  28. package/src/a2a/A2AClient.js +136 -0
  29. package/src/a2a/A2AServer.js +316 -0
  30. package/src/a2a/AgentCard.js +79 -0
  31. package/src/agents/SubAgentManager.js +369 -0
  32. package/src/agents/Supervisor.js +192 -0
  33. package/src/channels/BaseChannel.js +104 -0
  34. package/src/channels/DiscordChannel.js +288 -0
  35. package/src/channels/EmailChannel.js +172 -0
  36. package/src/channels/GoogleChatChannel.js +316 -0
  37. package/src/channels/HttpChannel.js +26 -0
  38. package/src/channels/LineChannel.js +168 -0
  39. package/src/channels/SignalChannel.js +186 -0
  40. package/src/channels/SlackChannel.js +329 -0
  41. package/src/channels/TeamsChannel.js +272 -0
  42. package/src/channels/TelegramChannel.js +347 -0
  43. package/src/channels/WhatsAppChannel.js +219 -0
  44. package/src/channels/index.js +198 -0
  45. package/src/cli.js +1267 -0
  46. package/src/config/agentProfiles.js +120 -0
  47. package/src/config/channels.js +32 -0
  48. package/src/config/default.js +206 -0
  49. package/src/config/models.js +123 -0
  50. package/src/config/permissions.js +167 -0
  51. package/src/core/AgentLoop.js +446 -0
  52. package/src/core/Compaction.js +143 -0
  53. package/src/core/CostTracker.js +116 -0
  54. package/src/core/EventBus.js +46 -0
  55. package/src/core/Task.js +67 -0
  56. package/src/core/TaskQueue.js +206 -0
  57. package/src/core/TaskRunner.js +226 -0
  58. package/src/daemon/DaemonManager.js +301 -0
  59. package/src/hooks/HookRunner.js +230 -0
  60. package/src/index.js +482 -0
  61. package/src/mcp/MCPAgentRunner.js +112 -0
  62. package/src/mcp/MCPClient.js +186 -0
  63. package/src/mcp/MCPManager.js +412 -0
  64. package/src/models/ModelRouter.js +180 -0
  65. package/src/safety/AuditLog.js +135 -0
  66. package/src/safety/CircuitBreaker.js +126 -0
  67. package/src/safety/FilesystemGuard.js +169 -0
  68. package/src/safety/GitRollback.js +139 -0
  69. package/src/safety/HumanApproval.js +156 -0
  70. package/src/safety/InputSanitizer.js +72 -0
  71. package/src/safety/PermissionGuard.js +83 -0
  72. package/src/safety/Sandbox.js +70 -0
  73. package/src/safety/SecretScanner.js +100 -0
  74. package/src/safety/SecretVault.js +250 -0
  75. package/src/scheduler/Heartbeat.js +115 -0
  76. package/src/scheduler/Scheduler.js +228 -0
  77. package/src/services/models/outputSchema.js +15 -0
  78. package/src/services/openai.js +25 -0
  79. package/src/services/sessions.js +65 -0
  80. package/src/setup/theme.js +110 -0
  81. package/src/setup/wizard.js +788 -0
  82. package/src/skills/SkillLoader.js +168 -0
  83. package/src/storage/TaskStore.js +69 -0
  84. package/src/systemPrompt.js +526 -0
  85. package/src/tenants/TenantContext.js +19 -0
  86. package/src/tenants/TenantManager.js +379 -0
  87. package/src/tools/ToolRegistry.js +141 -0
  88. package/src/tools/applyPatch.js +144 -0
  89. package/src/tools/browserAutomation.js +223 -0
  90. package/src/tools/createDocument.js +265 -0
  91. package/src/tools/cronTool.js +105 -0
  92. package/src/tools/editFile.js +139 -0
  93. package/src/tools/executeCommand.js +123 -0
  94. package/src/tools/glob.js +67 -0
  95. package/src/tools/grep.js +121 -0
  96. package/src/tools/imageAnalysis.js +120 -0
  97. package/src/tools/index.js +173 -0
  98. package/src/tools/listDirectory.js +47 -0
  99. package/src/tools/manageAgents.js +47 -0
  100. package/src/tools/manageMCP.js +159 -0
  101. package/src/tools/memory.js +478 -0
  102. package/src/tools/messageChannel.js +45 -0
  103. package/src/tools/projectTracker.js +259 -0
  104. package/src/tools/readFile.js +52 -0
  105. package/src/tools/screenCapture.js +112 -0
  106. package/src/tools/searchContent.js +76 -0
  107. package/src/tools/searchFiles.js +75 -0
  108. package/src/tools/sendEmail.js +118 -0
  109. package/src/tools/sendFile.js +63 -0
  110. package/src/tools/textToSpeech.js +161 -0
  111. package/src/tools/transcribeAudio.js +82 -0
  112. package/src/tools/useMCP.js +29 -0
  113. package/src/tools/webFetch.js +150 -0
  114. package/src/tools/webSearch.js +134 -0
  115. package/src/tools/writeFile.js +26 -0
@@ -0,0 +1,127 @@
1
+ ---
2
+ name: weather
3
+ description: Get current weather conditions, forecasts, hourly data, and rain predictions for any city, airport, or coordinates. Use when the user asks about weather, temperature, rain, wind, humidity, UV index, or travel forecasts. No API key required.
4
+ triggers: weather, temperature, rain, forecast, wind, humidity, hot, cold, snow, sunny, cloudy, storm, UV, climate, degrees
5
+ ---
6
+
7
+ ## When to Use
8
+
9
+ ✅ Current conditions, today's forecast, multi-day forecast, rain prediction, travel weather, "will it rain?", "is it cold in [city]?"
10
+
11
+ ❌ Historical weather archives, climate trends, aviation METAR/TAF, severe weather emergency alerts — use official sources for those.
12
+
13
+ ## Quick Commands (no API key)
14
+
15
+ ```bash
16
+ # One-line summary — best for quick answers
17
+ curl -s "wttr.in/London?format=3"
18
+ # → London: ⛅️ +18°C
19
+
20
+ # Full current conditions + 3-day forecast
21
+ curl -s "wttr.in/London"
22
+
23
+ # Specific city with spaces
24
+ curl -s "wttr.in/New+York"
25
+
26
+ # Airport code (IATA)
27
+ curl -s "wttr.in/JFK"
28
+
29
+ # Coordinates
30
+ curl -s "wttr.in/48.8566,2.3522" # Paris lat/lon
31
+
32
+ # JSON — parse programmatically
33
+ curl -s "wttr.in/London?format=j1" | python3 -c "
34
+ import sys, json
35
+ d = json.load(sys.stdin)
36
+ c = d['current_condition'][0]
37
+ print(f\"Temp: {c['temp_C']}°C / {c['temp_F']}°F\")
38
+ print(f\"Feels like: {c['FeelsLikeC']}°C\")
39
+ print(f\"Humidity: {c['humidity']}%\")
40
+ print(f\"Wind: {c['windspeedKmph']} km/h {c['winddir16Point']}\")
41
+ print(f\"Condition: {c['weatherDesc'][0]['value']}\")
42
+ "
43
+ ```
44
+
45
+ ## Forecast Queries
46
+
47
+ ```bash
48
+ # Today only
49
+ curl -s "wttr.in/Tokyo?1"
50
+
51
+ # Tomorrow only
52
+ curl -s "wttr.in/Tokyo?2"
53
+
54
+ # 3-day compact view
55
+ curl -s "wttr.in/Tokyo?format=v2"
56
+
57
+ # Moon phase bonus
58
+ curl -s "wttr.in/~moon"
59
+ ```
60
+
61
+ ## Custom Format Codes
62
+
63
+ Build exactly the output you want:
64
+
65
+ | Code | Meaning |
66
+ |------|---------|
67
+ | `%l` | Location name |
68
+ | `%c` | Condition emoji |
69
+ | `%t` | Temperature |
70
+ | `%f` | Feels like |
71
+ | `%h` | Humidity % |
72
+ | `%w` | Wind speed + direction |
73
+ | `%p` | Precipitation (mm) |
74
+ | `%P` | Pressure (hPa) |
75
+ | `%u` | UV index |
76
+ | `%S` | Sunrise |
77
+ | `%s` | Sunset |
78
+
79
+ ```bash
80
+ # Rich one-liner
81
+ curl -s "wttr.in/London?format=%l:+%c+%t+(feels+%f)+💨%w+💧%h+☔%p"
82
+ # → London: ⛅ +17°C (feels +14°C) 💨 18km/h W 💧 72% ☔ 0.0mm
83
+
84
+ # Travel check — sunrise/sunset
85
+ curl -s "wttr.in/Dubai?format=%l:+%c+%t+🌅%S+🌇%s"
86
+ ```
87
+
88
+ ## "Will it rain?" — Rain Probability
89
+
90
+ ```bash
91
+ # Parse hourly rain chance from JSON
92
+ curl -s "wttr.in/London?format=j1" | python3 -c "
93
+ import sys, json
94
+ d = json.load(sys.stdin)
95
+ print('Rain chance by period today:')
96
+ for period in d['weather'][0]['hourly']:
97
+ t = period['time'].zfill(4)
98
+ print(f\" {t[:2]}:00 — {period['chanceofrain']}% rain, {period['tempC']}°C\")
99
+ "
100
+ ```
101
+
102
+ ## Multiple Cities Comparison
103
+
104
+ ```bash
105
+ for city in "London" "New+York" "Tokyo" "Sydney"; do
106
+ curl -s "wttr.in/${city}?format=%l:+%c+%t+💧%h" &
107
+ done
108
+ wait
109
+ ```
110
+
111
+ ## Error Handling
112
+
113
+ - If `wttr.in` is slow or down: fall back to `curl -s "wttr.in/${city}?format=j1"` (JSON is more reliable than HTML)
114
+ - Unknown city → wttr.in returns nearest match or error; try airport code instead
115
+ - Rate limited → wait 10s and retry once; if still failing, report the outage
116
+
117
+ ## Output Format for Users
118
+
119
+ Always present weather in a clean, scannable format:
120
+
121
+ ```
122
+ 📍 London, UK
123
+ 🌤 Partly cloudy · 17°C (feels like 14°C)
124
+ 💨 Wind: 18 km/h W · 💧 Humidity: 72%
125
+ 🌧 Rain chance: 10% today, 60% tomorrow
126
+ 🌅 Sunrise: 06:42 · 🌇 Sunset: 20:15
127
+ ```
@@ -0,0 +1,136 @@
1
+ /**
2
+ * A2A Client — delegates tasks to external agents via A2A protocol.
3
+ *
4
+ * Flow:
5
+ * 1. Discover agent: fetch /.well-known/agent.json
6
+ * 2. Send task: POST /a2a/tasks
7
+ * 3. Poll for result: GET /a2a/tasks/:id (or stream via SSE)
8
+ */
9
+
10
+ /**
11
+ * Discover an agent's capabilities.
12
+ * @param {string} agentUrl - Base URL of the agent
13
+ * @returns {object} Agent card
14
+ */
15
+ export async function discoverAgent(agentUrl) {
16
+ const url = `${agentUrl.replace(/\/$/, "")}/.well-known/agent.json`;
17
+
18
+ const response = await fetch(url, {
19
+ signal: AbortSignal.timeout(10000),
20
+ });
21
+
22
+ if (!response.ok) {
23
+ throw new Error(`Agent discovery failed: ${response.status}`);
24
+ }
25
+
26
+ return response.json();
27
+ }
28
+
29
+ /**
30
+ * Send a task to an external agent.
31
+ * @param {string} agentUrl - Base URL of the agent
32
+ * @param {string} taskInput - The task to send
33
+ * @returns {object} Task response with id and status
34
+ */
35
+ export async function sendTaskToAgent(agentUrl, taskInput) {
36
+ const baseUrl = agentUrl.replace(/\/$/, "");
37
+
38
+ const response = await fetch(`${baseUrl}/a2a/tasks`, {
39
+ method: "POST",
40
+ headers: { "Content-Type": "application/json" },
41
+ body: JSON.stringify({
42
+ jsonrpc: "2.0",
43
+ method: "tasks/send",
44
+ params: {
45
+ message: {
46
+ role: "user",
47
+ parts: [{ type: "text", text: taskInput }],
48
+ },
49
+ },
50
+ }),
51
+ signal: AbortSignal.timeout(30000),
52
+ });
53
+
54
+ if (!response.ok) {
55
+ throw new Error(`A2A task submission failed: ${response.status}`);
56
+ }
57
+
58
+ return response.json();
59
+ }
60
+
61
+ /**
62
+ * Poll for task completion.
63
+ * @param {string} agentUrl - Base URL of the agent
64
+ * @param {string} taskId - Task ID to poll
65
+ * @param {number} maxWaitMs - Maximum wait time in ms (default: 120000)
66
+ * @returns {object} Completed task
67
+ */
68
+ export async function pollTaskResult(agentUrl, taskId, maxWaitMs = 120000) {
69
+ const baseUrl = agentUrl.replace(/\/$/, "");
70
+ const start = Date.now();
71
+
72
+ while (Date.now() - start < maxWaitMs) {
73
+ const response = await fetch(`${baseUrl}/a2a/tasks/${taskId}`, {
74
+ signal: AbortSignal.timeout(10000),
75
+ });
76
+
77
+ if (!response.ok) {
78
+ throw new Error(`A2A task poll failed: ${response.status}`);
79
+ }
80
+
81
+ const data = await response.json();
82
+ const state = data.result?.status?.state;
83
+
84
+ if (state === "completed" || state === "failed") {
85
+ const text =
86
+ data.result?.status?.message?.parts
87
+ ?.filter((p) => p.type === "text")
88
+ .map((p) => p.text)
89
+ .join("\n") || "";
90
+ return { state, text };
91
+ }
92
+
93
+ // Wait before next poll
94
+ await new Promise((resolve) => setTimeout(resolve, 2000));
95
+ }
96
+
97
+ throw new Error("A2A task timed out");
98
+ }
99
+
100
+ /**
101
+ * Tool function: delegate a task to an external agent.
102
+ * Used by the agent as a tool call.
103
+ */
104
+ export async function delegateToAgent(agentUrl, taskInput) {
105
+ console.log(` [A2A] Delegating to ${agentUrl}: "${taskInput.slice(0, 80)}"`);
106
+
107
+ try {
108
+ // Discover agent capabilities
109
+ const card = await discoverAgent(agentUrl);
110
+ console.log(
111
+ ` [A2A] Agent: ${card.name} — ${card.skills?.length || 0} skills`
112
+ );
113
+
114
+ // Send task
115
+ const submitResult = await sendTaskToAgent(agentUrl, taskInput);
116
+ const taskId = submitResult.result?.id;
117
+
118
+ if (!taskId) {
119
+ return `A2A task submission failed: no task ID returned`;
120
+ }
121
+
122
+ console.log(` [A2A] Task submitted: ${taskId}`);
123
+
124
+ // Poll for result
125
+ const result = await pollTaskResult(agentUrl, taskId);
126
+ console.log(` [A2A] Task ${taskId} ${result.state}`);
127
+
128
+ return result.text || `Task ${result.state}`;
129
+ } catch (error) {
130
+ console.log(` [A2A] Error: ${error.message}`);
131
+ return `A2A delegation failed: ${error.message}`;
132
+ }
133
+ }
134
+
135
+ export const delegateToAgentDescription =
136
+ 'delegateToAgent(agentUrl: string, taskInput: string) - Delegates a task to another AI agent via A2A protocol. The external agent processes the task and returns the result. agentUrl is the base URL (e.g., "http://localhost:8082").';
@@ -0,0 +1,316 @@
1
+ import taskQueue from "../core/TaskQueue.js";
2
+ import { loadTask } from "../storage/TaskStore.js";
3
+ import eventBus from "../core/EventBus.js";
4
+ import { config } from "../config/default.js";
5
+ import inputSanitizer from "../safety/InputSanitizer.js";
6
+
7
+ /**
8
+ * A2A Server — receives tasks from other agents via A2A protocol.
9
+ *
10
+ * SECURITY: A2A is the #1 attack surface. A rogue agent can:
11
+ * 1. Send malicious tasks (prompt injection → file delete, email exfil)
12
+ * 2. Flood tasks (cost/resource exhaustion)
13
+ * 3. Probe capabilities via Agent Card
14
+ *
15
+ * Mitigations:
16
+ * - DISABLED by default (A2A_ENABLED=true to opt in)
17
+ * - Bearer token auth (A2A_AUTH_TOKEN)
18
+ * - Agent URL allowlist (A2A_ALLOWED_AGENTS)
19
+ * - Forced "minimal" permission tier for A2A tasks (read-only by default)
20
+ * - Lower cost budget per A2A task
21
+ * - Rate limiting (5/min default)
22
+ * - Input wrapped with <untrusted-content> tags
23
+ * - Dangerous tools blocked (executeCommand, writeFile, sendEmail, etc.)
24
+ */
25
+
26
+ // Rate limiter state
27
+ const rateLimiter = {
28
+ timestamps: [],
29
+ check() {
30
+ const now = Date.now();
31
+ const windowMs = 60000;
32
+ // Remove old entries
33
+ this.timestamps = this.timestamps.filter((t) => now - t < windowMs);
34
+ if (this.timestamps.length >= config.a2a.rateLimitPerMinute) {
35
+ return false;
36
+ }
37
+ this.timestamps.push(now);
38
+ return true;
39
+ },
40
+ };
41
+
42
+ export function mountA2AServer(app) {
43
+ /**
44
+ * A2A authentication + authorization middleware.
45
+ */
46
+ function a2aAuth(req, res, next) {
47
+ // Check if A2A is enabled
48
+ if (!config.a2a.enabled) {
49
+ return res.status(403).json({
50
+ jsonrpc: "2.0",
51
+ error: {
52
+ code: -32001,
53
+ message: "A2A protocol is disabled. Set A2A_ENABLED=true to enable.",
54
+ },
55
+ });
56
+ }
57
+
58
+ // Check Bearer token if configured
59
+ if (config.a2a.authToken) {
60
+ const authHeader = req.headers.authorization;
61
+ if (!authHeader || authHeader !== `Bearer ${config.a2a.authToken}`) {
62
+ eventBus.emitEvent("a2a:auth_failed", {
63
+ ip: req.ip,
64
+ reason: "Invalid or missing auth token",
65
+ });
66
+ return res.status(401).json({
67
+ jsonrpc: "2.0",
68
+ error: { code: -32002, message: "Authentication required" },
69
+ });
70
+ }
71
+ }
72
+
73
+ // Check agent allowlist
74
+ if (config.a2a.allowedAgents.length > 0) {
75
+ const origin = req.headers.origin || req.headers.referer || "";
76
+ const agentUrl = req.headers["x-agent-url"] || "";
77
+ const allowed = config.a2a.allowedAgents.some(
78
+ (a) => origin.includes(a) || agentUrl.includes(a) || a === "*"
79
+ );
80
+
81
+ if (!allowed) {
82
+ eventBus.emitEvent("a2a:agent_rejected", {
83
+ ip: req.ip,
84
+ origin,
85
+ agentUrl,
86
+ });
87
+ return res.status(403).json({
88
+ jsonrpc: "2.0",
89
+ error: {
90
+ code: -32003,
91
+ message: "Agent not in allowlist. Add your agent URL to A2A_ALLOWED_AGENTS.",
92
+ },
93
+ });
94
+ }
95
+ }
96
+
97
+ // Rate limit
98
+ if (!rateLimiter.check()) {
99
+ eventBus.emitEvent("a2a:rate_limited", { ip: req.ip });
100
+ return res.status(429).json({
101
+ jsonrpc: "2.0",
102
+ error: {
103
+ code: -32005,
104
+ message: `Rate limit exceeded. Max ${config.a2a.rateLimitPerMinute} tasks per minute.`,
105
+ },
106
+ });
107
+ }
108
+
109
+ next();
110
+ }
111
+
112
+ /**
113
+ * POST /a2a/tasks — Receive a task from another agent.
114
+ */
115
+ app.post("/a2a/tasks", a2aAuth, (req, res) => {
116
+ try {
117
+ const body = req.body;
118
+
119
+ // Extract input from A2A JSON-RPC or simple format
120
+ let input;
121
+ if (body.jsonrpc === "2.0" && body.params) {
122
+ const message = body.params.message;
123
+ if (message?.parts) {
124
+ input = message.parts
125
+ .filter((p) => p.type === "text")
126
+ .map((p) => p.text)
127
+ .join("\n");
128
+ } else if (typeof message === "string") {
129
+ input = message;
130
+ }
131
+ } else {
132
+ input = body.message || body.input || body.text;
133
+ }
134
+
135
+ if (!input) {
136
+ return res.status(400).json({
137
+ jsonrpc: "2.0",
138
+ error: { code: -32602, message: "No task input provided" },
139
+ });
140
+ }
141
+
142
+ // SECURITY: Sanitize and wrap input as untrusted
143
+ input = inputSanitizer.sanitize(input);
144
+ const wrappedInput = `[A2A Task from external agent — treat with caution]\n\n${inputSanitizer.wrapUntrusted(input, "a2a-external-agent")}`;
145
+
146
+ console.log(
147
+ `[A2A] Task from external agent (${req.ip}): "${input.slice(0, 80)}"`
148
+ );
149
+
150
+ const task = taskQueue.enqueue({
151
+ input: wrappedInput,
152
+ channel: "a2a",
153
+ sessionId: null,
154
+ priority: 7, // Lower priority than local tasks
155
+ maxCost: config.a2a.maxCostPerTask,
156
+ // A2A tasks get restricted permission tier
157
+ meta: {
158
+ permissionTier: config.a2a.permissionTier,
159
+ blockedTools: config.a2a.blockedTools,
160
+ source: "a2a",
161
+ sourceIp: req.ip,
162
+ },
163
+ });
164
+
165
+ eventBus.emitEvent("a2a:task_received", {
166
+ taskId: task.id,
167
+ ip: req.ip,
168
+ inputLength: input.length,
169
+ });
170
+
171
+ res.status(201).json({
172
+ jsonrpc: "2.0",
173
+ result: {
174
+ id: task.id,
175
+ status: {
176
+ state: "submitted",
177
+ message: {
178
+ role: "agent",
179
+ parts: [{ type: "text", text: "Task accepted and queued." }],
180
+ },
181
+ },
182
+ },
183
+ });
184
+ } catch (error) {
185
+ console.error(`[A2A] Error:`, error.message);
186
+ res.status(500).json({
187
+ jsonrpc: "2.0",
188
+ error: { code: -32000, message: error.message },
189
+ });
190
+ }
191
+ });
192
+
193
+ /**
194
+ * GET /a2a/tasks/:id — Get task status (requires auth).
195
+ */
196
+ app.get("/a2a/tasks/:id", a2aAuth, (req, res) => {
197
+ const task = loadTask(req.params.id);
198
+ if (!task) {
199
+ return res.status(404).json({
200
+ jsonrpc: "2.0",
201
+ error: { code: -32004, message: "Task not found" },
202
+ });
203
+ }
204
+
205
+ // Only expose A2A tasks to A2A clients
206
+ if (task.channel !== "a2a") {
207
+ return res.status(404).json({
208
+ jsonrpc: "2.0",
209
+ error: { code: -32004, message: "Task not found" },
210
+ });
211
+ }
212
+
213
+ const stateMap = {
214
+ pending: "submitted",
215
+ running: "working",
216
+ completed: "completed",
217
+ failed: "failed",
218
+ };
219
+
220
+ const result = {
221
+ id: task.id,
222
+ status: { state: stateMap[task.status] || task.status },
223
+ };
224
+
225
+ if (task.status === "completed" && task.result) {
226
+ result.status.message = {
227
+ role: "agent",
228
+ parts: [{ type: "text", text: task.result }],
229
+ };
230
+ }
231
+
232
+ if (task.status === "failed" && task.error) {
233
+ result.status.message = {
234
+ role: "agent",
235
+ parts: [{ type: "text", text: `Error: ${task.error}` }],
236
+ };
237
+ }
238
+
239
+ res.json({ jsonrpc: "2.0", result });
240
+ });
241
+
242
+ /**
243
+ * GET /a2a/tasks/:id/stream — SSE stream of task progress (requires auth).
244
+ */
245
+ app.get("/a2a/tasks/:id/stream", a2aAuth, (req, res) => {
246
+ const taskId = req.params.id;
247
+ const task = loadTask(taskId);
248
+
249
+ if (!task || task.channel !== "a2a") {
250
+ return res.status(404).json({ error: "Task not found" });
251
+ }
252
+
253
+ res.writeHead(200, {
254
+ "Content-Type": "text/event-stream",
255
+ "Cache-Control": "no-cache",
256
+ Connection: "keep-alive",
257
+ });
258
+
259
+ res.write(
260
+ `data: ${JSON.stringify({ type: "status", state: task.status })}\n\n`
261
+ );
262
+
263
+ if (task.status === "completed" || task.status === "failed") {
264
+ res.write(
265
+ `data: ${JSON.stringify({
266
+ type: "result",
267
+ state: task.status,
268
+ text: task.result || task.error,
269
+ })}\n\n`
270
+ );
271
+ res.end();
272
+ return;
273
+ }
274
+
275
+ const onCompleted = (data) => {
276
+ if (data.taskId === taskId) {
277
+ res.write(
278
+ `data: ${JSON.stringify({
279
+ type: "result",
280
+ state: "completed",
281
+ text: data.result,
282
+ })}\n\n`
283
+ );
284
+ cleanup();
285
+ }
286
+ };
287
+
288
+ const onFailed = (data) => {
289
+ if (data.taskId === taskId) {
290
+ res.write(
291
+ `data: ${JSON.stringify({
292
+ type: "result",
293
+ state: "failed",
294
+ text: data.error,
295
+ })}\n\n`
296
+ );
297
+ cleanup();
298
+ }
299
+ };
300
+
301
+ eventBus.on("task:completed", onCompleted);
302
+ eventBus.on("task:failed", onFailed);
303
+
304
+ const cleanup = () => {
305
+ eventBus.off("task:completed", onCompleted);
306
+ eventBus.off("task:failed", onFailed);
307
+ res.end();
308
+ };
309
+
310
+ req.on("close", cleanup);
311
+ setTimeout(cleanup, 300000);
312
+ });
313
+
314
+ const status = config.a2a.enabled ? "ENABLED" : "DISABLED (set A2A_ENABLED=true)";
315
+ console.log(`[A2A] Server endpoints mounted — ${status}`);
316
+ }
@@ -0,0 +1,79 @@
1
+ import { config } from "../config/default.js";
2
+
3
+ /**
4
+ * A2A Agent Card — serves agent capabilities at /.well-known/agent.json
5
+ *
6
+ * SECURITY: Only serves the card when A2A is enabled.
7
+ * Does NOT expose internal tools or file system capabilities.
8
+ */
9
+ export function getAgentCard() {
10
+ return {
11
+ name: "Daemora",
12
+ description:
13
+ "A multi-agent digital worker. Handles research, analysis, and general tasks.",
14
+ url: `http://localhost:${config.port}`,
15
+ version: "1.0.0",
16
+ protocol: "a2a/1.0",
17
+
18
+ capabilities: {
19
+ streaming: true,
20
+ pushNotifications: false,
21
+ stateTransitions: true,
22
+ },
23
+
24
+ // Only expose safe, high-level skill categories — NOT internal tools
25
+ skills: [
26
+ {
27
+ id: "research",
28
+ name: "Web Research",
29
+ description: "Search the web and synthesize information.",
30
+ tags: ["research", "search", "web"],
31
+ },
32
+ {
33
+ id: "analysis",
34
+ name: "Text Analysis",
35
+ description: "Analyze text, summarize, answer questions.",
36
+ tags: ["analysis", "summary", "qa"],
37
+ },
38
+ {
39
+ id: "documents",
40
+ name: "Document Creation",
41
+ description: "Create markdown documents and reports.",
42
+ tags: ["documents", "markdown"],
43
+ },
44
+ ],
45
+
46
+ endpoints: {
47
+ tasks: `/a2a/tasks`,
48
+ taskStatus: `/a2a/tasks/:id`,
49
+ stream: `/a2a/tasks/:id/stream`,
50
+ },
51
+
52
+ authentication: {
53
+ type: config.a2a.authToken ? "bearer" : "none",
54
+ description: config.a2a.authToken
55
+ ? "Include Authorization: Bearer <token> header"
56
+ : "No authentication required",
57
+ },
58
+
59
+ security: {
60
+ permissionTier: config.a2a.permissionTier,
61
+ rateLimitPerMinute: config.a2a.rateLimitPerMinute,
62
+ note: "A2A tasks run with restricted permissions. Dangerous tools are blocked.",
63
+ },
64
+ };
65
+ }
66
+
67
+ /**
68
+ * Mount A2A discovery endpoint on Express app.
69
+ */
70
+ export function mountAgentCard(app) {
71
+ app.get("/.well-known/agent.json", (req, res) => {
72
+ if (!config.a2a.enabled) {
73
+ return res.status(404).json({
74
+ error: "A2A protocol is not enabled on this agent.",
75
+ });
76
+ }
77
+ res.json(getAgentCard());
78
+ });
79
+ }