@adaptic/maestro 1.1.0 → 1.1.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.
- package/.claude/commands/init-maestro.md +292 -29
- package/.gitignore +29 -0
- package/README.md +220 -344
- package/bin/maestro.mjs +462 -7
- package/lib/action-executor.js +225 -0
- package/lib/index.js +16 -0
- package/lib/singleton.js +116 -0
- package/lib/tool-definitions.js +510 -0
- package/package.json +10 -1
- package/scaffold/CLAUDE.md +207 -0
- package/scripts/daemon/maestro-daemon.mjs +37 -0
- package/scripts/local-triggers/generate-plists.sh +235 -0
- package/scripts/local-triggers/install-all.sh +18 -12
- package/scripts/setup/init-agent.sh +58 -27
- package/scripts/slack-events-server.mjs +10 -0
- package/scripts/local-triggers/plists/ai.adaptic.slack-events-server.plist +0 -45
package/scaffold/CLAUDE.md
CHANGED
|
@@ -12,6 +12,213 @@ claude "/init-maestro"
|
|
|
12
12
|
|
|
13
13
|
This will configure your name, role, responsibilities, communication style, and all operational parameters.
|
|
14
14
|
|
|
15
|
+
## Company Context
|
|
16
|
+
|
|
17
|
+
*Configure via /init-maestro*
|
|
18
|
+
|
|
19
|
+
## Operating Principles
|
|
20
|
+
|
|
21
|
+
*Configure via /init-maestro — principles will be set based on your role archetype.*
|
|
22
|
+
|
|
23
|
+
## Communication Rules
|
|
24
|
+
|
|
25
|
+
### Voice Modes
|
|
26
|
+
|
|
27
|
+
- **Agent's voice**: Default for all operational communications (internal + external)
|
|
28
|
+
- **Principal's voice**: For communications attributed to the principal
|
|
29
|
+
- **Internal notes**: Analysis, risk flags — not sent externally
|
|
30
|
+
- **Institutional**: Regulatory and legal correspondence
|
|
31
|
+
|
|
32
|
+
### Autonomy Model
|
|
33
|
+
|
|
34
|
+
*Configure via /init-maestro — autonomy boundaries depend on role archetype.*
|
|
35
|
+
|
|
36
|
+
### Immediate Acknowledgement Rule (Slack, SMS, WhatsApp)
|
|
37
|
+
|
|
38
|
+
When this agent receives an incoming request via Slack, SMS, or WhatsApp that is complex or will take more than a few minutes to action, it MUST send an immediate holding note before beginning work.
|
|
39
|
+
|
|
40
|
+
### Document Sharing (CRITICAL)
|
|
41
|
+
|
|
42
|
+
**NEVER reference local file paths in Slack, email, or any outbound communication.** No one outside this Mac mini can access files at `outputs/`, `docs/`, or any other local path.
|
|
43
|
+
|
|
44
|
+
When sharing documents or supporting materials:
|
|
45
|
+
|
|
46
|
+
1. **Prefer Google Drive** — Upload as a Google Doc/Sheet/Slides and share the Drive link
|
|
47
|
+
2. **Use PDF when appropriate** — For final/read-only documents, generate a branded PDF via `npm run pdf:memo`
|
|
48
|
+
3. **Inline short content** — For brief updates (< 1 page), include directly in the message body
|
|
49
|
+
4. **Never reference local file paths** in any external communication
|
|
50
|
+
|
|
51
|
+
### Logging
|
|
52
|
+
|
|
53
|
+
All sent communications are logged to `logs/` with full audit trail.
|
|
54
|
+
|
|
55
|
+
## Repository Layout
|
|
56
|
+
|
|
57
|
+
- `agents/` — Agent definitions with mandates and prompts
|
|
58
|
+
- `teams/` — Agent team compositions
|
|
59
|
+
- `workflows/` — Daily/weekly/monthly/quarterly workflow configs
|
|
60
|
+
- `knowledge/` — Sources, memory, entities, decisions
|
|
61
|
+
- `config/` — Environment, priorities, contacts, repo registry, brand assets
|
|
62
|
+
- `policies/` — Action classification and approval rules
|
|
63
|
+
- `desktop-control/` — macOS app control profiles
|
|
64
|
+
- `outputs/` — Briefs, memos, board materials, follow-ups
|
|
65
|
+
- `scripts/` — Emergency stop, healthcheck, bootstrap, PDF generation, media generation
|
|
66
|
+
- `public/assets/` — Brand SVG assets (icon + logo, dark + light variants)
|
|
67
|
+
- `public/generated/` — Generated illustrations, diagrams, and videos
|
|
68
|
+
|
|
69
|
+
## Key Config Files
|
|
70
|
+
|
|
71
|
+
- `config/agent.ts` — Central identity configuration (single source of truth)
|
|
72
|
+
- `config/priorities.yaml` — Active strategic priorities and milestones
|
|
73
|
+
- `config/contacts.yaml` — Key contacts with communication classifications
|
|
74
|
+
- `config/environment.yaml` — Environment-specific configuration
|
|
75
|
+
- `config/sla-defaults.yaml` — SLA response time expectations
|
|
76
|
+
- `config/brand-assets.yaml` — Brand asset inventory, usage rules, and placement guidelines
|
|
77
|
+
- `policies/action-classification.yaml` — Action risk levels and approval rules
|
|
78
|
+
|
|
79
|
+
## Brand Assets
|
|
80
|
+
|
|
81
|
+
Official Adaptic brand assets live in `public/assets/`. See `config/brand-assets.yaml` for full rules.
|
|
82
|
+
|
|
83
|
+
## PDF Generation
|
|
84
|
+
|
|
85
|
+
Generate branded PDFs from Markdown using Pandoc + XeLaTeX.
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
npm run pdf:memo -- --input <file.md> # Internal memos and briefs
|
|
89
|
+
npm run pdf:board-pack -- --input <file.md> # Board materials with cover + TOC
|
|
90
|
+
npm run pdf:letter -- --input <file.md> # Corporate correspondence
|
|
91
|
+
npm run pdf:investor -- --input <file.md> # Investor communications
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Visual Media Generation
|
|
95
|
+
|
|
96
|
+
Generate branded illustrations, diagrams, and videos using Gemini and Veo APIs.
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
npm run gen:illustration -- <slug> # Single illustration via Gemini
|
|
100
|
+
npm run gen:video -- <slug> # Single video via Veo 3.1
|
|
101
|
+
npm run gen:all-missing # Batch generate missing illustrations
|
|
102
|
+
npm run gen:list # List available prompt specs
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## People (Key Leadership)
|
|
106
|
+
|
|
107
|
+
*Configure via /init-maestro*
|
|
108
|
+
|
|
109
|
+
## Operational Infrastructure
|
|
110
|
+
|
|
111
|
+
### State Management
|
|
112
|
+
|
|
113
|
+
- **Live queues**: 16 operational queues in `state/queues/` track action items, decisions, follow-ups, blockers
|
|
114
|
+
- **Dashboards**: `state/dashboards/executive-summary.yaml` is read at every session start
|
|
115
|
+
- **Standard schema**: All queue items follow open → in_progress → blocked → resolved → closed
|
|
116
|
+
|
|
117
|
+
### Event Polling
|
|
118
|
+
|
|
119
|
+
- **Daemon**: `scripts/daemon/maestro-daemon.mjs` polls Slack/Gmail/Calendar every 60 seconds
|
|
120
|
+
- **Inbox**: New items land in `state/inbox/{service}/` as YAML files
|
|
121
|
+
- **Inbox processor**: Classifies and routes items every 5 minutes
|
|
122
|
+
- **Priority triggers**: Principal DMs, urgent tags, and agent mentions trigger immediate processing
|
|
123
|
+
|
|
124
|
+
### Self-Evolution
|
|
125
|
+
|
|
126
|
+
- Agent learns from interactions and evolves its own operating setup
|
|
127
|
+
- Changes classified by risk: low (auto-apply), medium (apply + notify), high (escalate)
|
|
128
|
+
- All self-modifications logged to `logs/evolution/`
|
|
129
|
+
|
|
130
|
+
### Prompt Injection Defence
|
|
131
|
+
|
|
132
|
+
- 5-layer defence defined in `policies/prompt-injection-defence.yaml`
|
|
133
|
+
|
|
134
|
+
### Audit & Hooks
|
|
135
|
+
|
|
136
|
+
- Pre-send audit hook gates all outbound Slack/Gmail communications
|
|
137
|
+
- Post-action hook logs all tool completions
|
|
138
|
+
- Session-end hook logs session completion
|
|
139
|
+
- All logs in `logs/audit/`, `logs/workflows/`, `logs/security/`, `logs/sessions/`
|
|
140
|
+
|
|
141
|
+
### Session Protocol
|
|
142
|
+
|
|
143
|
+
**Every session starts by:**
|
|
144
|
+
1. Reading `state/dashboards/executive-summary.yaml`
|
|
145
|
+
2. Checking `state/inbox/` for unprocessed items
|
|
146
|
+
3. Checking `state/queues/decision-queue.yaml` for pending decisions
|
|
147
|
+
|
|
148
|
+
**Every session ends by:**
|
|
149
|
+
1. Updating modified queue items
|
|
150
|
+
2. Regenerating the executive summary dashboard
|
|
151
|
+
3. Running self-evolution reflection
|
|
152
|
+
4. Logging session summary
|
|
153
|
+
|
|
154
|
+
## Control Towers
|
|
155
|
+
|
|
156
|
+
12 operational control towers provide comprehensive organisational coverage:
|
|
157
|
+
|
|
158
|
+
1. **Strategy & Decisions** — Priority drift detection, decision library, strategic coherence
|
|
159
|
+
2. **Executive Office & Follow-Through** — Action capture, loop closure, SLA tracking
|
|
160
|
+
3. **Operating Cadence & PMO** — Meeting prep, action routing, initiative scorecard
|
|
161
|
+
4. **People, Hiring & Organisation** — Org health, founder dependency, onboarding
|
|
162
|
+
5. **Culture, Standards & Leadership** — Leadership accountability, communication quality
|
|
163
|
+
6. **Product, Platform & Engineering** — Engineering health, tech debt, delivery confidence
|
|
164
|
+
7. **Commercial, Customers & Market** — Relationship pipeline, competitive intelligence
|
|
165
|
+
8. **Board, Advisors & Stakeholders** — Board calendar, advisor utilisation
|
|
166
|
+
9. **Capital, Finance & Corporate Dev** — Capital readiness, runway tracking
|
|
167
|
+
10. **Legal, Risk, Compliance & Regulatory** — Enterprise risk register, compliance calendar
|
|
168
|
+
11. **Systems, Automation & Memory** — Knowledge health, automation opportunities
|
|
169
|
+
12. **Self-Governance** — System health, quarterly self-assessment
|
|
170
|
+
|
|
171
|
+
## Build & Test
|
|
172
|
+
|
|
173
|
+
Install dependencies with `npm install`.
|
|
174
|
+
|
|
175
|
+
- Health check: `npm run healthcheck`
|
|
176
|
+
- Emergency stop: `npm run emergency-stop`
|
|
177
|
+
- Resume: `npm run resume`
|
|
178
|
+
- PDF generation: `npm run pdf:memo -- --input <file>` (requires Pandoc + MacTeX)
|
|
179
|
+
- Media generation: `npm run gen:illustration -- <slug>` (requires GEMINI_API_KEY)
|
|
180
|
+
|
|
181
|
+
## Maestro Framework
|
|
182
|
+
|
|
183
|
+
This agent is powered by the `@adaptic/maestro` framework. Update framework: `npm run upgrade`
|
|
184
|
+
|
|
185
|
+
## Code Standards
|
|
186
|
+
|
|
187
|
+
- YAML for all configuration and workflow definitions
|
|
188
|
+
- Markdown for all documentation and agent prompts
|
|
189
|
+
- Shell scripts must be POSIX-compatible where possible
|
|
190
|
+
- All scripts must have error handling (`set -e`)
|
|
191
|
+
- All configs must use environment variables for secrets
|
|
192
|
+
- All actions must be auditable via logs/
|
|
193
|
+
|
|
194
|
+
## Three Operating Modes
|
|
195
|
+
|
|
196
|
+
### Mode 1: Reactive — Respond to Events
|
|
197
|
+
- Daemon detects inbound Slack/Gmail/Calendar events every 60 seconds
|
|
198
|
+
- Priority events trigger immediate processing
|
|
199
|
+
|
|
200
|
+
### Mode 2: Scheduled — Run Cadence Workflows
|
|
201
|
+
- Morning brief, midday sweep, evening wrap (daily)
|
|
202
|
+
- Domain-specific reviews (weekly)
|
|
203
|
+
- Self-assessment (quarterly)
|
|
204
|
+
|
|
205
|
+
### Mode 3: Proactive — Execute the Backlog
|
|
206
|
+
- Backlog executor runs every 10 minutes
|
|
207
|
+
- Reads all queues, selects top actionable items
|
|
208
|
+
- Spawns parallel background agents to execute each item
|
|
209
|
+
|
|
210
|
+
## Parallel Execution & Agent Teams
|
|
211
|
+
|
|
212
|
+
Agent MUST use parallel execution for complex tasks. Agent Teams are enabled via `CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1`.
|
|
213
|
+
|
|
214
|
+
### When to spawn parallel agents
|
|
215
|
+
|
|
216
|
+
- **Always** for tasks with 2+ independent subtasks
|
|
217
|
+
- **Always** for cross-functional work
|
|
218
|
+
- **Always** for time-sensitive workflows
|
|
219
|
+
|
|
220
|
+
Sequential processing should be the exception, not the default.
|
|
221
|
+
|
|
15
222
|
## Quick Reference
|
|
16
223
|
|
|
17
224
|
- Agent config: `config/agent.ts`
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// =============================================================================
|
|
3
|
+
// Maestro Daemon — Reactive event-driven message processor
|
|
4
|
+
// =============================================================================
|
|
5
|
+
//
|
|
6
|
+
// Generic entry point that loads agent identity from config/agent.ts and
|
|
7
|
+
// delegates to the core daemon logic. This file is agent-name-agnostic.
|
|
8
|
+
//
|
|
9
|
+
// Run: node scripts/daemon/maestro-daemon.mjs
|
|
10
|
+
// Install: launchd plist with KeepAlive: true
|
|
11
|
+
// =============================================================================
|
|
12
|
+
|
|
13
|
+
// The core daemon is in sophie-daemon.mjs for backward compatibility.
|
|
14
|
+
// It uses AGENT_DIR (resolved from this file's location) as the base path.
|
|
15
|
+
// The init-maestro wizard renames sophie references in that file.
|
|
16
|
+
|
|
17
|
+
import { resolve, dirname } from "node:path";
|
|
18
|
+
import { fileURLToPath } from "node:url";
|
|
19
|
+
|
|
20
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
21
|
+
const AGENT_DIR = resolve(__dirname, "../..");
|
|
22
|
+
|
|
23
|
+
// Set AGENT_DIR as env var so all modules can use it
|
|
24
|
+
process.env.AGENT_DIR = AGENT_DIR;
|
|
25
|
+
process.env.AGENT_ROOT = AGENT_DIR;
|
|
26
|
+
|
|
27
|
+
// Singleton guard — prevent duplicate daemon instances
|
|
28
|
+
try {
|
|
29
|
+
const { acquireLock } = await import(resolve(process.env.HOME, "maestro/lib/singleton.js"));
|
|
30
|
+
if (!acquireLock("daemon")) {
|
|
31
|
+
console.log("[DAEMON] Already running — exiting");
|
|
32
|
+
process.exit(0);
|
|
33
|
+
}
|
|
34
|
+
} catch { /* maestro singleton not available */ }
|
|
35
|
+
|
|
36
|
+
// Import and run the daemon (handles its own .env loading)
|
|
37
|
+
await import("./sophie-daemon.mjs");
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# =============================================================================
|
|
3
|
+
# generate-plists.sh — Generate launchd plist files from templates
|
|
4
|
+
# =============================================================================
|
|
5
|
+
#
|
|
6
|
+
# Reads config/agent.ts to extract the agent's first name and generates
|
|
7
|
+
# properly configured plist files for all scheduled triggers and the daemon.
|
|
8
|
+
#
|
|
9
|
+
# Usage:
|
|
10
|
+
# ./scripts/local-triggers/generate-plists.sh
|
|
11
|
+
#
|
|
12
|
+
# Called by: init-agent.sh (Step 5)
|
|
13
|
+
# =============================================================================
|
|
14
|
+
|
|
15
|
+
set -e
|
|
16
|
+
|
|
17
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
18
|
+
AGENT_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
|
19
|
+
PLIST_DIR="$SCRIPT_DIR/plists"
|
|
20
|
+
AGENT_CONFIG="$AGENT_DIR/config/agent.ts"
|
|
21
|
+
|
|
22
|
+
# Extract agent first name from config/agent.ts
|
|
23
|
+
if [ -f "$AGENT_CONFIG" ]; then
|
|
24
|
+
AGENT_FIRST=$(grep "firstName:" "$AGENT_CONFIG" | head -1 | sed "s/.*firstName:[[:space:]]*['\"]//; s/['\"].*//" | tr '[:upper:]' '[:lower:]')
|
|
25
|
+
fi
|
|
26
|
+
|
|
27
|
+
# Fall back to directory name if config not set or UNCONFIGURED
|
|
28
|
+
if [ -z "$AGENT_FIRST" ] || [ "$AGENT_FIRST" = "unconfigured" ]; then
|
|
29
|
+
AGENT_FIRST=$(basename "$AGENT_DIR" | sed 's/-ai$//')
|
|
30
|
+
fi
|
|
31
|
+
|
|
32
|
+
AGENT_UPPER=$(echo "$AGENT_FIRST" | tr '[:lower:]' '[:upper:]')
|
|
33
|
+
AGENT_USER=$(whoami)
|
|
34
|
+
NODE_PATH=$(which node 2>/dev/null || echo "/opt/homebrew/bin/node")
|
|
35
|
+
|
|
36
|
+
echo "[generate-plists] Agent: $AGENT_FIRST"
|
|
37
|
+
echo "[generate-plists] Directory: $AGENT_DIR"
|
|
38
|
+
echo "[generate-plists] User: $AGENT_USER"
|
|
39
|
+
|
|
40
|
+
mkdir -p "$PLIST_DIR"
|
|
41
|
+
|
|
42
|
+
# Clear old plists before generating — prevents stale plists from a previous
|
|
43
|
+
# agent identity (e.g. sophie-*) from being installed alongside new ones
|
|
44
|
+
rm -f "$PLIST_DIR"/*.plist 2>/dev/null
|
|
45
|
+
|
|
46
|
+
# ── Helper: generate a single plist ──────────────────────────────────────────
|
|
47
|
+
|
|
48
|
+
generate_plist() {
|
|
49
|
+
local LABEL="$1"
|
|
50
|
+
local PROGRAM_ARGS="$2" # The command to run (array elements separated by |)
|
|
51
|
+
local SCHEDULE="$3" # Optional: StartCalendarInterval dict content
|
|
52
|
+
local INTERVAL="$4" # Optional: StartInterval in seconds
|
|
53
|
+
local KEEP_ALIVE="$5" # Optional: true for daemon-style
|
|
54
|
+
|
|
55
|
+
local FILE="$PLIST_DIR/$LABEL.plist"
|
|
56
|
+
|
|
57
|
+
cat > "$FILE" << PLIST_HEADER
|
|
58
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
59
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
60
|
+
<plist version="1.0">
|
|
61
|
+
<dict>
|
|
62
|
+
<key>Label</key>
|
|
63
|
+
<string>$LABEL</string>
|
|
64
|
+
|
|
65
|
+
<key>ProgramArguments</key>
|
|
66
|
+
<array>
|
|
67
|
+
PLIST_HEADER
|
|
68
|
+
|
|
69
|
+
# Split program args by |
|
|
70
|
+
IFS='|' read -ra ARGS <<< "$PROGRAM_ARGS"
|
|
71
|
+
for arg in "${ARGS[@]}"; do
|
|
72
|
+
echo " <string>$arg</string>" >> "$FILE"
|
|
73
|
+
done
|
|
74
|
+
|
|
75
|
+
cat >> "$FILE" << PLIST_MID
|
|
76
|
+
</array>
|
|
77
|
+
|
|
78
|
+
<key>WorkingDirectory</key>
|
|
79
|
+
<string>$AGENT_DIR</string>
|
|
80
|
+
PLIST_MID
|
|
81
|
+
|
|
82
|
+
if [ "$KEEP_ALIVE" = "true" ]; then
|
|
83
|
+
cat >> "$FILE" << 'PLIST_KEEPALIVE'
|
|
84
|
+
|
|
85
|
+
<key>KeepAlive</key>
|
|
86
|
+
<true/>
|
|
87
|
+
|
|
88
|
+
<key>RunAtLoad</key>
|
|
89
|
+
<true/>
|
|
90
|
+
|
|
91
|
+
<key>ThrottleInterval</key>
|
|
92
|
+
<integer>5</integer>
|
|
93
|
+
PLIST_KEEPALIVE
|
|
94
|
+
fi
|
|
95
|
+
|
|
96
|
+
if [ -n "$SCHEDULE" ]; then
|
|
97
|
+
cat >> "$FILE" << PLIST_SCHED
|
|
98
|
+
|
|
99
|
+
<key>StartCalendarInterval</key>
|
|
100
|
+
<dict>
|
|
101
|
+
$SCHEDULE
|
|
102
|
+
</dict>
|
|
103
|
+
PLIST_SCHED
|
|
104
|
+
fi
|
|
105
|
+
|
|
106
|
+
if [ -n "$INTERVAL" ]; then
|
|
107
|
+
cat >> "$FILE" << PLIST_INTERVAL
|
|
108
|
+
|
|
109
|
+
<key>StartInterval</key>
|
|
110
|
+
<integer>$INTERVAL</integer>
|
|
111
|
+
PLIST_INTERVAL
|
|
112
|
+
fi
|
|
113
|
+
|
|
114
|
+
cat >> "$FILE" << PLIST_FOOTER
|
|
115
|
+
|
|
116
|
+
<key>StandardOutPath</key>
|
|
117
|
+
<string>$AGENT_DIR/logs/daemon/launchd-stdout.log</string>
|
|
118
|
+
|
|
119
|
+
<key>StandardErrorPath</key>
|
|
120
|
+
<string>$AGENT_DIR/logs/daemon/launchd-stderr.log</string>
|
|
121
|
+
</dict>
|
|
122
|
+
</plist>
|
|
123
|
+
PLIST_FOOTER
|
|
124
|
+
|
|
125
|
+
echo " Generated: $LABEL"
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
# ── Helper: trigger plist (runs claude with a trigger prompt) ────────────────
|
|
129
|
+
|
|
130
|
+
generate_trigger_plist() {
|
|
131
|
+
local TRIGGER_NAME="$1"
|
|
132
|
+
local SCHEDULE="$2"
|
|
133
|
+
local INTERVAL="$3"
|
|
134
|
+
|
|
135
|
+
local LABEL="ai.adaptic.${AGENT_FIRST}-${TRIGGER_NAME}"
|
|
136
|
+
local TRIGGER_FILE="$AGENT_DIR/schedules/triggers/${TRIGGER_NAME}.md"
|
|
137
|
+
local RUN_TRIGGER="$AGENT_DIR/scripts/local-triggers/run-trigger.sh"
|
|
138
|
+
|
|
139
|
+
generate_plist "$LABEL" \
|
|
140
|
+
"$RUN_TRIGGER|$TRIGGER_FILE" \
|
|
141
|
+
"$SCHEDULE" \
|
|
142
|
+
"$INTERVAL" \
|
|
143
|
+
""
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
# ── Generate all plists ─────────────────────────────────────────────────────
|
|
147
|
+
|
|
148
|
+
echo ""
|
|
149
|
+
echo "Generating launchd plist files..."
|
|
150
|
+
|
|
151
|
+
# 1. Main daemon (KeepAlive)
|
|
152
|
+
generate_plist "ai.adaptic.${AGENT_FIRST}-daemon" \
|
|
153
|
+
"${NODE_PATH}|${AGENT_DIR}/scripts/daemon/maestro-daemon.mjs" \
|
|
154
|
+
"" "" "true"
|
|
155
|
+
|
|
156
|
+
# 2. Slack events server (KeepAlive) — real-time Slack message handling
|
|
157
|
+
generate_plist "ai.adaptic.${AGENT_FIRST}-slack-events" \
|
|
158
|
+
"${NODE_PATH}|${AGENT_DIR}/scripts/slack-events-server.mjs" \
|
|
159
|
+
"" "" "true"
|
|
160
|
+
|
|
161
|
+
# 3. Inbox processor (every 5 minutes)
|
|
162
|
+
generate_trigger_plist "inbox-processor" "" "300"
|
|
163
|
+
|
|
164
|
+
# 4. Backlog executor (every 10 minutes)
|
|
165
|
+
generate_trigger_plist "backlog-executor" "" "600"
|
|
166
|
+
|
|
167
|
+
# 5. Meeting prep (every 15 minutes)
|
|
168
|
+
generate_trigger_plist "meeting-prep" "" "900"
|
|
169
|
+
|
|
170
|
+
# 6. Meeting action capture (every 30 minutes)
|
|
171
|
+
generate_trigger_plist "meeting-action-capture" "" "1800"
|
|
172
|
+
|
|
173
|
+
# 7. Midday sweep (daily at 12:00 local)
|
|
174
|
+
generate_trigger_plist "daily-midday-sweep" \
|
|
175
|
+
" <key>Hour</key>
|
|
176
|
+
<integer>12</integer>
|
|
177
|
+
<key>Minute</key>
|
|
178
|
+
<integer>0</integer>"
|
|
179
|
+
|
|
180
|
+
# 8. Weekly hiring (Monday 09:00)
|
|
181
|
+
generate_trigger_plist "weekly-hiring" \
|
|
182
|
+
" <key>Weekday</key>
|
|
183
|
+
<integer>1</integer>
|
|
184
|
+
<key>Hour</key>
|
|
185
|
+
<integer>9</integer>
|
|
186
|
+
<key>Minute</key>
|
|
187
|
+
<integer>0</integer>"
|
|
188
|
+
|
|
189
|
+
# 9. Weekly priorities (Monday 11:00)
|
|
190
|
+
generate_trigger_plist "weekly-priorities" \
|
|
191
|
+
" <key>Weekday</key>
|
|
192
|
+
<integer>1</integer>
|
|
193
|
+
<key>Hour</key>
|
|
194
|
+
<integer>11</integer>
|
|
195
|
+
<key>Minute</key>
|
|
196
|
+
<integer>0</integer>"
|
|
197
|
+
|
|
198
|
+
# 10. Weekly execution (Wednesday 14:00)
|
|
199
|
+
generate_trigger_plist "weekly-execution" \
|
|
200
|
+
" <key>Weekday</key>
|
|
201
|
+
<integer>3</integer>
|
|
202
|
+
<key>Hour</key>
|
|
203
|
+
<integer>14</integer>
|
|
204
|
+
<key>Minute</key>
|
|
205
|
+
<integer>0</integer>"
|
|
206
|
+
|
|
207
|
+
# 11. Weekly engineering health (Wednesday 10:00)
|
|
208
|
+
generate_trigger_plist "weekly-engineering-health" \
|
|
209
|
+
" <key>Weekday</key>
|
|
210
|
+
<integer>3</integer>
|
|
211
|
+
<key>Hour</key>
|
|
212
|
+
<integer>10</integer>
|
|
213
|
+
<key>Minute</key>
|
|
214
|
+
<integer>0</integer>"
|
|
215
|
+
|
|
216
|
+
# 12. Weekly strategic memo (Friday 14:00)
|
|
217
|
+
generate_trigger_plist "weekly-strategic-memo" \
|
|
218
|
+
" <key>Weekday</key>
|
|
219
|
+
<integer>5</integer>
|
|
220
|
+
<key>Hour</key>
|
|
221
|
+
<integer>14</integer>
|
|
222
|
+
<key>Minute</key>
|
|
223
|
+
<integer>0</integer>"
|
|
224
|
+
|
|
225
|
+
# 13. Quarterly self-assessment (1st of month, 09:00)
|
|
226
|
+
generate_trigger_plist "quarterly-self-assessment" \
|
|
227
|
+
" <key>Day</key>
|
|
228
|
+
<integer>1</integer>
|
|
229
|
+
<key>Hour</key>
|
|
230
|
+
<integer>9</integer>
|
|
231
|
+
<key>Minute</key>
|
|
232
|
+
<integer>0</integer>"
|
|
233
|
+
|
|
234
|
+
echo ""
|
|
235
|
+
echo "Generated $(ls "$PLIST_DIR"/*.plist 2>/dev/null | wc -l | tr -d ' ') plist files in $PLIST_DIR"
|
|
@@ -1,28 +1,34 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
|
-
# Install all local
|
|
2
|
+
# Install all local agent triggers as macOS launchd daemons
|
|
3
3
|
# Usage: ./scripts/local-triggers/install-all.sh
|
|
4
4
|
#
|
|
5
|
-
# This
|
|
6
|
-
# -
|
|
7
|
-
#
|
|
8
|
-
# - Weekly hiring review (Monday 09:00 GST)
|
|
9
|
-
# - Weekly priorities reset (Monday 11:00 GST)
|
|
10
|
-
# - Weekly execution review (Wednesday 14:00 GST)
|
|
11
|
-
# - Weekly strategic memo (Friday 14:00 GST)
|
|
12
|
-
#
|
|
13
|
-
# The morning brief and evening wrap run as remote cloud triggers.
|
|
5
|
+
# This generates plist files from agent config and installs them.
|
|
6
|
+
# The generate-plists.sh script reads config/agent.ts to determine
|
|
7
|
+
# the agent name and generates properly configured plist files.
|
|
14
8
|
|
|
15
9
|
set -e
|
|
16
10
|
|
|
17
11
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
12
|
+
AGENT_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
|
18
13
|
PLIST_DIR="$SCRIPT_DIR/plists"
|
|
14
|
+
GENERATOR="$SCRIPT_DIR/generate-plists.sh"
|
|
19
15
|
LAUNCH_AGENTS_DIR="$HOME/Library/LaunchAgents"
|
|
20
16
|
|
|
21
17
|
mkdir -p "$LAUNCH_AGENTS_DIR"
|
|
22
18
|
|
|
23
|
-
|
|
19
|
+
# Generate plists from agent config
|
|
20
|
+
if [ -x "$GENERATOR" ]; then
|
|
21
|
+
echo "Generating plist files from agent config..."
|
|
22
|
+
bash "$GENERATOR"
|
|
23
|
+
else
|
|
24
|
+
echo "Warning: generate-plists.sh not found — using existing plists"
|
|
25
|
+
fi
|
|
26
|
+
|
|
27
|
+
echo ""
|
|
28
|
+
echo "Installing local triggers..."
|
|
24
29
|
|
|
25
30
|
for plist in "$PLIST_DIR"/*.plist; do
|
|
31
|
+
[ -f "$plist" ] || continue
|
|
26
32
|
PLIST_NAME=$(basename "$plist")
|
|
27
33
|
LABEL=$(basename "$plist" .plist)
|
|
28
34
|
DST="$LAUNCH_AGENTS_DIR/$PLIST_NAME"
|
|
@@ -40,4 +46,4 @@ done
|
|
|
40
46
|
echo ""
|
|
41
47
|
echo "All local triggers installed."
|
|
42
48
|
echo "Check status: launchctl list | grep adaptic"
|
|
43
|
-
echo "Uninstall all: for f in ~/Library/LaunchAgents/ai.adaptic
|
|
49
|
+
echo "Uninstall all: for f in ~/Library/LaunchAgents/ai.adaptic.*.plist; do launchctl unload \"\$f\" && rm \"\$f\"; done"
|
|
@@ -126,35 +126,63 @@ echo ""
|
|
|
126
126
|
log "Creating state and log directories..."
|
|
127
127
|
|
|
128
128
|
DIRS=(
|
|
129
|
+
"config"
|
|
130
|
+
"knowledge/decisions"
|
|
131
|
+
"knowledge/decisions/archive"
|
|
132
|
+
"knowledge/entities"
|
|
133
|
+
"knowledge/memory"
|
|
134
|
+
"knowledge/sources"
|
|
135
|
+
"knowledge/syntheses"
|
|
136
|
+
"memory/interactions"
|
|
137
|
+
"memory/profiles/users"
|
|
138
|
+
"memory/profiles/channels"
|
|
139
|
+
"memory/precedents"
|
|
140
|
+
"memory/precedents/market-signals"
|
|
141
|
+
"memory/indexes"
|
|
142
|
+
"memory/templates"
|
|
129
143
|
"state/inbox/slack"
|
|
130
144
|
"state/inbox/gmail"
|
|
131
145
|
"state/inbox/calendar"
|
|
132
146
|
"state/inbox/sms"
|
|
147
|
+
"state/inbox/whatsapp"
|
|
133
148
|
"state/inbox/internal"
|
|
134
|
-
"state/
|
|
135
|
-
"state/
|
|
149
|
+
"state/inbox/attachments"
|
|
150
|
+
"state/inbox/processed"
|
|
136
151
|
"state/queues"
|
|
137
152
|
"state/dashboards"
|
|
138
153
|
"state/polling"
|
|
139
|
-
"
|
|
140
|
-
"
|
|
141
|
-
"
|
|
142
|
-
"
|
|
143
|
-
"
|
|
144
|
-
"
|
|
145
|
-
"
|
|
146
|
-
"
|
|
147
|
-
"
|
|
148
|
-
"
|
|
149
|
-
"memory/profiles/channels"
|
|
150
|
-
"memory/precedents"
|
|
151
|
-
"memory/templates"
|
|
154
|
+
"state/locks/outbound"
|
|
155
|
+
"state/triggers/priority"
|
|
156
|
+
"state/handoffs"
|
|
157
|
+
"state/huddle"
|
|
158
|
+
"state/indexes"
|
|
159
|
+
"state/rag"
|
|
160
|
+
"state/sessions"
|
|
161
|
+
"state/slack-responded"
|
|
162
|
+
"state/slack-thread-tracker"
|
|
163
|
+
"state/tmp"
|
|
152
164
|
"outputs/briefs"
|
|
153
165
|
"outputs/drafts"
|
|
154
166
|
"outputs/memos"
|
|
155
167
|
"outputs/research"
|
|
156
168
|
"outputs/tasks"
|
|
157
169
|
"outputs/sessions"
|
|
170
|
+
"logs/polling"
|
|
171
|
+
"logs/workflows"
|
|
172
|
+
"logs/sessions"
|
|
173
|
+
"logs/audit"
|
|
174
|
+
"logs/security"
|
|
175
|
+
"logs/evolution"
|
|
176
|
+
"logs/huddle"
|
|
177
|
+
"logs/daemon"
|
|
178
|
+
"logs/infra"
|
|
179
|
+
"logs/monitor"
|
|
180
|
+
"logs/phone"
|
|
181
|
+
"logs/sms"
|
|
182
|
+
"logs/whatsapp"
|
|
183
|
+
"logs/email"
|
|
184
|
+
"self-optimization/scenarios"
|
|
185
|
+
"tests"
|
|
158
186
|
)
|
|
159
187
|
|
|
160
188
|
for dir in "${DIRS[@]}"; do
|
|
@@ -337,16 +365,23 @@ echo ""
|
|
|
337
365
|
# Step 5: Generate and install launchd plists
|
|
338
366
|
# ---------------------------------------------------------------------------
|
|
339
367
|
|
|
340
|
-
log "
|
|
368
|
+
log "Generating and installing macOS launchd agents..."
|
|
341
369
|
|
|
342
370
|
LAUNCH_AGENTS_DIR="$HOME/Library/LaunchAgents"
|
|
343
|
-
|
|
371
|
+
PLIST_GENERATOR="$AGENT_DIR/scripts/local-triggers/generate-plists.sh"
|
|
372
|
+
PLIST_DIR="$AGENT_DIR/scripts/local-triggers/plists"
|
|
344
373
|
mkdir -p "$LAUNCH_AGENTS_DIR"
|
|
345
374
|
|
|
346
|
-
|
|
347
|
-
|
|
375
|
+
# Generate plists from agent config (handles agent name substitution)
|
|
376
|
+
if [ -x "$PLIST_GENERATOR" ]; then
|
|
377
|
+
bash "$PLIST_GENERATOR"
|
|
378
|
+
ok "Generated plist files from agent config"
|
|
348
379
|
else
|
|
349
|
-
|
|
380
|
+
warn "Plist generator not found at $PLIST_GENERATOR — using existing plists"
|
|
381
|
+
fi
|
|
382
|
+
|
|
383
|
+
if [ -d "$PLIST_DIR" ] && ls "$PLIST_DIR"/*.plist &>/dev/null; then
|
|
384
|
+
for plist in "$PLIST_DIR"/*.plist; do
|
|
350
385
|
PLIST_NAME=$(basename "$plist")
|
|
351
386
|
LABEL=$(basename "$plist" .plist)
|
|
352
387
|
DST="$LAUNCH_AGENTS_DIR/$PLIST_NAME"
|
|
@@ -354,17 +389,13 @@ else
|
|
|
354
389
|
# Unload if already loaded
|
|
355
390
|
launchctl unload "$DST" 2>/dev/null || true
|
|
356
391
|
|
|
357
|
-
# Copy
|
|
392
|
+
# Copy and load
|
|
358
393
|
cp "$plist" "$DST"
|
|
359
|
-
|
|
360
|
-
# Substitute the agent directory path in case the template has a different one
|
|
361
|
-
# Handles both legacy (sophie-ai) and current (maestro) path patterns
|
|
362
|
-
sed -i '' "s|/Users/[^<]*/sophie-ai|$AGENT_DIR|g; s|/Users/[^<]*/maestro|$AGENT_DIR|g" "$DST" 2>/dev/null || true
|
|
363
|
-
|
|
364
|
-
# Load
|
|
365
394
|
launchctl load "$DST"
|
|
366
395
|
ok "Loaded: $LABEL"
|
|
367
396
|
done
|
|
397
|
+
else
|
|
398
|
+
warn "No plist files found in $PLIST_DIR — skipping launchd setup"
|
|
368
399
|
fi
|
|
369
400
|
|
|
370
401
|
echo ""
|
|
@@ -31,6 +31,16 @@ import { fileURLToPath } from "node:url";
|
|
|
31
31
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
32
32
|
const ROOT = path.resolve(__dirname, "..");
|
|
33
33
|
|
|
34
|
+
// ── Singleton guard — prevent duplicate instances ────────────────────────────
|
|
35
|
+
try {
|
|
36
|
+
process.env.AGENT_ROOT = process.env.AGENT_ROOT || ROOT;
|
|
37
|
+
const { acquireLock } = await import(path.join(process.env.HOME, "maestro/lib/singleton.js"));
|
|
38
|
+
if (!acquireLock("slack-events")) {
|
|
39
|
+
console.log("[INIT] Slack events server already running — exiting");
|
|
40
|
+
process.exit(0);
|
|
41
|
+
}
|
|
42
|
+
} catch { /* maestro not available — skip guard */ }
|
|
43
|
+
|
|
34
44
|
// ── Load environment ──────────────────────────────────────────────────────────
|
|
35
45
|
|
|
36
46
|
const envPath = path.join(ROOT, ".env");
|