@adaptic/maestro 1.8.1 → 1.8.3

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 (46) hide show
  1. package/bin/maestro.mjs +15 -3
  2. package/package.json +1 -1
  3. package/plugins/maestro-skills/skills/board-deck.md +2 -2
  4. package/plugins/maestro-skills/skills/decision-brief.md +6 -6
  5. package/plugins/maestro-skills/skills/draft-comms.md +9 -9
  6. package/plugins/maestro-skills/skills/evening-wrap.md +2 -2
  7. package/plugins/maestro-skills/skills/hiring-triage.md +4 -4
  8. package/plugins/maestro-skills/skills/inbox-triage.md +5 -5
  9. package/plugins/maestro-skills/skills/morning-brief.md +4 -4
  10. package/plugins/maestro-skills/skills/pipeline-review.md +2 -2
  11. package/plugins/maestro-skills/skills/regulatory-status.md +2 -2
  12. package/plugins/maestro-skills/skills/schedule-meeting.md +3 -3
  13. package/plugins/maestro-skills/skills/slack-followup.md +5 -5
  14. package/plugins/maestro-skills/skills/weekly-memo.md +5 -5
  15. package/scaffold/CLAUDE.md +21 -0
  16. package/scripts/daemon/classifier.mjs +21 -5
  17. package/scripts/daemon/maestro-daemon.mjs +46 -7
  18. package/scripts/hooks/block-mcp-slack-send.sh +1 -1
  19. package/scripts/huddle/audio-bridge.mjs +17 -17
  20. package/scripts/huddle/boot-slack-cdp.sh +1 -1
  21. package/scripts/huddle/huddle-controller.mjs +3 -3
  22. package/scripts/huddle/huddle-server.mjs +21 -7
  23. package/scripts/huddle/launch-slack.sh +2 -2
  24. package/scripts/huddle/package-lock.json +2 -2
  25. package/scripts/huddle/package.json +2 -2
  26. package/scripts/huddle/setup-audio.sh +6 -6
  27. package/scripts/huddle/start-call.mjs +2 -2
  28. package/scripts/huddle/test-pipeline.mjs +2 -2
  29. package/scripts/local-triggers/generate-plists.sh +15 -1
  30. package/scripts/local-triggers/generate-plists.test.mjs +9 -2
  31. package/scripts/parse-voice-transcript.mjs +4 -9
  32. package/scripts/poller/gmail-poller.mjs +8 -2
  33. package/scripts/poller/intra-session-check.mjs +4 -3
  34. package/scripts/poller-launchd/install.sh +48 -10
  35. package/scripts/pre_draft_lookup.py +2 -2
  36. package/scripts/self-optimization/compute-metrics.py +23 -2
  37. package/scripts/setup/boot-claude-session.sh +14 -5
  38. package/scripts/setup/render-environment-yaml.mjs +133 -0
  39. package/scripts/watchdog/ai.maestro.memory-watchdog.plist +3 -3
  40. package/scripts/watchdog/force-reboot.sh +3 -3
  41. package/scripts/watchdog/memory-watchdog.sh +11 -5
  42. package/workflows/daily/applicant-triage.yaml +3 -3
  43. package/workflows/daily/comms-triage.yaml +1 -1
  44. package/workflows/daily/morning-brief.yaml +1 -1
  45. package/workflows/daily/slack-followup-sweep.yaml +1 -1
  46. package/workflows/weekly/hiring-review.yaml +3 -3
