@rubytech/create-realagent 1.0.435 → 1.0.437

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 (41) hide show
  1. package/dist/uninstall.js +44 -26
  2. package/package.json +1 -1
  3. package/payload/maxy/server.js +295 -84
  4. package/payload/platform/neo4j/schema.cypher +6 -0
  5. package/payload/platform/plugins/admin/mcp/dist/index.js +379 -10
  6. package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
  7. package/payload/platform/plugins/admin/skills/onboarding/skill.md +1 -1
  8. package/payload/platform/plugins/admin/skills/plugin-management/skill.md +16 -58
  9. package/payload/platform/plugins/anthropic/PLUGIN.md +2 -2
  10. package/payload/platform/plugins/contacts/mcp/dist/index.js +34 -16
  11. package/payload/platform/plugins/contacts/mcp/dist/index.js.map +1 -1
  12. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-create.d.ts +2 -2
  13. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-create.d.ts.map +1 -1
  14. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-create.js +34 -9
  15. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-create.js.map +1 -1
  16. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-delete.d.ts +2 -1
  17. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-delete.d.ts.map +1 -1
  18. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-delete.js +13 -7
  19. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-delete.js.map +1 -1
  20. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-list.d.ts +2 -1
  21. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-list.d.ts.map +1 -1
  22. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-list.js +1 -0
  23. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-list.js.map +1 -1
  24. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-lookup.d.ts +2 -1
  25. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-lookup.d.ts.map +1 -1
  26. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-lookup.js +10 -1
  27. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-lookup.js.map +1 -1
  28. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-update.d.ts +1 -0
  29. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-update.d.ts.map +1 -1
  30. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-update.js +8 -4
  31. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-update.js.map +1 -1
  32. package/payload/platform/plugins/docs/references/contacts-guide.md +7 -5
  33. package/payload/platform/plugins/email/mcp/dist/scripts/email-auto-respond.d.ts +1 -1
  34. package/payload/platform/plugins/email/mcp/dist/scripts/email-auto-respond.js +30 -4
  35. package/payload/platform/plugins/email/mcp/dist/scripts/email-auto-respond.js.map +1 -1
  36. package/payload/platform/plugins/email/mcp/dist/scripts/email-fetch.js +31 -3
  37. package/payload/platform/plugins/email/mcp/dist/scripts/email-fetch.js.map +1 -1
  38. package/payload/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.d.ts +1 -1
  39. package/payload/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.js +30 -2
  40. package/payload/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.js.map +1 -1
  41. package/payload/platform/templates/agents/admin/IDENTITY.md +1 -1
@@ -2845,7 +2845,7 @@ var serveStatic = (options = { root: "" }) => {
2845
2845
  };
2846
2846
 
2847
2847
  // server/index.ts
2848
- import { readFileSync as readFileSync17, existsSync as existsSync17, watchFile } from "fs";
2848
+ import { readFileSync as readFileSync18, existsSync as existsSync18, watchFile } from "fs";
2849
2849
  import { resolve as resolve15, join as join5 } from "path";
2850
2850
  import { homedir as homedir2 } from "os";
2851
2851
 
