@adaptic/maestro 1.5.2 → 1.6.1

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.
@@ -1,14 +1,29 @@
1
1
  #!/bin/bash
2
- # slack-send.sh — Send Slack message as Sophie via User OAuth Token (xoxp-)
3
- # Messages appear as Sophie's user account, NOT the bot app.
2
+ # slack-send.sh — Send Slack message as the running agent via User OAuth Token (xoxp-)
3
+ # Messages appear as the agent's own Slack user, NOT the bot app.
4
4
  # Usage: ./scripts/slack-send.sh <channel_id> <message> [--thread_ts <ts>]
5
5
  set -e
6
6
 
7
- source /Users/sophie/sophie-ai/.env
7
+ # Resolve the agent repo dir so we can source env files regardless of cwd.
8
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
9
+ AGENT_REPO_DIR="${AGENT_DIR:-$(cd "$SCRIPT_DIR/.." && pwd)}"
10
+
11
+ # Load identity vars (AGENT_FIRST_NAME, AGENT_FULL_NAME, AGENT_EMAIL, …) so
12
+ # nothing in this script needs to hardcode the agent's name or paths.
13
+ if [ -f "$AGENT_REPO_DIR/config/agent.env" ]; then
14
+ # shellcheck disable=SC1091
15
+ source "$AGENT_REPO_DIR/config/agent.env"
16
+ fi
8
17
 
9
- # ENFORCEMENT: Must use User Token (xoxp-), never Bot Token (xoxb-)
18
+ # Load secrets from the agent's .env (must live alongside agent.env).
19
+ if [ -f "$AGENT_REPO_DIR/.env" ]; then
20
+ # shellcheck disable=SC1091
21
+ source "$AGENT_REPO_DIR/.env"
22
+ fi
23
+
24
+ # ENFORCEMENT: Must use User Token (xoxp-), never Bot Token (xoxb-).
10
25
  # Per CEO directive: all Slack sends must come from user scope so messages
11
- # appear as Sophie Nguyen, not the Adaptic COO bot.
26
+ # appear as the running agent, not a generic bot app.
12
27
  SLACK_SEND_TOKEN="${SLACK_USER_TOKEN}"
13
28
  if [ -z "$SLACK_SEND_TOKEN" ]; then
14
29
  echo "ERROR: SLACK_USER_TOKEN not set in .env — cannot send as user scope" >&2
@@ -58,14 +73,15 @@ if [ -z "$CHANNEL" ] || [ -z "$MESSAGE" ]; then
58
73
  fi
59
74
 
60
75
  # Dedup check: if --responding_to is set, use atomic locking to prevent concurrent sends
61
- SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
62
76
  DEDUP_ACQUIRED=""
63
77
  if [ -n "$RESPONDING_TO" ]; then
64
- # Atomic lock acquisition (mkdir-based, race-safe)
78
+ # Atomic lock acquisition (mkdir-based, race-safe).
65
79
  # This is the primary dedup mechanism. mkdir is atomic on POSIX — if two sessions
66
80
  # race to respond to the same message, exactly one will succeed and the other will
67
81
  # get LOCKED and skip. This eliminates the check-then-write race condition.
68
- SESSION_ID="${SOPHIE_SESSION_ID:-$$}"
82
+ # AGENT_SESSION_ID is the per-agent session identifier (used to be SOPHIE_SESSION_ID,
83
+ # RAVI_SESSION_ID, etc.); $$ is the fallback when running outside a daemon-spawned shell.
84
+ SESSION_ID="${AGENT_SESSION_ID:-${SOPHIE_SESSION_ID:-${RAVI_SESSION_ID:-$$}}}"
69
85
  ACQUIRE_RESULT=$("$SCRIPT_DIR/slack-responded.sh" acquire "$CHANNEL" "$RESPONDING_TO" "$SESSION_ID" 2>/dev/null) || true
70
86
  if [ "$ACQUIRE_RESULT" != "ACQUIRED" ]; then
