@adaptic/maestro 1.8.0 → 1.8.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adaptic/maestro",
3
- "version": "1.8.0",
3
+ "version": "1.8.1",
4
4
  "description": "Maestro — Autonomous AI agent operating system. Deploy AI employees on dedicated Mac minis.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -36,14 +36,38 @@ MAESTRO_PLIST_ARCH_VERSION="1"
36
36
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
37
37
  AGENT_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)"
38
38
  PLIST_DIR="$SCRIPT_DIR/plists"
39
- AGENT_CONFIG="$AGENT_DIR/config/agent.ts"
40
39
 
41
- # Extract agent first name from config/agent.ts
42
- if [ -f "$AGENT_CONFIG" ]; then
43
- AGENT_FIRST=$(grep "firstName:" "$AGENT_CONFIG" | head -1 | sed "s/.*firstName:[[:space:]]*['\"]//; s/['\"].*//" | tr '[:upper:]' '[:lower:]')
40
+ # Resolve the agent's first name. Order of preference:
41
+ # 1. config/agent.json — single source of truth (SOT) introduced when
42
+ # maestro split identity out of TypeScript into JSON. Use jq if
43
+ # available, otherwise a small awk fallback.
44
+ # 2. config/agent.ts — legacy path; only works if firstName is
45
+ # defined inline (string literal), not as a type-only declaration
46
+ # like `firstName: string;`.
47
+ # 3. Directory name — last-resort fallback (e.g. ~/ravi-ai → ravi).
48
+ AGENT_JSON="$AGENT_DIR/config/agent.json"
49
+ AGENT_TS="$AGENT_DIR/config/agent.ts"
50
+ AGENT_FIRST=""
51
+
52
+ if [ -z "$AGENT_FIRST" ] && [ -f "$AGENT_JSON" ]; then
53
+ if command -v jq >/dev/null 2>&1; then
54
+ AGENT_FIRST=$(jq -r '.firstName // empty' "$AGENT_JSON" 2>/dev/null | tr '[:upper:]' '[:lower:]')
55
+ else
56
+ # Awk fallback — handle "firstName": "value" with optional whitespace
57
+ AGENT_FIRST=$(awk -F'"' '/"firstName"[[:space:]]*:/ { print tolower($4); exit }' "$AGENT_JSON")
58
+ fi
59
+ fi
60
+
61
+ if [ -z "$AGENT_FIRST" ] && [ -f "$AGENT_TS" ]; then
62
+ # Only accept a quoted inline value — `firstName: string;` (type
63
+ # declaration) must not match. The grep requires a quote on the line.
64
+ AGENT_FIRST=$(grep "firstName:[[:space:]]*['\"]" "$AGENT_TS" \
65
+ | head -1 \
66
+ | sed "s/.*firstName:[[:space:]]*['\"]//; s/['\"].*//" \
67
+ | tr '[:upper:]' '[:lower:]')
44
68
  fi
45
69
 
46
- # Fall back to directory name if config not set or UNCONFIGURED
70
+ # Fall back to directory name if config not set or UNCONFIGURED.
47
71
  if [ -z "$AGENT_FIRST" ] || [ "$AGENT_FIRST" = "unconfigured" ]; then
48
72
  AGENT_FIRST=$(basename "$AGENT_DIR" | sed 's/-ai$//')
49
73
  fi
@@ -167,6 +167,75 @@ test("daemon plist remains a KeepAlive job (not a cadence enqueue)", async () =>
167
167
  } finally { await rmRoot(root); }
168
168
  });
169
169
 
170
+ test("generator reads firstName from agent.json when agent.ts is type-only (SOT layout)", async () => {
171
+ // Reproduce the SOT layout where agent.ts only declares interfaces
172
+ // (containing `firstName: string;` as a type, not a value) and the
173
+ // actual identity lives in agent.json. The legacy resolver matched the
174
+ // interface line and produced labels like
175
+ // `ai.adaptic. firstname: string;-…` — guard against regression.
176
+ const root = join(
177
+ tmpdir(),
178
+ `plist-sot-test-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`
179
+ );
180
+ await fsp.mkdir(root, { recursive: true });
181
+ // Manually wire the same fixture-builder used by other tests, except
182
+ // overwrite agent.ts to look like a type-only TypeScript file and add
183
+ // a real agent.json.
184
+ await fsp.mkdir(join(root, "config"), { recursive: true });
185
+ writeFileSync(
186
+ join(root, "config/agent.ts"),
187
+ `// Type declarations only — value lives in agent.json
188
+ export interface PrincipalConfig { firstName: string; }
189
+ export interface AgentConfig { firstName: string; }
190
+ `,
191
+ );
192
+ writeFileSync(
193
+ join(root, "config/agent.json"),
194
+ JSON.stringify({ firstName: "Sigrid", lastName: "Test" }) + "\n",
195
+ );
196
+ writeFileSync(join(root, "package.json"), '{"name":"t"}');
197
+
198
+ await fsp.mkdir(join(root, "scripts/local-triggers"), { recursive: true });
199
+ await fsp.mkdir(join(root, "scripts/daemon"), { recursive: true });
200
+ await fsp.mkdir(join(root, "scripts/cadence"), { recursive: true });
201
+ // Same set of helper scripts as makeAgent().
202
+ const { copyFileSync } = await import("node:fs");
203
+ copyFileSync(GENERATOR, join(root, "scripts/local-triggers/generate-plists.sh"));
204
+ copyFileSync(
205
+ join(MAESTRO_ROOT, "scripts/local-triggers/run-trigger.sh"),
206
+ join(root, "scripts/local-triggers/run-trigger.sh"),
207
+ );
208
+ copyFileSync(
209
+ join(MAESTRO_ROOT, "scripts/daemon/launchd-wrapper.sh"),
210
+ join(root, "scripts/daemon/launchd-wrapper.sh"),
211
+ );
212
+ copyFileSync(
213
+ join(MAESTRO_ROOT, "scripts/daemon/launchd-wrapper-generic.sh"),
214
+ join(root, "scripts/daemon/launchd-wrapper-generic.sh"),
215
+ );
216
+ copyFileSync(
217
+ join(MAESTRO_ROOT, "scripts/cadence/launchd-cadence-wrapper.sh"),
218
+ join(root, "scripts/cadence/launchd-cadence-wrapper.sh"),
219
+ );
220
+ copyFileSync(
221
+ join(MAESTRO_ROOT, "scripts/cadence/enqueue-cadence-tick.mjs"),
222
+ join(root, "scripts/cadence/enqueue-cadence-tick.mjs"),
223
+ );
224
+ await fsp.chmod(join(root, "scripts/local-triggers/generate-plists.sh"), 0o755);
225
+
226
+ try {
227
+ const r = runGenerator(root);
228
+ assert.equal(r.status, 0, r.stderr);
229
+ const plists = listPlists(root);
230
+ assert.ok(plists.length >= 1);
231
+ for (const p of plists) {
232
+ assert.match(p, /^ai\.adaptic\.sigrid-/, `expected "sigrid" label, got: ${p}`);
233
+ assert.ok(!p.includes("string"),
234
+ `label leaked the TypeScript type: ${p}`);
235
+ }
236
+ } finally { await rmRoot(root); }
237
+ });
238
+
170
239
  test("regeneration clears stale plists", async () => {
171
240
  const root = await makeAgent("frank");
172
241
  try {