@@ -3128,7 +3128,7 @@ async function GET() {
3128
3128
 
3129
3129
  // app/lib/claude-agent.ts
3130
3130
  import Anthropic from "@anthropic-ai/sdk";
3131
- import { spawn } from "child_process";
3131
+ import { spawn as spawn2 } from "child_process";
3132
3132
  import { resolve as resolve4 } from "path";
3133
3133
  import { readFileSync as readFileSync5, readdirSync, existsSync as existsSync5, mkdirSync as mkdirSync2, createWriteStream, statSync as statSync2, unlinkSync } from "fs";
3134
3134
 
@@ -3267,6 +3267,7 @@ function buildX11Env(chromiumWrapperPath) {
3267
3267
  // app/lib/neo4j-store.ts
3268
3268
  import neo4j from "neo4j-driver";
3269
3269
  import { randomUUID } from "crypto";
3270
+ import { spawn } from "child_process";
3270
3271
  import { readFileSync as readFileSync4 } from "fs";
3271
3272
  import { resolve as resolve3 } from "path";
3272
3273
  var PLATFORM_ROOT2 = process.env.MAXY_PLATFORM_ROOT ?? resolve3(process.cwd(), "..");
@@ -3620,47 +3621,162 @@ async function deleteConversation(conversationId) {
3620
3621
  }
3621
3622
  }
3622
3623
  var GENERIC_MESSAGE = /^(h(i|ello|ey|owdy)|yo|sup|thanks|thank you|ok|okay|yes|no|good\s*(morning|afternoon|evening|night)|greetings|what'?s\s*up)[\s!?.,:;]*$/i;
3623
- function deriveLabel(message) {
3624
+ var SESSION_LABEL_MSG_CAP = 500;
3625
+ var SESSION_LABEL_TIMEOUT_MS = 15e3;
3626
+ var SESSION_LABEL_MODEL = "claude-haiku-4-5-20251001";
3627
+ var SESSION_LABEL_MAX_WORDS = 6;
3628
+ var SESSION_LABEL_MAX_ATTEMPTS = 3;
3629
+ var SESSION_LABEL_MAX_STDERR = 2048;
3630
+ function isMessageUseful(message) {
3624
3631
  const trimmed = message.trim();
3625
- if (trimmed.length < 4) return null;
3626
- if (GENERIC_MESSAGE.test(trimmed)) return null;
3627
- if (trimmed.startsWith("[New session.")) return null;
3628
- const firstSentence = trimmed.split(/[.!?\n]/)[0].trim();
3629
- if (firstSentence.length < 4) return null;
3630
- const words = firstSentence.split(/\s+/).slice(0, 8);
3631
- return words.join(" ");
3632
+ if (trimmed.length < 4) return false;
3633
+ if (trimmed.startsWith('{"')) return false;
3634
+ if (GENERIC_MESSAGE.test(trimmed)) return false;
3635
+ if (trimmed.startsWith("[New session.")) return false;
3636
+ return true;
3637
+ }
3638
+ var labelAccumulator = /* @__PURE__ */ new Map();
3639
+ var _spawnOverride = null;
3640
+ var SESSION_LABEL_SYSTEM = `You are a session labeler. Given the opening messages of a conversation with an AI assistant, produce a concise topic label.
3641
+
3642
+ Rules:
3643
+ - Exactly 3 to 6 words
3644
+ - Summarize the user's intent, do not copy verbatim
3645
+ - Capitalize the first word only (sentence case)
3646
+ - No punctuation, no quotes
3647
+ - If the messages are too vague or meaningless to summarize, respond with exactly: SKIP`;
3648
+ async function generateSessionLabel(messages) {
3649
+ const cappedMessages = messages.map((m) => m.slice(0, SESSION_LABEL_MSG_CAP));
3650
+ const userContent = cappedMessages.map((m, i) => `Message ${i + 1}: ${m}`).join("\n");
3651
+ const prompt = `${SESSION_LABEL_SYSTEM}
3652
+
3653
+ ${userContent}`;
3654
+ const args = [
3655
+ "--print",
3656
+ "--model",
3657
+ SESSION_LABEL_MODEL,
3658
+ "--max-turns",
3659
+ "1",
3660
+ "--permission-mode",
3661
+ "dontAsk",
3662
+ prompt
3663
+ ];
3664
+ return new Promise((resolve16) => {
3665
+ let stdout = "";
3666
+ let stderr = "";
3667
+ const spawnFn = _spawnOverride ?? spawn;
3668
+ const proc = spawnFn("claude", args, {
3669
+ stdio: ["ignore", "pipe", "pipe"]
3670
+ });
3671
+ proc.stdout?.on("data", (chunk) => {
3672
+ stdout += chunk.toString("utf-8");
3673
+ });
3674
+ proc.stderr?.on("data", (chunk) => {
3675
+ if (stderr.length < SESSION_LABEL_MAX_STDERR) {
3676
+ stderr += chunk.toString("utf-8").slice(0, SESSION_LABEL_MAX_STDERR - stderr.length);
3677
+ }
3678
+ });
3679
+ const timer = setTimeout(() => {
3680
+ proc.kill("SIGTERM");
3681
+ console.error("[persist] autoLabel: haiku subprocess timed out");
3682
+ resolve16(null);
3683
+ }, SESSION_LABEL_TIMEOUT_MS);
3684
+ proc.on("error", (err) => {
3685
+ clearTimeout(timer);
3686
+ console.error(`[persist] autoLabel: subprocess error \u2014 ${err.message}`);
3687
+ resolve16(null);
3688
+ });
3689
+ proc.on("close", (code) => {
3690
+ clearTimeout(timer);
3691
+ if (code !== 0) {
3692
+ console.error(`[persist] autoLabel: subprocess exited code=${code}${stderr ? ` stderr=${stderr.trim().slice(0, 200)}` : ""}`);
3693
+ resolve16(null);
3694
+ return;
3695
+ }
3696
+ const text = stdout.trim();
3697
+ if (!text) {
3698
+ console.error("[persist] autoLabel: haiku returned empty response");
3699
+ resolve16(null);
3700
+ return;
3701
+ }
3702
+ if (text === "SKIP") {
3703
+ console.error("[persist] autoLabel: haiku returned SKIP \u2014 messages too vague");
3704
+ resolve16(null);
3705
+ return;
3706
+ }
3707
+ const words = text.split(/\s+/).slice(0, SESSION_LABEL_MAX_WORDS);
3708
+ const label = words.join(" ");
3709
+ console.error(`[persist] autoLabel: haiku response="${label}"`);
3710
+ resolve16(label);
3711
+ });
3712
+ });
3632
3713
  }
3633
3714
  async function autoLabelSession(conversationId, userMessage) {
3634
- const label = deriveLabel(userMessage);
3635
- if (!label) return;
3636
- let embedding = null;
3637
- try {
3638
- embedding = await embed(label);
3639
- } catch (err) {
3640
- console.error(`[persist] Conversation embedding failed, labelling without: ${err instanceof Error ? err.message : String(err)}`);
3715
+ if (!conversationId) return;
3716
+ if (!isMessageUseful(userMessage)) {
3717
+ const reason = userMessage.trim().startsWith('{"') ? "JSON envelope" : userMessage.trim().length < 4 ? "too short" : GENERIC_MESSAGE.test(userMessage.trim()) ? "greeting" : "directive";
3718
+ console.error(`[persist] autoLabel: skipped ${conversationId.slice(0, 8)}\u2026 \u2014 ${reason}`);
3719
+ return;
3641
3720
  }
3642
- const session = getSession();
3721
+ let entry = labelAccumulator.get(conversationId);
3722
+ if (!entry) {
3723
+ entry = { messages: [], pending: false, attempts: 0 };
3724
+ labelAccumulator.set(conversationId, entry);
3725
+ }
3726
+ if (entry.attempts >= SESSION_LABEL_MAX_ATTEMPTS) {
3727
+ console.error(`[persist] autoLabel: evicted ${conversationId.slice(0, 8)}\u2026 after ${SESSION_LABEL_MAX_ATTEMPTS} attempts`);
3728
+ labelAccumulator.delete(conversationId);
3729
+ return;
3730
+ }
3731
+ entry.messages.push(userMessage.trim());
3732
+ if (entry.pending) {
3733
+ console.error(`[persist] autoLabel: accumulated for ${conversationId.slice(0, 8)}\u2026 (pending, ${entry.messages.length} msgs)`);
3734
+ return;
3735
+ }
3736
+ entry.pending = true;
3737
+ entry.attempts++;
3643
3738
  try {
3644
- const result = await session.run(
3645
- `MATCH (c:Conversation {conversationId: $conversationId})
3646
- WHERE c.name IS NULL
3647
- WITH c
3648
- OPTIONAL MATCH (m:Message)-[:PART_OF]->(c)
3649
- WHERE m.role = 'user'
3650
- WITH c, count(m) AS userCount
3651
- WHERE userCount <= 3
3652
- SET c.name = $label, c.updatedAt = datetime()
3653
- ${embedding ? ", c.embedding = $embedding" : ""}
3654
- RETURN c.name AS name`,
3655
- { conversationId, label, ...embedding ? { embedding } : {} }
3656
- );
3657
- if (result.records.length > 0) {
3658
- console.error(`[persist] Auto-labeled session ${conversationId.slice(0, 8)}\u2026: "${label}"${embedding ? " (embedded)" : ""}`);
3739
+ const label = await generateSessionLabel(entry.messages);
3740
+ if (!label) {
3741
+ entry.pending = false;
3742
+ return;
3743
+ }
3744
+ const fullLabel = `${label} \xB7 ${conversationId.slice(0, 8)}`;
3745
+ let embedding = null;
3746
+ try {
3747
+ embedding = await embed(fullLabel);
3748
+ } catch (err) {
3749
+ console.error(`[persist] Conversation embedding failed, labelling without: ${err instanceof Error ? err.message : String(err)}`);
3750
+ }
3751
+ const session = getSession();
3752
+ try {
3753
+ const result = await session.run(
3754
+ `MATCH (c:Conversation {conversationId: $conversationId})
3755
+ WHERE c.name IS NULL
3756
+ WITH c
3757
+ OPTIONAL MATCH (m:Message)-[:PART_OF]->(c)
3758
+ WHERE m.role = 'user'
3759
+ WITH c, count(m) AS userCount
3760
+ WHERE userCount <= 3
3761
+ SET c.name = $label, c.updatedAt = datetime()
3762
+ ${embedding ? ", c.embedding = $embedding" : ""}
3763
+ RETURN c.name AS name`,
3764
+ { conversationId, label: fullLabel, ...embedding ? { embedding } : {} }
3765
+ );
3766
+ if (result.records.length > 0) {
3767
+ console.error(`[persist] Auto-labeled session ${conversationId.slice(0, 8)}\u2026: "${fullLabel}"${embedding ? " (embedded)" : ""}`);
3768
+ labelAccumulator.delete(conversationId);
3769
+ }
3770
+ } catch (err) {
3771
+ console.error(`[persist] autoLabelSession failed: ${err instanceof Error ? err.message : String(err)}`);
3772
+ } finally {
3773
+ await session.close();
3659
3774
  }
3660
3775
  } catch (err) {
3661
- console.error(`[persist] autoLabelSession failed: ${err instanceof Error ? err.message : String(err)}`);
3776
+ console.error(`[persist] autoLabel: unexpected error \u2014 ${err instanceof Error ? err.message : String(err)}`);
3662
3777
  } finally {
3663
- await session.close();
3778
+ const currentEntry = labelAccumulator.get(conversationId);
3779
+ if (currentEntry) currentEntry.pending = false;
3664
3780
  }
3665
3781
  }
3666
3782
  var INITIAL_CONFIDENCE = 0.5;
@@ -4552,7 +4668,7 @@ function fetchMcpToolsList(pluginDir) {
4552
4668
  if (!existsSync5(serverPath)) return Promise.resolve([]);
4553
4669
  const startMs = Date.now();
4554
4670
  return new Promise((resolvePromise) => {
4555
- const proc = spawn(process.execPath, [serverPath], {
4671
+ const proc = spawn2(process.execPath, [serverPath], {
4556
4672
  env: {
4557
4673
  ...process.env,
4558
4674
  PLATFORM_ROOT: PLATFORM_ROOT3,
@@ -5190,7 +5306,7 @@ async function fetchMemoryContext(accountId, query, sessionKey, options) {
5190
5306
  }
5191
5307
  const startMs = Date.now();
5192
5308
  return new Promise((resolve16) => {
5193
- const proc = spawn(process.execPath, [serverPath], {
5309
+ const proc = spawn2(process.execPath, [serverPath], {
5194
5310
  env: {
5195
5311
  ...process.env,
5196
5312
  ACCOUNT_ID: accountId,
@@ -5285,7 +5401,7 @@ async function compactTrimmedMessages(accountId, trimmedMessages) {
5285
5401
  if (!existsSync5(serverPath)) return false;
5286
5402
  const briefing = trimmedMessages.map((m) => `[${m.role.toUpperCase()}] ${m.content}`).join("\n\n");
5287
5403
  return new Promise((resolvePromise) => {
5288
- const proc = spawn(process.execPath, [serverPath], {
5404
+ const proc = spawn2(process.execPath, [serverPath], {
5289
5405
  env: { ...process.env, ACCOUNT_ID: accountId }
5290
5406
  });
5291
5407
  let buffer = "";
@@ -5710,7 +5826,7 @@ async function* runCompactionTurn(accountDir, accountId, systemPrompt, resumeSes
5710
5826
  resumeSessionId,
5711
5827
  COMPACTION_PROMPT
5712
5828
  ];
5713
- const proc = spawn("claude", args, {
5829
+ const proc = spawn2("claude", args, {
5714
5830
  cwd: accountDir,
5715
5831
  stdio: ["ignore", "pipe", "pipe"],
5716
5832
  env: { ...process.env, PLATFORM_ROOT: PLATFORM_ROOT3, ACCOUNT_DIR: accountDir }
@@ -6318,7 +6434,7 @@ async function* invokeAdminAgent(message, systemPrompt, accountDir, accountId, a
6318
6434
  }
6319
6435
  const fullMessage = attachments.length > 0 ? message + buildAttachmentMetaText(attachments) : message;
6320
6436
  args.push(fullMessage);
6321
- const proc = spawn("claude", args, {
6437
+ const proc = spawn2("claude", args, {
6322
6438
  cwd: accountDir,
6323
6439
  stdio: ["ignore", "pipe", "pipe"],
6324
6440
  env: { ...process.env, PLATFORM_ROOT: PLATFORM_ROOT3, ACCOUNT_DIR: accountDir }
@@ -6567,7 +6683,7 @@ async function* invokeManagedAdminAgent(message, systemPrompt, accountDir, accou
6567
6683
  specialistsDir,
6568
6684
  fullMessage
6569
6685
  ];
6570
- const proc = spawn("claude", args, {
6686
+ const proc = spawn2("claude", args, {
6571
6687
  cwd: accountDir,
6572
6688
  stdio: ["ignore", "pipe", "pipe"],
6573
6689
  env: { ...process.env, PLATFORM_ROOT: PLATFORM_ROOT3, ACCOUNT_DIR: accountDir }
@@ -9074,7 +9190,7 @@ async function POST8(req) {
9074
9190
  }
9075
9191
 
9076
9192
  // app/api/onboarding/claude-auth/route.ts
9077
- import { spawn as spawn2, execFileSync as execFileSync2 } from "child_process";
9193
+ import { spawn as spawn3, execFileSync as execFileSync2 } from "child_process";
9078
9194
  import { openSync, closeSync, writeFileSync as writeFileSync6, writeSync } from "fs";
9079
9195
  function checkAuthStatus() {
9080
9196
  try {
@@ -9141,7 +9257,7 @@ async function POST9(req) {
9141
9257
  const chromiumWrapper = writeChromiumWrapper();
9142
9258
  const x11Env = buildX11Env(chromiumWrapper);
9143
9259
  const claudeAuthLogFd = openSync(logPath("claude-auth"), "a");
9144
- const claudeProc = spawn2("claude", ["auth", "login"], {
9260
+ const claudeProc = spawn3("claude", ["auth", "login"], {
9145
9261
  env: x11Env,
9146
9262
  stdio: ["ignore", "pipe", "pipe"]
9147
9263
  });
@@ -9259,6 +9375,86 @@ async function POST11(req) {
9259
9375
  }
9260
9376
 
9261
9377
  // app/api/admin/chat/route.ts
9378
+ import { existsSync as existsSync12, readFileSync as readFileSync12, writeFileSync as writeFileSync8, mkdirSync as mkdirSync6 } from "fs";
9379
+ import { dirname as dirname2 } from "path";
9380
+ function isComponentDone(parsed) {
9381
+ return typeof parsed === "object" && parsed !== null && parsed._componentDone === true && typeof parsed.component === "string" && typeof parsed.payload === "string";
9382
+ }
9383
+ function transformComponentDone(envelope) {
9384
+ const { component, payload } = envelope;
9385
+ if (component === "document-editor") {
9386
+ try {
9387
+ const inner = JSON.parse(payload);
9388
+ if (typeof inner === "object" && inner !== null) {
9389
+ if (inner.action === "approve" && typeof inner.content === "string") {
9390
+ const path = inner.filePath ?? "unknown";
9391
+ return `[User approved document: ${path}]
9392
+
9393
+ ${inner.content}`;
9394
+ }
9395
+ if (inner.type === "comment" && typeof inner.text === "string") {
9396
+ return `[User commented on document: "${inner.text}"]`;
9397
+ }
9398
+ }
9399
+ } catch {
9400
+ }
9401
+ }
9402
+ return payload;
9403
+ }
9404
+ var VOLATILE_STATE_FILE = "/tmp/maxy-agent-create-state.json";
9405
+ function advanceGateIfNeeded(envelope, accountDir, logFn) {
9406
+ if (!existsSync12(VOLATILE_STATE_FILE)) return;
9407
+ const { component, payload } = envelope;
9408
+ let gateFlag = null;
9409
+ let agentSlug = "";
9410
+ if (component === "document-editor") {
9411
+ try {
9412
+ const inner = typeof payload === "string" ? JSON.parse(payload) : payload;
9413
+ if (typeof inner === "object" && inner !== null && typeof inner.filePath === "string") {
9414
+ const match2 = inner.filePath.match(/agents\/([^/]+)\/(SOUL\.md|KNOWLEDGE\.md)$/);
9415
+ if (match2 && match2[1] !== "admin") {
9416
+ agentSlug = match2[1];
9417
+ gateFlag = match2[2] === "SOUL.md" ? "soul" : "knowledge";
9418
+ }
9419
+ }
9420
+ } catch {
9421
+ }
9422
+ } else if (component === "form") {
9423
+ gateFlag = "config";
9424
+ }
9425
+ if (!gateFlag) return;
9426
+ let state;
9427
+ try {
9428
+ state = JSON.parse(readFileSync12(VOLATILE_STATE_FILE, "utf-8"));
9429
+ } catch (err) {
9430
+ logFn(`ERROR \u2014 failed to read state file for advancement: ${err instanceof Error ? err.message : String(err)}`);
9431
+ return;
9432
+ }
9433
+ const gates = state.gates;
9434
+ if (!gates || typeof gates !== "object") {
9435
+ logFn("WARNING \u2014 state file has unexpected structure, skipping advancement");
9436
+ return;
9437
+ }
9438
+ if (!state.slug && agentSlug) {
9439
+ state.slug = agentSlug;
9440
+ }
9441
+ if (gates[gateFlag] === true) return;
9442
+ gates[gateFlag] = true;
9443
+ try {
9444
+ writeFileSync8(VOLATILE_STATE_FILE, JSON.stringify(state));
9445
+ } catch (err) {
9446
+ logFn(`ERROR \u2014 failed to write volatile state after advancement: ${err instanceof Error ? err.message : String(err)}`);
9447
+ }
9448
+ const durablePath = `${accountDir}/.claude/agent-create-state.json`;
9449
+ try {
9450
+ mkdirSync6(dirname2(durablePath), { recursive: true });
9451
+ writeFileSync8(durablePath, JSON.stringify(state));
9452
+ } catch (err) {
9453
+ logFn(`WARNING \u2014 failed to write durable state after advancement: ${err instanceof Error ? err.message : String(err)}`);
9454
+ }
9455
+ const pending = Object.entries(gates).filter(([, v]) => !v).map(([k]) => k);
9456
+ logFn(`${gateFlag} gate advanced (route-level). Pending: ${pending.length > 0 ? pending.join(", ") : "none"}`);
9457
+ }
9262
9458
  async function POST12(req) {
9263
9459
  const contentType = req.headers.get("content-type") ?? "";
9264
9460
  let message;
@@ -9394,6 +9590,21 @@ async function POST12(req) {
9394
9590
  }
9395
9591
  } catch {
9396
9592
  }
9593
+ try {
9594
+ const parsed = JSON.parse(message);
9595
+ if (isComponentDone(parsed)) {
9596
+ const componentLog = agentLogStream("component-lifecycle", account.accountDir);
9597
+ const ts = (/* @__PURE__ */ new Date()).toISOString().slice(11, 23);
9598
+ const logFn = (msg) => componentLog.write(`[${ts}] [agent-creation-approval:route] ${msg}
9599
+ `);
9600
+ advanceGateIfNeeded(parsed, account.accountDir, logFn);
9601
+ message = transformComponentDone(parsed);
9602
+ componentLog.write(`[${ts}] [component:${parsed.component}] componentDone \u2192 transformed
9603
+ `);
9604
+ componentLog.end();
9605
+ }
9606
+ } catch {
9607
+ }
9397
9608
  const encoder = new TextEncoder();
9398
9609
  const sseLog = agentLogStream("sse-events", account.accountDir);
9399
9610
  const sk = session_key.slice(0, 8);
@@ -9503,7 +9714,7 @@ async function POST13(req) {
9503
9714
  }
9504
9715
 
9505
9716
  // app/api/admin/logs/route.ts
9506
- import { existsSync as existsSync12, readdirSync as readdirSync2, readFileSync as readFileSync12, statSync as statSync3 } from "fs";
9717
+ import { existsSync as existsSync13, readdirSync as readdirSync2, readFileSync as readFileSync13, statSync as statSync3 } from "fs";
9507
9718
  import { resolve as resolve9, basename } from "path";
9508
9719
  var TAIL_BYTES = 8192;
9509
9720
  async function GET3(request) {
@@ -9519,7 +9730,7 @@ async function GET3(request) {
9519
9730
  if (!dir) continue;
9520
9731
  const filePath = resolve9(dir, safe);
9521
9732
  try {
9522
- const content = readFileSync12(filePath, "utf-8");
9733
+ const content = readFileSync13(filePath, "utf-8");
9523
9734
  const headers = { "Content-Type": "text/plain; charset=utf-8" };
9524
9735
  if (download) headers["Content-Disposition"] = `attachment; filename="${safe}"`;
9525
9736
  return new Response(content, { headers });
@@ -9545,7 +9756,7 @@ async function GET3(request) {
9545
9756
  if (!dir) continue;
9546
9757
  const filePath = resolve9(dir, fileName);
9547
9758
  try {
9548
- const content = readFileSync12(filePath, "utf-8");
9759
+ const content = readFileSync13(filePath, "utf-8");
9549
9760
  const headers = { "Content-Type": "text/plain; charset=utf-8" };
9550
9761
  if (download) headers["Content-Disposition"] = `attachment; filename="${fileName}"`;
9551
9762
  return new Response(content, { headers });
@@ -9557,7 +9768,7 @@ async function GET3(request) {
9557
9768
  const seen = /* @__PURE__ */ new Set();
9558
9769
  const logs = {};
9559
9770
  for (const dir of [accountLogDir, LOG_DIR]) {
9560
- if (!dir || !existsSync12(dir)) continue;
9771
+ if (!dir || !existsSync13(dir)) continue;
9561
9772
  let files;
9562
9773
  try {
9563
9774
  files = readdirSync2(dir).filter((f) => f.endsWith(".log"));
@@ -9567,7 +9778,7 @@ async function GET3(request) {
9567
9778
  files.filter((f) => !seen.has(f)).map((f) => ({ name: f, mtime: statSync3(resolve9(dir, f)).mtimeMs })).sort((a, b) => b.mtime - a.mtime).forEach(({ name }) => {
9568
9779
  seen.add(name);
9569
9780
  try {
9570
- const content = readFileSync12(resolve9(dir, name));
9781
+ const content = readFileSync13(resolve9(dir, name));
9571
9782
  const tail = content.length > TAIL_BYTES ? content.subarray(content.length - TAIL_BYTES).toString("utf-8") : content.toString("utf-8");
9572
9783
  logs[name] = tail.trim() || "(empty)";
9573
9784
  } catch {
@@ -9602,7 +9813,7 @@ async function GET4() {
9602
9813
 
9603
9814
  // app/api/admin/attachment/[attachmentId]/route.ts
9604
9815
  import { readFile, readdir } from "fs/promises";
9605
- import { existsSync as existsSync13 } from "fs";
9816
+ import { existsSync as existsSync14 } from "fs";
9606
9817
  import { resolve as resolve10 } from "path";
9607
9818
  async function GET5(req, attachmentId) {
9608
9819
  const sessionKey = new URL(req.url).searchParams.get("session_key") ?? "";
@@ -9617,11 +9828,11 @@ async function GET5(req, attachmentId) {
9617
9828
  return new Response("Not found", { status: 404 });
9618
9829
  }
9619
9830
  const dir = resolve10(ATTACHMENTS_ROOT, accountId, attachmentId);
9620
- if (!existsSync13(dir)) {
9831
+ if (!existsSync14(dir)) {
9621
9832
  return new Response("Not found", { status: 404 });
9622
9833
  }
9623
9834
  const metaPath = resolve10(dir, `${attachmentId}.meta.json`);
9624
- if (!existsSync13(metaPath)) {
9835
+ if (!existsSync14(metaPath)) {
9625
9836
  return new Response("Not found", { status: 404 });
9626
9837
  }
9627
9838
  let meta;
@@ -9647,7 +9858,7 @@ async function GET5(req, attachmentId) {
9647
9858
  }
9648
9859
 
9649
9860
  // app/api/admin/account/route.ts
9650
- import { readFileSync as readFileSync13, writeFileSync as writeFileSync8 } from "fs";
9861
+ import { readFileSync as readFileSync14, writeFileSync as writeFileSync9 } from "fs";
9651
9862
  import { resolve as resolve11 } from "path";
9652
9863
  var VALID_CONTEXT_MODES = ["managed", "claude-code"];
9653
9864
  async function PATCH(req) {
@@ -9673,10 +9884,10 @@ async function PATCH(req) {
9673
9884
  }
9674
9885
  const configPath = resolve11(account.accountDir, "account.json");
9675
9886
  try {
9676
- const raw2 = readFileSync13(configPath, "utf-8");
9887
+ const raw2 = readFileSync14(configPath, "utf-8");
9677
9888
  const config = JSON.parse(raw2);
9678
9889
  config.contextMode = contextMode;
9679
- writeFileSync8(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
9890
+ writeFileSync9(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
9680
9891
  console.error(`[account-update] contextMode=${contextMode}`);
9681
9892
  return Response.json({ ok: true, contextMode });
9682
9893
  } catch (err) {
@@ -9687,14 +9898,14 @@ async function PATCH(req) {
9687
9898
 
9688
9899
  // app/api/admin/agents/route.ts
9689
9900
  import { resolve as resolve12 } from "path";
9690
- import { readdirSync as readdirSync3, readFileSync as readFileSync14, existsSync as existsSync14 } from "fs";
9901
+ import { readdirSync as readdirSync3, readFileSync as readFileSync15, existsSync as existsSync15 } from "fs";
9691
9902
  async function GET6() {
9692
9903
  const account = resolveAccount();
9693
9904
  if (!account) {
9694
9905
  return Response.json({ agents: [] });
9695
9906
  }
9696
9907
  const agentsDir = resolve12(account.accountDir, "agents");
9697
- if (!existsSync14(agentsDir)) {
9908
+ if (!existsSync15(agentsDir)) {
9698
9909
  return Response.json({ agents: [] });
9699
9910
  }
9700
9911
  const agents = [];
@@ -9704,9 +9915,9 @@ async function GET6() {
9704
9915
  if (!entry.isDirectory()) continue;
9705
9916
  if (entry.name === "admin") continue;
9706
9917
  const configPath = resolve12(agentsDir, entry.name, "config.json");
9707
- if (!existsSync14(configPath)) continue;
9918
+ if (!existsSync15(configPath)) continue;
9708
9919
  try {
9709
- const config = JSON.parse(readFileSync14(configPath, "utf-8"));
9920
+ const config = JSON.parse(readFileSync15(configPath, "utf-8"));
9710
9921
  agents.push({
9711
9922
  slug: entry.name,
9712
9923
  displayName: config.displayName ?? entry.name,
@@ -9724,15 +9935,15 @@ async function GET6() {
9724
9935
  }
9725
9936
 
9726
9937
  // app/api/admin/version/route.ts
9727
- import { readFileSync as readFileSync15, existsSync as existsSync15 } from "fs";
9938
+ import { readFileSync as readFileSync16, existsSync as existsSync16 } from "fs";
9728
9939
  import { resolve as resolve13, join as join3 } from "path";
9729
9940
  var PLATFORM_ROOT6 = process.env.MAXY_PLATFORM_ROOT ?? resolve13(process.cwd(), "..");
9730
9941
  var brandHostname = "maxy";
9731
9942
  var brandNpmPackage = "@rubytech/create-maxy";
9732
9943
  var brandJsonPath = join3(PLATFORM_ROOT6, "config", "brand.json");
9733
- if (existsSync15(brandJsonPath)) {
9944
+ if (existsSync16(brandJsonPath)) {
9734
9945
  try {
9735
- const brand = JSON.parse(readFileSync15(brandJsonPath, "utf-8"));
9946
+ const brand = JSON.parse(readFileSync16(brandJsonPath, "utf-8"));
9736
9947
  if (brand.hostname) brandHostname = brand.hostname;
9737
9948
  if (brand.npm?.packageName) brandNpmPackage = brand.npm.packageName;
9738
9949
  } catch {
@@ -9745,8 +9956,8 @@ var CACHE_TTL_MS = 60 * 60 * 1e3;
9745
9956
  var FETCH_TIMEOUT_MS = 5e3;
9746
9957
  var latestCache = null;
9747
9958
  function readInstalled() {
9748
- if (!existsSync15(VERSION_FILE)) return "unknown";
9749
- const content = readFileSync15(VERSION_FILE, "utf-8").trim();
9959
+ if (!existsSync16(VERSION_FILE)) return "unknown";
9960
+ const content = readFileSync16(VERSION_FILE, "utf-8").trim();
9750
9961
  return content || "unknown";
9751
9962
  }
9752
9963
  async function fetchLatest() {
@@ -9799,16 +10010,16 @@ async function GET7() {
9799
10010
  }
9800
10011
 
9801
10012
  // app/api/admin/version/upgrade/route.ts
9802
- import { spawn as spawn3 } from "child_process";
9803
- import { existsSync as existsSync16, statSync as statSync4, writeFileSync as writeFileSync9, readFileSync as readFileSync16, openSync as openSync2, closeSync as closeSync2 } from "fs";
10013
+ import { spawn as spawn4 } from "child_process";
10014
+ import { existsSync as existsSync17, statSync as statSync4, writeFileSync as writeFileSync10, readFileSync as readFileSync17, openSync as openSync2, closeSync as closeSync2 } from "fs";
9804
10015
  import { resolve as resolve14, join as join4 } from "path";
9805
10016
  var PLATFORM_ROOT7 = process.env.MAXY_PLATFORM_ROOT ?? resolve14(process.cwd(), "..");
9806
10017
  var upgradePkg = "@rubytech/create-maxy";
9807
10018
  var upgradeHostname = "maxy";
9808
10019
  var brandPath = join4(PLATFORM_ROOT7, "config", "brand.json");
9809
- if (existsSync16(brandPath)) {
10020
+ if (existsSync17(brandPath)) {
9810
10021
  try {
9811
- const brand = JSON.parse(readFileSync16(brandPath, "utf-8"));
10022
+ const brand = JSON.parse(readFileSync17(brandPath, "utf-8"));
9812
10023
  if (brand.npm?.packageName) upgradePkg = brand.npm.packageName;
9813
10024
  if (brand.hostname) upgradeHostname = brand.hostname;
9814
10025
  } catch {
@@ -9818,7 +10029,7 @@ var LOCK_FILE = `/tmp/${upgradeHostname}-upgrade.lock`;
9818
10029
  var LOG_FILE = `/tmp/${upgradeHostname}-upgrade.log`;
9819
10030
  var LOCK_MAX_AGE_MS = 3 * 60 * 1e3;
9820
10031
  function isLockFresh() {
9821
- if (!existsSync16(LOCK_FILE)) return false;
10032
+ if (!existsSync17(LOCK_FILE)) return false;
9822
10033
  try {
9823
10034
  const stat = statSync4(LOCK_FILE);
9824
10035
  return Date.now() - stat.mtimeMs < LOCK_MAX_AGE_MS;
@@ -9844,14 +10055,14 @@ async function POST14(req) {
9844
10055
  return Response.json({ ok: false, error: "upgrade already in progress" }, { status: 409 });
9845
10056
  }
9846
10057
  try {
9847
- writeFileSync9(LOCK_FILE, String(Date.now()));
10058
+ writeFileSync10(LOCK_FILE, String(Date.now()));
9848
10059
  } catch (err) {
9849
10060
  console.error("[admin/version/upgrade] failed to write lock file:", err);
9850
10061
  }
9851
10062
  invalidateVersionCache();
9852
10063
  try {
9853
10064
  const logFd = openSync2(LOG_FILE, "w");
9854
- const child = spawn3("npx", ["-y", `${upgradePkg}@latest`], {
10065
+ const child = spawn4("npx", ["-y", `${upgradePkg}@latest`], {
9855
10066
  detached: true,
9856
10067
  stdio: ["ignore", logFd, logFd],
9857
10068
  env: { ...process.env, npm_config_yes: "true" },
@@ -9974,12 +10185,12 @@ async function POST15() {
9974
10185
  var PLATFORM_ROOT8 = process.env.MAXY_PLATFORM_ROOT || "";
9975
10186
  var BRAND_JSON_PATH = PLATFORM_ROOT8 ? join5(PLATFORM_ROOT8, "config", "brand.json") : "";
9976
10187
  var BRAND = { productName: "Maxy", hostname: "maxy", configDir: ".maxy", domain: "getmaxy.com" };
9977
- if (BRAND_JSON_PATH && !existsSync17(BRAND_JSON_PATH)) {
10188
+ if (BRAND_JSON_PATH && !existsSync18(BRAND_JSON_PATH)) {
9978
10189
  console.error(`[brand] WARNING: brand.json not found at ${BRAND_JSON_PATH} \u2014 using Maxy defaults`);
9979
10190
  }
9980
- if (BRAND_JSON_PATH && existsSync17(BRAND_JSON_PATH)) {
10191
+ if (BRAND_JSON_PATH && existsSync18(BRAND_JSON_PATH)) {
9981
10192
  try {
9982
- const parsed = JSON.parse(readFileSync17(BRAND_JSON_PATH, "utf-8"));
10193
+ const parsed = JSON.parse(readFileSync18(BRAND_JSON_PATH, "utf-8"));
9983
10194
  BRAND = { ...BRAND, ...parsed };
9984
10195
  } catch (err) {
9985
10196
  console.error(`[brand] Failed to parse brand.json: ${err.message}`);
@@ -9994,8 +10205,8 @@ var brandLoginOpts = {
9994
10205
  var ALIAS_DOMAINS_PATH = join5(homedir2(), BRAND.configDir, "alias-domains.json");
9995
10206
  function loadAliasDomains() {
9996
10207
  try {
9997
- if (!existsSync17(ALIAS_DOMAINS_PATH)) return null;
9998
- const parsed = JSON.parse(readFileSync17(ALIAS_DOMAINS_PATH, "utf-8"));
10208
+ if (!existsSync18(ALIAS_DOMAINS_PATH)) return null;
10209
+ const parsed = JSON.parse(readFileSync18(ALIAS_DOMAINS_PATH, "utf-8"));
9999
10210
  if (!Array.isArray(parsed)) {
10000
10211
  console.error("[alias-domains] malformed alias-domains.json \u2014 expected array");
10001
10212
  return null;
@@ -10325,9 +10536,9 @@ app.get(
10325
10536
  );
10326
10537
  var htmlCache = /* @__PURE__ */ new Map();
10327
10538
  var brandLogoPath = "/brand/maxy-monochrome.png";
10328
- if (BRAND_JSON_PATH && existsSync17(BRAND_JSON_PATH)) {
10539
+ if (BRAND_JSON_PATH && existsSync18(BRAND_JSON_PATH)) {
10329
10540
  try {
10330
- const fullBrand = JSON.parse(readFileSync17(BRAND_JSON_PATH, "utf-8"));
10541
+ const fullBrand = JSON.parse(readFileSync18(BRAND_JSON_PATH, "utf-8"));
10331
10542
  if (fullBrand.assets?.logo) brandLogoPath = `/brand/${fullBrand.assets.logo}`;
10332
10543
  } catch {
10333
10544
  }
@@ -10341,7 +10552,7 @@ var brandScript = `<script>window.__BRAND__=${JSON.stringify({
10341
10552
  function cachedHtml(file) {
10342
10553
  let html = htmlCache.get(file);
10343
10554
  if (!html) {
10344
- html = readFileSync17(resolve15(process.cwd(), "public", file), "utf-8");
10555
+ html = readFileSync18(resolve15(process.cwd(), "public", file), "utf-8");
10345
10556
  html = html.replace("</head>", `${brandScript}
10346
10557
  </head>`);
10347
10558
  htmlCache.set(file, html);
@@ -10353,13 +10564,13 @@ function loadBrandingCache(agentSlug) {
10353
10564
  const configDir = join5(homedir2(), BRAND.configDir);
10354
10565
  try {
10355
10566
  const accountJsonPath = join5(configDir, "account.json");
10356
- if (!existsSync17(accountJsonPath)) return null;
10357
- const account = JSON.parse(readFileSync17(accountJsonPath, "utf-8"));
10567
+ if (!existsSync18(accountJsonPath)) return null;
10568
+ const account = JSON.parse(readFileSync18(accountJsonPath, "utf-8"));
10358
10569
  const accountId = account.accountId;
10359
10570
  if (!accountId) return null;
10360
10571
  const cachePath = join5(configDir, "branding-cache", accountId, `${agentSlug}.json`);
10361
- if (!existsSync17(cachePath)) return null;
10362
- return JSON.parse(readFileSync17(cachePath, "utf-8"));
10572
+ if (!existsSync18(cachePath)) return null;
10573
+ return JSON.parse(readFileSync18(cachePath, "utf-8"));
10363
10574
  } catch {
10364
10575
  return null;
10365
10576
  }
@@ -10368,8 +10579,8 @@ function resolveDefaultSlug() {
10368
10579
  try {
10369
10580
  const configDir = join5(homedir2(), BRAND.configDir);
10370
10581
  const accountJsonPath = join5(configDir, "account.json");
10371
- if (!existsSync17(accountJsonPath)) return null;
10372
- const account = JSON.parse(readFileSync17(accountJsonPath, "utf-8"));
10582
+ if (!existsSync18(accountJsonPath)) return null;
10583
+ const account = JSON.parse(readFileSync18(accountJsonPath, "utf-8"));
10373
10584
  return account.defaultAgent || null;
10374
10585
  } catch {
10375
10586
  return null;