@adaptic/maestro 1.5.1 → 1.6.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.
- package/bin/maestro.mjs +35 -16
- package/package.json +1 -1
- package/scaffold/config/agent.json +52 -0
- package/scaffold/config/agent.ts.example +62 -192
- package/scripts/archive-email.sh +16 -2
- package/scripts/daemon/classifier.mjs +69 -59
- package/scripts/daemon/dispatcher.mjs +6 -6
- package/scripts/daemon/prompt-builder.mjs +30 -12
- package/scripts/daemon/responder.mjs +41 -21
- package/scripts/daemon/session-lock.mjs +4 -4
- package/scripts/daemon/sophie-daemon.mjs +49 -26
- package/scripts/local-triggers/run-trigger.sh +6 -6
- package/scripts/pdf-generation/build-document.mjs +24 -7
- package/scripts/poller/gmail-poller.mjs +32 -18
- package/scripts/poller/utils.mjs +34 -14
- package/scripts/send-email-threaded.py +30 -10
- package/scripts/send-email-with-attachment.py +26 -9
- package/scripts/send-email.sh +23 -10
- package/scripts/setup/generate-agent-env.mjs +92 -0
- package/scripts/setup/migrate-agent-to-sot.mjs +192 -0
- package/scripts/slack-events-server.mjs +78 -44
- package/scripts/slack-responded.sh +5 -2
- package/scripts/slack-send.sh +25 -9
- package/scripts/sms-handler.mjs +32 -18
- package/scripts/whatsapp-handler.mjs +27 -14
package/bin/maestro.mjs
CHANGED
|
@@ -762,26 +762,28 @@ function loadMaestroignore(cwd) {
|
|
|
762
762
|
}
|
|
763
763
|
|
|
764
764
|
function patternToRegex(pat) {
|
|
765
|
-
//
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
// Step order matters: ** must be captured before single * to preserve
|
|
772
|
-
// recursive semantics. * is intentionally NOT in the regex-escape char
|
|
773
|
-
// class so we can rewrite it after escaping the other specials.
|
|
774
|
-
if (pat.includes("*")) {
|
|
775
|
-
const escaped = pat
|
|
776
|
-
.replace(/[.+^${}()|[\]\\]/g, "\\$&")
|
|
765
|
+
// Helper: convert a glob-aware pattern to its regex source.
|
|
766
|
+
// ** must be substituted before single * so we don't double-rewrite it.
|
|
767
|
+
// * is intentionally outside the regex-escape char class so the
|
|
768
|
+
// glob substitution can find unescaped `*` characters afterward.
|
|
769
|
+
const globToRe = (p) =>
|
|
770
|
+
p.replace(/[.+^${}()|[\]\\]/g, "\\$&")
|
|
777
771
|
.replace(/\*\*/g, "<DBL>")
|
|
778
772
|
.replace(/\*/g, "[^/]*")
|
|
779
773
|
.replace(/<DBL>/g, ".*");
|
|
780
|
-
|
|
774
|
+
|
|
775
|
+
// Directory prefix: `scripts/daemon/` or `agents/sophie-*/` match
|
|
776
|
+
// anything under that path (recursive).
|
|
777
|
+
if (pat.endsWith("/")) {
|
|
778
|
+
return new RegExp("^" + globToRe(pat));
|
|
781
779
|
}
|
|
782
|
-
//
|
|
783
|
-
|
|
784
|
-
|
|
780
|
+
// Single-line glob: must match in full (no trailing slash).
|
|
781
|
+
if (pat.includes("*")) {
|
|
782
|
+
return new RegExp("^" + globToRe(pat) + "$");
|
|
783
|
+
}
|
|
784
|
+
// Exact: also matches anything underneath (gitignore semantics — a
|
|
785
|
+
// pattern without trailing slash still protects a directory if it
|
|
786
|
+
// resolves to one).
|
|
785
787
|
const escaped = pat.replace(/[.+^${}()|[\]\\]/g, "\\$&");
|
|
786
788
|
return new RegExp("^" + escaped + "(/|$)");
|
|
787
789
|
}
|
|
@@ -1013,6 +1015,23 @@ Per-file behaviour:
|
|
|
1013
1015
|
}
|
|
1014
1016
|
}
|
|
1015
1017
|
|
|
1018
|
+
// Regenerate config/agent.env from config/agent.json so newly-installed
|
|
1019
|
+
// (or already-installed) shell scripts find the agent identity vars.
|
|
1020
|
+
// Soft failure — older agents that haven't migrated to the SOT layout
|
|
1021
|
+
// yet don't have agent.json and that's fine.
|
|
1022
|
+
if (!flags.dryRun && existsSync(join(cwd, "config", "agent.json"))) {
|
|
1023
|
+
try {
|
|
1024
|
+
execFileSync(
|
|
1025
|
+
"node",
|
|
1026
|
+
[join(MAESTRO_ROOT, "scripts/setup/generate-agent-env.mjs")],
|
|
1027
|
+
{ cwd, env: { ...process.env, AGENT_DIR: cwd }, stdio: "pipe" }
|
|
1028
|
+
);
|
|
1029
|
+
ok("regenerated config/agent.env");
|
|
1030
|
+
} catch (e) {
|
|
1031
|
+
warn(`could not regenerate config/agent.env: ${e.message}`);
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1016
1035
|
// Summary
|
|
1017
1036
|
console.log();
|
|
1018
1037
|
console.log(`${C.bold}Upgrade summary${C.reset}${flags.dryRun ? " (dry run, nothing written)" : ""}`);
|
package/package.json
CHANGED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"firstName": "Sophie",
|
|
3
|
+
"lastName": "Nguyen",
|
|
4
|
+
"fullName": "Sophie Nguyen",
|
|
5
|
+
"title": "Chief of Staff",
|
|
6
|
+
"archetype": "executive-operator",
|
|
7
|
+
|
|
8
|
+
"email": "sophie@adaptic.ai",
|
|
9
|
+
"phone": "",
|
|
10
|
+
"slackMemberId": "",
|
|
11
|
+
|
|
12
|
+
"company": "Adaptic.ai",
|
|
13
|
+
"companyDomain": "adaptic.ai",
|
|
14
|
+
"companyDescription": "AI-native institutional asset management group",
|
|
15
|
+
|
|
16
|
+
"principal": {
|
|
17
|
+
"firstName": "Mehran",
|
|
18
|
+
"lastName": "Granfar",
|
|
19
|
+
"fullName": "Mehran Granfar",
|
|
20
|
+
"title": "CEO",
|
|
21
|
+
"email": "mehran@adaptic.ai",
|
|
22
|
+
"slackMemberId": ""
|
|
23
|
+
},
|
|
24
|
+
|
|
25
|
+
"machineName": "sophie-mini",
|
|
26
|
+
"repoSlug": "sophie-ai",
|
|
27
|
+
"launchdLabelPrefix": "ai.adaptic.sophie",
|
|
28
|
+
|
|
29
|
+
"timezone": "UTC",
|
|
30
|
+
"locale": "en-US",
|
|
31
|
+
|
|
32
|
+
"schedule": {
|
|
33
|
+
"morningBrief": "08:00",
|
|
34
|
+
"commsTriage": ["08:00", "12:00", "17:00"],
|
|
35
|
+
"eveningWrap": "18:00",
|
|
36
|
+
"overnightMonitoring": "22:00"
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
"communication": {
|
|
40
|
+
"defaultTone": "warm-professional",
|
|
41
|
+
"externalTone": "formal",
|
|
42
|
+
"voiceModes": [
|
|
43
|
+
{ "id": "agent", "label": "Agent's voice", "description": "Default for all operational communications" },
|
|
44
|
+
{ "id": "principal", "label": "Principal's voice", "description": "For communications attributed to the principal" },
|
|
45
|
+
{ "id": "internal", "label": "Internal notes", "description": "Analysis, risk flags — not sent externally" },
|
|
46
|
+
{ "id": "institutional", "label": "Institutional", "description": "Regulatory and legal correspondence" }
|
|
47
|
+
]
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
"responsibilities": [],
|
|
51
|
+
"operatingPrinciples": []
|
|
52
|
+
}
|
|
@@ -1,219 +1,89 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Maestro — Agent Configuration
|
|
2
|
+
* Maestro — Agent Configuration (TypeScript wrapper)
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* instead of hardcoding values.
|
|
4
|
+
* The canonical source of truth is `config/agent.json` — every framework
|
|
5
|
+
* file (Python, shell, Node, YAML) reads from that file (or its sibling
|
|
6
|
+
* `config/agent.env` which is auto-generated for shell consumers).
|
|
8
7
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
8
|
+
* This TypeScript wrapper exists ONLY to:
|
|
9
|
+
* - Provide compile-time types for code that imports the config
|
|
10
|
+
* - Expose computed convenience values (REPO_DIR, EMAIL_SIGNATURE, …)
|
|
12
11
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* To use in shell scripts, read the generated JSON:
|
|
17
|
-
* jq '.firstName' config/agent.json
|
|
12
|
+
* Do NOT add agent identity values here. Edit `config/agent.json` instead,
|
|
13
|
+
* then run `npx tsx scripts/setup/generate-agent-env.mjs` to regenerate
|
|
14
|
+
* `config/agent.env`.
|
|
18
15
|
*/
|
|
19
16
|
|
|
20
|
-
|
|
21
|
-
// Agent Identity
|
|
22
|
-
// ---------------------------------------------------------------------------
|
|
23
|
-
|
|
24
|
-
export const agent = {
|
|
25
|
-
/** Agent's first name — used in greetings, signatures, and voice references */
|
|
26
|
-
firstName: 'Sophie',
|
|
27
|
-
|
|
28
|
-
/** Agent's last name */
|
|
29
|
-
lastName: 'Nguyen',
|
|
30
|
-
|
|
31
|
-
/** Full display name — appears in email signatures, Slack profile, documents */
|
|
32
|
-
fullName: 'Sophie Nguyen',
|
|
33
|
-
|
|
34
|
-
/** Job title — used in CLAUDE.md identity section and email signatures */
|
|
35
|
-
title: 'Chief of Staff',
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Role archetype — determines which operating principles, communication rules,
|
|
39
|
-
* and workflow templates are most appropriate. Used by /init-maestro to select
|
|
40
|
-
* defaults. Common archetypes:
|
|
41
|
-
* 'executive-operator' — Chief of Staff, COO-type roles
|
|
42
|
-
* 'technical-leader' — CTO, Head of Engineering, Chief Scientist
|
|
43
|
-
* 'commercial-leader' — Head of Sales, IR, BD
|
|
44
|
-
* 'compliance-officer' — Head of Compliance, Legal, Regulatory
|
|
45
|
-
* 'product-leader' — Head of Product, UX
|
|
46
|
-
* 'operations-leader' — Head of Ops, Fund Ops, HR
|
|
47
|
-
*/
|
|
48
|
-
archetype: 'executive-operator' as const,
|
|
49
|
-
|
|
50
|
-
// ---------------------------------------------------------------------------
|
|
51
|
-
// Contact Details
|
|
52
|
-
// ---------------------------------------------------------------------------
|
|
53
|
-
|
|
54
|
-
/** Agent's primary email address */
|
|
55
|
-
email: 'sophie@adaptic.ai',
|
|
56
|
-
|
|
57
|
-
/** Agent's phone number (E.164 format) — used for Twilio SMS/voice/WhatsApp */
|
|
58
|
-
phone: '+16282656712',
|
|
17
|
+
import agentData from './agent.json' with { type: 'json' };
|
|
59
18
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
19
|
+
export interface VoiceMode {
|
|
20
|
+
id: string;
|
|
21
|
+
label: string;
|
|
22
|
+
description: string;
|
|
23
|
+
}
|
|
63
24
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
25
|
+
export interface PrincipalConfig {
|
|
26
|
+
firstName: string;
|
|
27
|
+
lastName: string;
|
|
28
|
+
fullName: string;
|
|
29
|
+
title: string;
|
|
30
|
+
email: string;
|
|
31
|
+
slackMemberId: string;
|
|
32
|
+
}
|
|
67
33
|
|
|
68
|
-
|
|
69
|
-
|
|
34
|
+
export interface AgentConfig {
|
|
35
|
+
firstName: string;
|
|
36
|
+
lastName: string;
|
|
37
|
+
fullName: string;
|
|
38
|
+
title: string;
|
|
39
|
+
archetype: string;
|
|
70
40
|
|
|
71
|
-
|
|
72
|
-
|
|
41
|
+
email: string;
|
|
42
|
+
phone: string;
|
|
43
|
+
slackMemberId: string;
|
|
73
44
|
|
|
74
|
-
|
|
75
|
-
|
|
45
|
+
company: string;
|
|
46
|
+
companyDomain: string;
|
|
47
|
+
companyDescription: string;
|
|
76
48
|
|
|
77
|
-
|
|
78
|
-
// Reporting Line
|
|
79
|
-
// ---------------------------------------------------------------------------
|
|
49
|
+
principal: PrincipalConfig;
|
|
80
50
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
lastName: 'Granfar',
|
|
85
|
-
fullName: 'Mehran Granfar',
|
|
86
|
-
title: 'CEO',
|
|
87
|
-
email: 'mehran@adaptic.ai',
|
|
88
|
-
slackMemberId: 'U097N5R0M7U',
|
|
89
|
-
},
|
|
51
|
+
machineName: string;
|
|
52
|
+
repoSlug: string;
|
|
53
|
+
launchdLabelPrefix: string;
|
|
90
54
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
// ---------------------------------------------------------------------------
|
|
55
|
+
timezone: string;
|
|
56
|
+
locale: string;
|
|
94
57
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* LaunchD label prefix for all daemon processes.
|
|
103
|
-
* Processes will be named: {labelPrefix}-poller, {labelPrefix}-inbox-processor, etc.
|
|
104
|
-
*/
|
|
105
|
-
launchdLabelPrefix: 'ai.adaptic.sophie',
|
|
58
|
+
schedule: {
|
|
59
|
+
morningBrief: string;
|
|
60
|
+
commsTriage: string[];
|
|
61
|
+
eveningWrap: string;
|
|
62
|
+
overnightMonitoring: string;
|
|
63
|
+
};
|
|
106
64
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
65
|
+
communication: {
|
|
66
|
+
defaultTone: string;
|
|
67
|
+
externalTone: string;
|
|
68
|
+
voiceModes: VoiceMode[];
|
|
69
|
+
};
|
|
110
70
|
|
|
111
|
-
|
|
112
|
-
|
|
71
|
+
responsibilities: string[];
|
|
72
|
+
operatingPrinciples: string[];
|
|
73
|
+
}
|
|
113
74
|
|
|
114
|
-
|
|
115
|
-
locale: 'en-US',
|
|
75
|
+
export const agent: AgentConfig = agentData as AgentConfig;
|
|
116
76
|
|
|
117
|
-
|
|
118
|
-
schedule: {
|
|
119
|
-
morningBrief: '06:00',
|
|
120
|
-
commsTriage: ['08:00', '12:00', '17:00'],
|
|
121
|
-
eveningWrap: '18:00',
|
|
122
|
-
overnightMonitoring: '22:00',
|
|
123
|
-
},
|
|
77
|
+
// ── Derived values ──────────────────────────────────────────────────────
|
|
124
78
|
|
|
125
|
-
|
|
126
|
-
// Communication Style
|
|
127
|
-
// ---------------------------------------------------------------------------
|
|
128
|
-
|
|
129
|
-
communication: {
|
|
130
|
-
/**
|
|
131
|
-
* Default tone: 'formal' | 'warm-professional' | 'casual'
|
|
132
|
-
* This affects how the agent writes emails, Slack messages, and documents.
|
|
133
|
-
*/
|
|
134
|
-
defaultTone: 'warm-professional' as const,
|
|
135
|
-
|
|
136
|
-
/** Tone used for external communications (partners, candidates, vendors) */
|
|
137
|
-
externalTone: 'formal' as const,
|
|
138
|
-
|
|
139
|
-
/**
|
|
140
|
-
* Voice modes available to this agent.
|
|
141
|
-
* The first entry is the default voice.
|
|
142
|
-
*/
|
|
143
|
-
voiceModes: [
|
|
144
|
-
{
|
|
145
|
-
id: 'agent',
|
|
146
|
-
label: "Agent's voice",
|
|
147
|
-
description: 'Default for all operational communications',
|
|
148
|
-
},
|
|
149
|
-
{
|
|
150
|
-
id: 'principal',
|
|
151
|
-
label: "Principal's voice",
|
|
152
|
-
description: 'For communications attributed to the principal',
|
|
153
|
-
},
|
|
154
|
-
{
|
|
155
|
-
id: 'internal',
|
|
156
|
-
label: 'Internal notes',
|
|
157
|
-
description: 'Analysis, risk flags — not sent externally',
|
|
158
|
-
},
|
|
159
|
-
{
|
|
160
|
-
id: 'institutional',
|
|
161
|
-
label: 'Institutional',
|
|
162
|
-
description: 'Regulatory and legal correspondence',
|
|
163
|
-
},
|
|
164
|
-
],
|
|
165
|
-
},
|
|
166
|
-
|
|
167
|
-
// ---------------------------------------------------------------------------
|
|
168
|
-
// Responsibilities
|
|
169
|
-
// ---------------------------------------------------------------------------
|
|
170
|
-
|
|
171
|
-
/**
|
|
172
|
-
* Core responsibilities — used to generate the CLAUDE.md identity section
|
|
173
|
-
* and to scope the agent's operating principles. Keep to 3-7 bullet points.
|
|
174
|
-
*/
|
|
175
|
-
responsibilities: [
|
|
176
|
-
'Track every commitment until it closes',
|
|
177
|
-
'Coordinate 30+ specialist agent teams',
|
|
178
|
-
'Manage all inbound/outbound communications',
|
|
179
|
-
'Execute strategic priorities autonomously',
|
|
180
|
-
'Maintain institutional memory across sessions',
|
|
181
|
-
],
|
|
182
|
-
|
|
183
|
-
/**
|
|
184
|
-
* Operating principles — the behavioural rules that define how the agent works.
|
|
185
|
-
* These are injected into CLAUDE.md and guide all decision-making.
|
|
186
|
-
*/
|
|
187
|
-
operatingPrinciples: [
|
|
188
|
-
'Follow-through over brilliance — track every commitment until it closes',
|
|
189
|
-
'Concise over comprehensive — sharp recommendations, not exhaustive reports',
|
|
190
|
-
'Evidence over opinion — cite sources for recommendations',
|
|
191
|
-
'Bias to action — act decisively on operational matters; escalate only strategic commitments',
|
|
192
|
-
'Audit everything — every action is logged',
|
|
193
|
-
'Full autonomy, full accountability — operate independently with complete audit trail',
|
|
194
|
-
],
|
|
195
|
-
} as const;
|
|
196
|
-
|
|
197
|
-
// ---------------------------------------------------------------------------
|
|
198
|
-
// Derived Values (computed from the config above)
|
|
199
|
-
// ---------------------------------------------------------------------------
|
|
200
|
-
|
|
201
|
-
/** Uppercase first name for shell variable prefixes (e.g. SOPHIE_AI_DIR) */
|
|
79
|
+
/** Uppercase first name for shell variable prefixes */
|
|
202
80
|
export const AGENT_UPPER = agent.firstName.toUpperCase();
|
|
203
81
|
|
|
204
|
-
/** Lowercase first name for file/directory naming
|
|
82
|
+
/** Lowercase first name for file/directory naming */
|
|
205
83
|
export const AGENT_LOWER = agent.firstName.toLowerCase();
|
|
206
84
|
|
|
207
|
-
/**
|
|
85
|
+
/** Repo directory in the user's home (e.g. ~/sophie-ai) */
|
|
208
86
|
export const REPO_DIR = `~/${agent.repoSlug}`;
|
|
209
87
|
|
|
210
|
-
/** Email signature
|
|
88
|
+
/** Email signature one-liner */
|
|
211
89
|
export const EMAIL_SIGNATURE = `${agent.fullName} | ${agent.title} | ${agent.company}`;
|
|
212
|
-
|
|
213
|
-
// ---------------------------------------------------------------------------
|
|
214
|
-
// Type Exports
|
|
215
|
-
// ---------------------------------------------------------------------------
|
|
216
|
-
|
|
217
|
-
export type AgentConfig = typeof agent;
|
|
218
|
-
export type VoiceMode = (typeof agent.communication.voiceModes)[number];
|
|
219
|
-
export type Archetype = typeof agent.archetype;
|
package/scripts/archive-email.sh
CHANGED
|
@@ -4,14 +4,28 @@
|
|
|
4
4
|
# Requires GMAIL_APP_PASSWORD environment variable
|
|
5
5
|
set -e
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
8
|
+
AGENT_REPO_DIR="${AGENT_DIR:-$(cd "$SCRIPT_DIR/.." && pwd)}"
|
|
9
|
+
|
|
10
|
+
# Pull AGENT_EMAIL + GMAIL_APP_PASSWORD from agent.env + .env
|
|
11
|
+
[ -f "$AGENT_REPO_DIR/config/agent.env" ] && source "$AGENT_REPO_DIR/config/agent.env"
|
|
12
|
+
[ -f "$AGENT_REPO_DIR/.env" ] && source "$AGENT_REPO_DIR/.env"
|
|
13
|
+
|
|
14
|
+
if [ -z "${GMAIL_APP_PASSWORD:-}" ]; then
|
|
15
|
+
echo "ERROR: GMAIL_APP_PASSWORD not set" >&2
|
|
16
|
+
exit 1
|
|
17
|
+
fi
|
|
18
|
+
export AGENT_EMAIL
|
|
19
|
+
export GMAIL_APP_PASSWORD
|
|
8
20
|
|
|
9
21
|
python3 << 'PYEOF'
|
|
10
22
|
import imaplib
|
|
11
23
|
import os
|
|
12
24
|
|
|
13
|
-
user =
|
|
25
|
+
user = os.environ.get("AGENT_EMAIL", "")
|
|
14
26
|
password = os.environ["GMAIL_APP_PASSWORD"]
|
|
27
|
+
if not user:
|
|
28
|
+
raise SystemExit("AGENT_EMAIL not set — check config/agent.env")
|
|
15
29
|
|
|
16
30
|
mail = imaplib.IMAP4_SSL("imap.gmail.com")
|
|
17
31
|
mail.login(user, password)
|