@@ -0,0 +1,133 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * render-environment-yaml.mjs
4
+ *
5
+ * Derive `config/environment.yaml` from `config/agent.json` (the SOT).
6
+ * Replaces the previously-static, agent-name-hardcoded environment.yaml so
7
+ * that operator_persona / operator_role / ceo / hostname / paths / agent
8
+ * blocks always reflect what agent.json says.
9
+ *
10
+ * Called by `maestro upgrade` (post-merge) and safe to run by hand:
11
+ *
12
+ * node scripts/setup/render-environment-yaml.mjs # write
13
+ * node scripts/setup/render-environment-yaml.mjs --dry-run # preview
14
+ * node scripts/setup/render-environment-yaml.mjs --check # exit 1 if drift
15
+ *
16
+ * Output is canonical (deterministic). If the existing file already matches,
17
+ * nothing is written. The first 5 lines of the rendered file always include
18
+ * a `# regenerated-from: config/agent.json` banner so operators editing it
19
+ * directly know they're editing a derived file.
20
+ */
21
+
22
+ import { readFileSync, writeFileSync, existsSync } from "node:fs";
23
+ import { resolve, join } from "node:path";
24
+
25
+ const AGENT_DIR = process.env.AGENT_ROOT || process.env.AGENT_DIR || process.cwd();
26
+ const AGENT_JSON = join(AGENT_DIR, "config", "agent.json");
27
+ const TARGET = join(AGENT_DIR, "config", "environment.yaml");
28
+
29
+ function fail(msg) {
30
+ process.stderr.write(`[render-environment-yaml] ${msg}\n`);
31
+ process.exit(1);
32
+ }
33
+
34
+ if (!existsSync(AGENT_JSON)) {
35
+ fail(`config/agent.json not found at ${AGENT_JSON} — nothing to derive from.`);
36
+ }
37
+
38
+ let agent;
39
+ try {
40
+ agent = JSON.parse(readFileSync(AGENT_JSON, "utf-8"));
41
+ } catch (err) {
42
+ fail(`failed to parse agent.json: ${err.message}`);
43
+ }
44
+
45
+ // Render a stable, predictable YAML. We don't use a YAML library to keep
46
+ // this script dep-free and to control the exact formatting & comment
47
+ // placement. Mind the trailing newline on every value.
48
+ function quote(s) {
49
+ if (typeof s !== "string") return `"${s ?? ""}"`;
50
+ // No double-quotes in agent identity strings expected; if any appear,
51
+ // escape them properly.
52
+ return `"${s.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
53
+ }
54
+
55
+ function yamlValue(v) {
56
+ if (v === null || v === undefined) return '""';
57
+ if (typeof v === "number" || typeof v === "boolean") return String(v);
58
+ return quote(String(v));
59
+ }
60
+
61
+ function render(agent) {
62
+ const repoSlug = agent.repoSlug || `${(agent.firstName || "agent").toLowerCase()}-ai`;
63
+ const home = `~/${repoSlug}`;
64
+ const lines = [
65
+ "# Environment Configuration",
66
+ "# regenerated-from: config/agent.json by scripts/setup/render-environment-yaml.mjs",
67
+ "# DO NOT EDIT BY HAND — changes will be overwritten on every upgrade.",
68
+ "# Edit config/agent.json (the SOT) and re-run the renderer instead.",
69
+ "",
70
+ "system:",
71
+ ` name: maestro`,
72
+ ` version: 1.0.0`,
73
+ ` operator_persona: ${yamlValue(agent.fullName)}`,
74
+ ` operator_role: ${yamlValue(agent.title)}`,
75
+ ` company: ${yamlValue(agent.company)}`,
76
+ ` ceo: ${yamlValue(agent.principal?.fullName || "")}`,
77
+ ` timezone: ${yamlValue(agent.timezone || "UTC")}`,
78
+ "",
79
+ "machine:",
80
+ ` type: mac-mini`,
81
+ ` os: macOS`,
82
+ ` hostname: ${yamlValue(agent.machineName || "")}`,
83
+ ` purpose: Autonomous agent operations node`,
84
+ "",
85
+ "paths:",
86
+ ` agent_home: ${home}`,
87
+ ` logs: ${home}/logs`,
88
+ ` outputs: ${home}/outputs`,
89
+ "",
90
+ "agent:",
91
+ ` name: ${yamlValue(agent.fullName)}`,
92
+ ` email: ${yamlValue(agent.email)}`,
93
+ ` phone: ${yamlValue(agent.phone || "")}`,
94
+ "",
95
+ ];
96
+ return lines.join("\n");
97
+ }
98
+
99
+ const rendered = render(agent);
100
+ const args = process.argv.slice(2);
101
+ const dryRun = args.includes("--dry-run") || args.includes("-n");
102
+ const check = args.includes("--check");
103
+
104
+ if (check) {
105
+ if (!existsSync(TARGET)) {
106
+ process.stdout.write("config/environment.yaml missing\n");
107
+ process.exit(1);
108
+ }
109
+ const current = readFileSync(TARGET, "utf-8");
110
+ if (current === rendered) {
111
+ process.stdout.write("environment.yaml is up to date with agent.json\n");
112
+ process.exit(0);
113
+ }
114
+ process.stdout.write("environment.yaml drifts from agent.json — rerun the renderer\n");
115
+ process.exit(1);
116
+ }
117
+
118
+ if (dryRun) {
119
+ process.stdout.write(rendered);
120
+ process.exit(0);
121
+ }
122
+
123
+ // Skip the write if already up to date — keeps `upgrade` idempotent.
124
+ if (existsSync(TARGET)) {
125
+ const current = readFileSync(TARGET, "utf-8");
126
+ if (current === rendered) {
127
+ process.stdout.write("environment.yaml already up to date\n");
128
+ process.exit(0);
129
+ }
130
+ }
131
+
132
+ writeFileSync(TARGET, rendered);
133
+ process.stdout.write(`environment.yaml regenerated from agent.json (${rendered.split("\n").length} lines)\n`);
@@ -8,7 +8,7 @@
8
8
  <key>ProgramArguments</key>
9
9
  <array>
10
10
  <string>/bin/bash</string>
11
- <string>/Users/sophie/maestro/scripts/watchdog/memory-watchdog.sh</string>
11
+ <string>__AGENT_DIR__/scripts/watchdog/memory-watchdog.sh</string>
12
12
  </array>
13
13
 
14
14
  <key>StartInterval</key>
@@ -18,10 +18,10 @@
18
18
  <true/>
19
19
 
20
20
  <key>StandardOutPath</key>
21
- <string>/Users/sophie/maestro/logs/watchdog/launchd-stdout.log</string>
21
+ <string>__AGENT_DIR__/logs/watchdog/launchd-stdout.log</string>
22
22
 
23
23
  <key>StandardErrorPath</key>
24
- <string>/Users/sophie/maestro/logs/watchdog/launchd-stderr.log</string>
24
+ <string>__AGENT_DIR__/logs/watchdog/launchd-stderr.log</string>
25
25
 
26
26
  <key>EnvironmentVariables</key>
27
27
  <dict>
@@ -12,7 +12,7 @@
12
12
  # ./scripts/watchdog/force-reboot.sh --status # Check heartbeat and uptime
13
13
  #
14
14
  # Remote usage (from another machine via SSH):
15
- # ssh sophie@mac-mini.local "~/maestro/scripts/watchdog/force-reboot.sh --graceful"
15
+ # ssh <user>@<mac-mini> "~/maestro/scripts/watchdog/force-reboot.sh --graceful"
16
16
  #
17
17
  # =============================================================================
18
18
  #
@@ -43,8 +43,8 @@
43
43
  # - With `pmset autorestart 1`, it will boot automatically
44
44
  #
45
45
  # 5. SSH + SYSDIAGNOSE RESET (partial freeze, SSH still works):
46
- # - ssh sophie@mac-mini.local "sudo reboot"
47
- # - Or: ssh sophie@mac-mini.local "sudo shutdown -r now"
46
+ # - ssh <user>@<mac-mini> "sudo reboot"
47
+ # - Or: ssh <user>@<mac-mini> "sudo shutdown -r now"
48
48
  #
49
49
  # =============================================================================
50
50
 
@@ -26,8 +26,9 @@ set -euo pipefail
26
26
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
27
27
  MAESTRO_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)"
28
28
 
29
- # Auto-detect agent directory (sophie-ai, wundr, etc.)
30
- # The watchdog runs from maestro but protects the whole machine
29
+ # Auto-detect agent directory (any ~/<name>-ai or override via AGENT_DIR env).
30
+ # The watchdog runs from maestro but protects the whole machine — agents are
31
+ # discovered dynamically rather than hardcoded.
31
32
  AGENT_DIR="${AGENT_DIR:-}"
32
33
 
33
34
  # --- Ensure system paths are available ----------------------------------------
@@ -203,9 +204,14 @@ trigger_emergency_stop() {
203
204
  echo "$reason" > "$stop_file"
204
205
  log_event "critical" "emergency_stop_triggered" "$reason"
205
206
 
206
- # Also create in any detected agent directories
207
- for agent_dir in "$HOME"/sophie-ai "$HOME"/wundr; do
208
- if [ -d "$agent_dir" ]; then
207
+ # Also create in any detected agent directory. Discovers ~/<name>-ai
208
+ # directories dynamically so the watchdog protects every agent on this
209
+ # machine without a hardcoded list. AGENT_DIR override still wins.
210
+ if [ -n "$AGENT_DIR" ] && [ -d "$AGENT_DIR" ]; then
211
+ echo "$reason" > "$AGENT_DIR/.emergency-stop" 2>/dev/null || true
212
+ fi
213
+ for agent_dir in "$HOME"/*-ai; do
214
+ if [ -d "$agent_dir" ] && [ -f "$agent_dir/config/agent.json" ]; then
209
215
  echo "$reason" > "$agent_dir/.emergency-stop" 2>/dev/null || true
210
216
  fi
211
217
  done
@@ -107,7 +107,7 @@ steps:
107
107
  classification: outputs/briefs/daily/{date}/candidate-classification.md
108
108
  category: advance
109
109
  tone: warm_professional
110
- voice: sophie
110
+ voice: agent
111
111
  sla: 24h
112
112
  outputs:
113
113
  file: outputs/comms/hiring/{date}/daily-advance-drafts.md
@@ -123,7 +123,7 @@ steps:
123
123
  classification: outputs/briefs/daily/{date}/candidate-classification.md
124
124
  category: hold
125
125
  tone: professional
126
- voice: sophie
126
+ voice: agent
127
127
  outputs:
128
128
  file: outputs/comms/hiring/{date}/daily-hold-drafts.md
129
129
 
@@ -138,7 +138,7 @@ steps:
138
138
  classification: outputs/briefs/daily/{date}/candidate-classification.md
139
139
  category: reject
140
140
  tone: kind_encouraging
141
- voice: sophie
141
+ voice: agent
142
142
  sla: 48h
143
143
  outputs:
144
144
  file: outputs/comms/hiring/{date}/daily-rejection-drafts.md
@@ -29,7 +29,7 @@ steps:
29
29
  timeout: 300
30
30
  parallel_with: slack-scan
31
31
  inputs:
32
- account: sophie@adaptic.ai
32
+ account: "{{agent.email}}" # resolved from config/agent.json at workflow execution
33
33
  mode: observe
34
34
  last_triage: "{last_run_timestamp}"
35
35
  outputs:
@@ -62,7 +62,7 @@ steps:
62
62
  timeout: 300
63
63
  parallel_with: market-sweep
64
64
  inputs:
65
- account: sophie@adaptic.ai
65
+ account: "{{agent.email}}" # resolved from config/agent.json at workflow execution
66
66
  mode: observe # read-only, never act
67
67
  priority_senders: [] # from config/contacts.yaml
68
68
  outputs:
@@ -43,7 +43,7 @@ steps:
43
43
  depends_on: [check-commitments]
44
44
  inputs:
45
45
  overdue_items: outputs/followups/daily/{date}/slack-commitments-status.md
46
- voice: sophie # Follow up in Sophie's voice
46
+ voice: agent # Follow up in the agent's own voice (mode resolved from config/agent.json voiceModes)
47
47
  outputs:
48
48
  file: outputs/followups/daily/{date}/slack-followup-drafts.md
49
49
 
@@ -65,7 +65,7 @@ steps:
65
65
  screening_results: outputs/briefs/weekly/{date}/screening-results.md
66
66
  category: advance
67
67
  tone: warm_professional
68
- voice: sophie
68
+ voice: agent
69
69
  outputs:
70
70
  file: outputs/comms/hiring/{date}/advance-drafts.md
71
71
 
@@ -80,7 +80,7 @@ steps:
80
80
  screening_results: outputs/briefs/weekly/{date}/screening-results.md
81
81
  category: reject
82
82
  tone: kind_encouraging
83
- voice: sophie
83
+ voice: agent
84
84
  outputs:
85
85
  file: outputs/comms/hiring/{date}/rejection-drafts.md
86
86
 
@@ -95,7 +95,7 @@ steps:
95
95
  screening_results: outputs/briefs/weekly/{date}/screening-results.md
96
96
  category: hold
97
97
  tone: professional
98
- voice: sophie
98
+ voice: agent
99
99
  outputs:
100
100
  file: outputs/comms/hiring/{date}/hold-drafts.md
101
101