71
87
  echo "DEDUP_SKIP"
@@ -75,7 +91,7 @@ if [ -n "$RESPONDING_TO" ]; then
75
91
  fi
76
92
 
77
93
  # Show typing indicator before sending (non-blocking, fire-and-forget)
78
- # This makes Sophie's presence feel human — the "Sophie is typing..." dots appear
94
+ # This makes the agent's presence feel human — the "X is typing..." dots appear
79
95
  # before the message arrives. Requires rtm:stream scope on user token; degrades gracefully.
80
96
  TYPING_DURATION=2500
81
97
  MSG_LEN=${#MESSAGE}
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * Sophie SMS Handler — Twilio Inbound SMS Webhook
3
+ * Agent SMS Handler — Twilio Inbound SMS Webhook
4
4
  *
5
5
  * Receives inbound SMS/MMS via Twilio webhook, maps senders to known contacts,
6
6
  * writes messages to the inbox for the inbox processor, and logs everything.
@@ -31,13 +31,27 @@ import { URL } from "node:url";
31
31
  // ---------------------------------------------------------------------------
32
32
 
33
33
  const PORT = parseInt(process.env.SMS_PORT || "3001", 10);
34
- const SOPHIE_AI =
35
- process.env.SOPHIE_AI_PATH ||
36
- path.join(process.env.HOME || "/Users/sophie", "sophie-ai");
37
- const INBOX_DIR = path.join(SOPHIE_AI, "state", "inbox", "sms");
38
- const LOG_DIR = path.join(SOPHIE_AI, "logs", "sms");
39
- const CALLER_ID_MAP_PATH = path.join(SOPHIE_AI, "config", "caller-id-map.yaml");
40
- const SOPHIE_NUMBER = "+16282656712";
34
+ const AGENT_REPO_DIR =
35
+ process.env.AGENT_DIR ||
36
+ process.env.AGENT_REPO_DIR ||
37
+ path.resolve(path.dirname(new URL(import.meta.url).pathname), "..");
38
+
39
+ // Load identity from canonical SOT (config/agent.json).
40
+ let _agent = null;
41
+ function loadAgent() {
42
+ if (_agent) return _agent;
43
+ try {
44
+ _agent = JSON.parse(fs.readFileSync(path.join(AGENT_REPO_DIR, "config/agent.json"), "utf-8"));
45
+ } catch {
46
+ _agent = { firstName: "Agent", phone: "" };
47
+ }
48
+ return _agent;
49
+ }
50
+
51
+ const INBOX_DIR = path.join(AGENT_REPO_DIR, "state", "inbox", "sms");
52
+ const LOG_DIR = path.join(AGENT_REPO_DIR, "logs", "sms");
53
+ const CALLER_ID_MAP_PATH = path.join(AGENT_REPO_DIR, "config", "caller-id-map.yaml");
54
+ const AGENT_NUMBER = loadAgent().phone || "";
41
55
 
42
56
  // ---------------------------------------------------------------------------
43
57
  // Utilities
@@ -166,10 +180,10 @@ function logSms(entry) {
166
180
  }
167
181
 
168
182
  /**
169
- * Log to the main audit trail (sophie-ai/logs/audit/).
183
+ * Log to the agent's main audit trail (logs/audit/).
170
184
  */
171
185
  function logAudit(action, details) {
172
- const auditDir = path.join(SOPHIE_AI, "logs", "audit");
186
+ const auditDir = path.join(AGENT_REPO_DIR, "logs", "audit");
173
187
  ensureDir(auditDir);
174
188
 
175
189
  const auditFile = path.join(auditDir, `${today()}-actions.jsonl`);
@@ -229,13 +243,13 @@ function writeToInbox(smsData) {
229
243
  `subject: "SMS from ${senderName}"`,
230
244
  `content: |`,
231
245
  ` ${(smsData.Body || "").replace(/\n/g, "\n ")}`,
232
- `to: "${smsData.To || SOPHIE_NUMBER}"`,
246
+ `to: "${smsData.To || AGENT_NUMBER}"`,
233
247
  `is_reply: false`,
234
248
  `priority_signals:`,
235
249
  ` from_ceo: ${senderPrivilege === "ceo"}`,
236
250
  ` tagged_urgent: false`,
237
251
  ` contains_deadline: false`,
238
- ` mentions_sophie: false`,
252
+ ` mentions_agent: false`,
239
253
  ];
240
254
 
241
255
  if (mediaUrls.length > 0) {
@@ -283,7 +297,7 @@ const server = http.createServer(async (req, res) => {
283
297
  res.end(
284
298
  JSON.stringify({
285
299
  status: "ok",
286
- service: "sophie-sms-handler",
300
+ service: "agent-sms-handler",
287
301
  uptime: process.uptime(),
288
302
  timestamp: new Date().toISOString(),
289
303
  knownContacts: callerMap.size,
@@ -297,8 +311,8 @@ const server = http.createServer(async (req, res) => {
297
311
  res.writeHead(200, { "Content-Type": "application/json" });
298
312
  res.end(
299
313
  JSON.stringify({
300
- service: "Sophie SMS Handler",
301
- description: "Twilio inbound SMS webhook for sophie-ai inbox",
314
+ service: "Agent SMS Handler",
315
+ description: "Twilio inbound SMS webhook for the agent's inbox",
302
316
  endpoints: {
303
317
  health: "/health",
304
318
  sms: "/sms (POST)",
@@ -319,7 +333,7 @@ const server = http.createServer(async (req, res) => {
319
333
  const smsData = parseFormBody(body);
320
334
 
321
335
  const from = smsData.From || "unknown";
322
- const to = smsData.To || SOPHIE_NUMBER;
336
+ const to = smsData.To || AGENT_NUMBER;
323
337
  const messageBody = smsData.Body || "";
324
338
  const messageSid = smsData.MessageSid || "unknown";
325
339
  const numMedia = parseInt(smsData.NumMedia || "0", 10);
@@ -357,7 +371,7 @@ const server = http.createServer(async (req, res) => {
357
371
 
358
372
  // Write priority trigger
359
373
  const priorityDir = path.join(
360
- SOPHIE_AI,
374
+ AGENT_REPO_DIR,
361
375
  "state",
362
376
  "inbox",
363
377
  "processed",
@@ -415,7 +429,7 @@ ensureDir(LOG_DIR);
415
429
 
416
430
  server.listen(PORT, "0.0.0.0", () => {
417
431
  console.log(`\n========================================`);
418
- console.log(` Sophie SMS Handler`);
432
+ console.log(` Agent SMS Handler`);
419
433
  console.log(` Port: ${PORT}`);
420
434
  console.log(` Health: http://0.0.0.0:${PORT}/health`);
421
435
  console.log(` Webhook: POST /sms`);
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * Sophie WhatsApp Handler — Twilio Inbound WhatsApp Webhook
3
+ * Agent WhatsApp Handler — Twilio Inbound WhatsApp Webhook
4
4
  *
5
5
  * Receives inbound WhatsApp messages via Twilio webhook, maps senders to known
6
6
  * contacts, writes messages to the inbox for the inbox processor, and logs everything.
@@ -37,13 +37,26 @@ import { URL } from "node:url";
37
37
  // ---------------------------------------------------------------------------
38
38
 
39
39
  const PORT = parseInt(process.env.WHATSAPP_PORT || "3002", 10);
40
- const SOPHIE_AI =
41
- process.env.SOPHIE_AI_PATH ||
42
- path.join(process.env.HOME || "/Users/sophie", "sophie-ai");
43
- const INBOX_DIR = path.join(SOPHIE_AI, "state", "inbox", "whatsapp");
44
- const LOG_DIR = path.join(SOPHIE_AI, "logs", "whatsapp");
45
- const CALLER_ID_MAP_PATH = path.join(SOPHIE_AI, "config", "caller-id-map.yaml");
46
- const SOPHIE_NUMBER = "+16282656712";
40
+ const AGENT_REPO_DIR =
41
+ process.env.AGENT_DIR ||
42
+ process.env.AGENT_REPO_DIR ||
43
+ path.resolve(path.dirname(new URL(import.meta.url).pathname), "..");
44
+
45
+ let _agent = null;
46
+ function loadAgent() {
47
+ if (_agent) return _agent;
48
+ try {
49
+ _agent = JSON.parse(fs.readFileSync(path.join(AGENT_REPO_DIR, "config/agent.json"), "utf-8"));
50
+ } catch {
51
+ _agent = { firstName: "Agent", phone: "" };
52
+ }
53
+ return _agent;
54
+ }
55
+
56
+ const INBOX_DIR = path.join(AGENT_REPO_DIR, "state", "inbox", "whatsapp");
57
+ const LOG_DIR = path.join(AGENT_REPO_DIR, "logs", "whatsapp");
58
+ const CALLER_ID_MAP_PATH = path.join(AGENT_REPO_DIR, "config", "caller-id-map.yaml");
59
+ const AGENT_NUMBER = loadAgent().phone || "";
47
60
 
48
61
  // ---------------------------------------------------------------------------
49
62
  // Utilities
@@ -190,7 +203,7 @@ function logWhatsApp(entry) {
190
203
  * Log to the main audit trail.
191
204
  */
192
205
  function logAudit(action, details) {
193
- const auditDir = path.join(SOPHIE_AI, "logs", "audit");
206
+ const auditDir = path.join(AGENT_REPO_DIR, "logs", "audit");
194
207
  ensureDir(auditDir);
195
208
 
196
209
  const auditFile = path.join(auditDir, `${today()}-actions.jsonl`);
@@ -267,7 +280,7 @@ function writeToInbox(msgData) {
267
280
  ` from_ceo: ${senderPrivilege === "ceo"}`,
268
281
  ` tagged_urgent: ${body.toLowerCase().includes("urgent")}`,
269
282
  ` contains_deadline: false`,
270
- ` mentions_sophie: ${body.toLowerCase().includes("sophie")}`,
283
+ ` mentions_agent: ${body.toLowerCase().includes((loadAgent().firstName || "agent").toLowerCase())}`,
271
284
  ];
272
285
 
273
286
  if (mediaUrls.length > 0) {
@@ -311,7 +324,7 @@ const server = http.createServer(async (req, res) => {
311
324
  res.end(
312
325
  JSON.stringify({
313
326
  status: "ok",
314
- service: "sophie-whatsapp-handler",
327
+ service: "agent-whatsapp-handler",
315
328
  uptime: process.uptime(),
316
329
  timestamp: new Date().toISOString(),
317
330
  knownContacts: callerMap.size,
@@ -325,8 +338,8 @@ const server = http.createServer(async (req, res) => {
325
338
  res.writeHead(200, { "Content-Type": "application/json" });
326
339
  res.end(
327
340
  JSON.stringify({
328
- service: "Sophie WhatsApp Handler",
329
- description: "Twilio inbound WhatsApp webhook for sophie-ai inbox",
341
+ service: "Agent WhatsApp Handler",
342
+ description: "Twilio inbound WhatsApp webhook for the agent's inbox",
330
343
  endpoints: {
331
344
  health: "/health",
332
345
  whatsapp: "/whatsapp (POST)",
@@ -503,7 +516,7 @@ ensureDir(LOG_DIR);
503
516
 
504
517
  server.listen(PORT, "0.0.0.0", () => {
505
518
  console.log(`\n========================================`);
506
- console.log(` Sophie WhatsApp Handler`);
519
+ console.log(` Agent WhatsApp Handler`);
507
520
  console.log(` Port: ${PORT}`);
508
521
  console.log(` Health: http://0.0.0.0:${PORT}/health`);
509
522
  console.log(` Webhook: POST /whatsapp`);