@rubytech/create-maxy-code 0.1.129 → 0.1.132

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 (34) hide show
  1. package/package.json +1 -1
  2. package/payload/platform/lib/graph-write/dist/conversation-provenance.d.ts +5 -1
  3. package/payload/platform/lib/graph-write/dist/conversation-provenance.d.ts.map +1 -1
  4. package/payload/platform/lib/graph-write/dist/conversation-provenance.js +18 -11
  5. package/payload/platform/lib/graph-write/dist/conversation-provenance.js.map +1 -1
  6. package/payload/platform/lib/graph-write/src/conversation-provenance.ts +24 -16
  7. package/payload/platform/plugins/contacts/PLUGIN.md +1 -1
  8. package/payload/platform/plugins/docs/references/internals.md +1 -1
  9. package/payload/platform/plugins/docs/references/memory-guide.md +4 -0
  10. package/payload/platform/plugins/graph-viewer/mcp/dist/index.js +2 -6
  11. package/payload/platform/plugins/graph-viewer/mcp/dist/index.js.map +1 -1
  12. package/payload/platform/plugins/graph-viewer/mcp/dist/render/layout.d.ts.map +1 -1
  13. package/payload/platform/plugins/graph-viewer/mcp/dist/render/layout.js +46 -6
  14. package/payload/platform/plugins/graph-viewer/mcp/dist/render/layout.js.map +1 -1
  15. package/payload/platform/plugins/graph-viewer/mcp/dist/tools/graph-render.d.ts +1 -1
  16. package/payload/platform/plugins/graph-viewer/mcp/dist/tools/graph-render.d.ts.map +1 -1
  17. package/payload/platform/plugins/graph-viewer/mcp/dist/tools/graph-render.js +28 -2
  18. package/payload/platform/plugins/graph-viewer/mcp/dist/tools/graph-render.js.map +1 -1
  19. package/payload/platform/plugins/graph-viewer/skills/render-graph/SKILL.md +6 -3
  20. package/payload/platform/plugins/memory/PLUGIN.md +1 -1
  21. package/payload/platform/plugins/memory/references/schema-estate-agent.md +3 -3
  22. package/payload/platform/services/claude-session-manager/dist/http-server.d.ts.map +1 -1
  23. package/payload/platform/services/claude-session-manager/dist/http-server.js +16 -0
  24. package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -1
  25. package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts +11 -0
  26. package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts.map +1 -1
  27. package/payload/platform/services/claude-session-manager/dist/pty-spawner.js +11 -0
  28. package/payload/platform/services/claude-session-manager/dist/pty-spawner.js.map +1 -1
  29. package/payload/platform/templates/agents/admin/IDENTITY.md +12 -0
  30. package/payload/platform/templates/agents/public/IDENTITY.md +8 -2
  31. package/payload/platform/templates/specialists/agents/content-producer.md +4 -0
  32. package/payload/platform/templates/specialists/agents/database-operator.md +2 -0
  33. package/payload/platform/templates/specialists/agents/personal-assistant.md +2 -0
  34. package/payload/server/server.js +70 -11
@@ -14,9 +14,15 @@ Three rules govern every turn. They are load-bearing — when they conflict with
14
14
 
15
15
  *Failure symptoms:* unrequested summary, three-paragraph answer to a one-line question, pasting a raw tool result verbatim into chat.
16
16
 
17
- **EVIDENCE-BASED.** The graph is the single, canonical source of truth about this account. Consult it — via `memory-search`, `memory-read`, or `profile-read` — before answering factual questions or embarking on activity. When the graph is wrong, correct it via `memory-write` or `memory-update`, then answer. Never substitute training-data recall for a graph read when the graph holds the canonical version. When the graph has no answer and you must rely on training knowledge, say so explicitly. *Failure symptoms:* factual claim without a prior graph read this turn, training-data fallback when the graph has the canonical version.
17
+ **EVIDENCE-BASED.** The graph is the single, canonical source of truth about this account. Consult it — via `memory-search` — before answering factual questions or embarking on activity. Never substitute training-data recall for a graph read when the graph holds the canonical version. When the graph has no answer and you must rely on training knowledge, say so explicitly. You are read-only by design (see Boundaries); corrections to the graph are not your job — surface the gap so the operator can address it. *Failure symptoms:* factual claim without a prior graph read this turn, training-data fallback when the graph has the canonical version.
18
18
 
19
- A landfill graph defeats EVIDENCE-BASED: search returns noise, the agent re-writes the noise, the noise compounds. Compress on write; filter on read.
19
+ The 4-step brain-first loop, every turn that needs information:
20
+ 1. **Classify the question** — known entity, temporal fact, event, general knowledge, or none. The inbound gateway provides this as `retrievalClass` when available.
21
+ 2. **Read the graph** — `memory-search` is the first tool call of any informational turn. The server scopes every read to `scope: "public"`, so what comes back is exactly what the operator chose to expose.
22
+ 3. **Hydrate on partial hit** — when a result names but does not contain what you need, walk one hop (re-search by `elementId` or scan related neighbours) before assuming a miss.
23
+ 4. **Fall back honestly on a confirmed miss** — say the graph has no answer, then offer to take a message or suggest direct contact. Never invent the missing fact.
24
+
25
+ A landfill graph defeats EVIDENCE-BASED: search returns noise, the agent relays the noise, the visitor sees noise. Filter on read.
20
26
 
21
27
  ---
22
28
 
@@ -22,6 +22,10 @@ These three rules win when anything else in this prompt conflicts with them.
22
22
 
23
23
  Producing reads from the graph; it never writes external input to it. All ingestion (PDFs, text, transcripts, web pages, audio, video, archives) routes to `specialists:database-operator`. If a brief asks you to ingest, return immediately and tell admin to redispatch.
24
24
 
25
+ ## Brain-first read before produce
26
+
27
+ Every artifact you generate is grounded in graph content the operator already curated. The first action of any production turn is `memory-search` against the brief — for a brochure, that means searching for the `:LocalBusiness` and its `:DEPICTS`-linked `:ImageObject`s; for a chart, the `:Project` and its `:Task` and `:Event` neighbours; for an image prompt, the operator's `:UserProfile` or the named subject. Compose the artifact from properties the search returned. Image generation (`image-generate`) is the only outward call in your surface and is conditional on having confirmed the subject from the graph first — never call `image-generate` with a prompt assembled from the brief's free text alone when a graph subject could ground it.
28
+
25
29
  ## Image generation
26
30
 
27
31
  Three models via `image-generate`. Pick by output need: `recraft-v4` for design-quality and branded compositions (supports SVG); `nano-banana-pro` for photorealistic or text-heavy images; `flux-schnell` for fast drafts.
@@ -16,4 +16,6 @@ Node `name` and `title` properties are short identifiers, never denormalised com
16
16
 
17
17
  The `accountId` property is supplied by the writers from server-side environment state (the `ACCOUNT_ID` env var) — never refuse a write because accountId is absent from admin's brief.
18
18
 
19
+ **Brain-first read before write.** Resolve parents and existing nodes via `memory-search` before composing the write. The hierarchy rule (`LocalBusiness → Project → Task | Person | Organisation | KnowledgeDocument`) requires a parent `elementId`, and edges to existing entities require their `elementId` too — both come from a `memory-search` against the brief, not from a guess. When the brief names an entity that could plausibly already exist (a Person, a Project, an Organisation), the first action of the dispatch is a `memory-search` against that name; only on a confirmed miss do you create a new node. External-knowledge tools are absent from your surface by design — your job ends at the graph.
20
+
19
21
  Decide the precise operations against the schema, execute them with the writers in your tool surface, and report each write back to admin as `{label, elementId}` so admin can confirm the outcome to the operator. If admin's brief is ambiguous about which label, which edge type, or which existing node to attach to, name the ambiguity in your reply rather than guessing.
@@ -28,6 +28,8 @@ Each domain has a small set of tools and, where it exists, a skill that drives t
28
28
 
29
29
  ## Cross-domain rules
30
30
 
31
+ **Brain-first read before action.** The graph is the canonical store for everyone the operator works with, every scheduled commitment, and every channel configuration. Before composing a message, scheduling an event, or looking up a contact, run `memory-search` (and `profile-read` when the question is about the operator) against the brief. Resolve recipient `elementId`s from the graph, not from the brief's free text — wrapped writers (`schedule-event`, `contact-create`, `work-create`) reject zero-edge calls, and the resolved id is what satisfies them. External lookups (`outlook-mail-search`, `email-search`, Telegram message history, browser navigation) are step 2: run them when the graph confirms there is no local record of the thing you need. Each external result worth keeping goes back to admin so the Recording route on admin's side can persist it.
32
+
31
33
  **Credentials never leave a tool.** If you store an API key, password, or token via a tool, report only that storage succeeded or failed. Never repeat the secret in your output, even partially.
32
34
 
33
35
  **Timezones are not optional.** Scheduling output is rendered in the user's locale timezone from `UserProfile.timezone` (IANA). Storage is UTC. If timezone is unset, the scheduling tool errors; set it via `profile-update` with `profileFields: { timezone: "Europe/London" }`. There is no UTC fallback.
@@ -4441,6 +4441,10 @@ function defaultResult(rawText, latencyMs) {
4441
4441
  language: "en",
4442
4442
  requiresHistory: false,
4443
4443
  complexity: "complex",
4444
+ // Fallthrough defaults to "general" so the brain-first loop still runs a
4445
+ // graph read — safer than "none", which would skip retrieval entirely on
4446
+ // every Haiku failure.
4447
+ retrievalClass: "general",
4444
4448
  latencyMs,
4445
4449
  fallthrough: true
4446
4450
  };
@@ -4476,7 +4480,14 @@ LANGUAGE \u2014 detect the language and return its ISO 639-1 code (e.g. "en", "e
4476
4480
 
4477
4481
  REQUIRES HISTORY \u2014 set to true if the message references prior conversation context ("we discussed", "last time", "that thing", "the proposal", "earlier")
4478
4482
 
4479
- COMPLEXITY \u2014 "simple" for greetings, yes/no answers, brief confirmations; "complex" for multi-part requests, detailed questions, nuanced instructions`;
4483
+ COMPLEXITY \u2014 "simple" for greetings, yes/no answers, brief confirmations; "complex" for multi-part requests, detailed questions, nuanced instructions
4484
+
4485
+ RETRIEVAL CLASS \u2014 classify what kind of graph read this message warrants (brain-first lookup loop). One of:
4486
+ - "entity": names or asks about a specific person, organisation, place, listing, project, document \u2014 anything the graph might already know about ("who is Adam Langley", "tell me about Acme Ltd", "what do we have on 5 Glan Honddu Close")
4487
+ - "temporal": references a time-bound fact \u2014 past events, schedule, what happened when ("what did we agree last Tuesday", "what's on today", "when did the contract land")
4488
+ - "event": references something a tool returned earlier in this or a prior session ("did the upload finish", "what did the search find", "the result from earlier")
4489
+ - "general": a question or task that needs information from the graph but doesn't fall into the three categories above (open-ended ask, broad topic, free-form question)
4490
+ - "none": greeting, meta-instruction, complaint, acknowledgement, or any message that does not warrant a graph lookup ("hi", "thanks", "stop doing that", "this is broken")`;
4480
4491
  }
4481
4492
  var GATEWAY_TOOL = {
4482
4493
  name: "classify_message",
@@ -4522,6 +4533,11 @@ var GATEWAY_TOOL = {
4522
4533
  type: "string",
4523
4534
  enum: ["simple", "complex"],
4524
4535
  description: "Message complexity level"
4536
+ },
4537
+ retrievalClass: {
4538
+ type: "string",
4539
+ enum: ["entity", "temporal", "event", "general", "none"],
4540
+ description: "Brain-first retrieval class. entity=named entity lookup, temporal=time-bound fact, event=prior tool result reference, general=other informational, none=greeting/meta/complaint with no graph lookup warranted"
4525
4541
  }
4526
4542
  },
4527
4543
  required: [
@@ -4532,7 +4548,8 @@ var GATEWAY_TOOL = {
4532
4548
  "intent",
4533
4549
  "language",
4534
4550
  "requiresHistory",
4535
- "complexity"
4551
+ "complexity",
4552
+ "retrievalClass"
4536
4553
  ]
4537
4554
  }
4538
4555
  };
@@ -4603,6 +4620,7 @@ async function processInbound(rawText, channel) {
4603
4620
  const requiresHistory = input.requiresHistory === true;
4604
4621
  const complexity = input.complexity === "simple" ? "simple" : "complex";
4605
4622
  const searchQuery = typeof input.searchQuery === "string" ? input.searchQuery : null;
4623
+ const retrievalClass = ["entity", "temporal", "event", "general", "none"].includes(input.retrievalClass) ? input.retrievalClass : "general";
4606
4624
  const isAdmin = channel === "web-admin" || channel === "telegram-admin";
4607
4625
  const finalText = isAdmin ? rawText.trim() : processedText;
4608
4626
  const result = {
@@ -4613,11 +4631,12 @@ async function processInbound(rawText, channel) {
4613
4631
  language,
4614
4632
  requiresHistory,
4615
4633
  complexity,
4634
+ retrievalClass,
4616
4635
  latencyMs,
4617
4636
  fallthrough: false
4618
4637
  };
4619
4638
  console.log(
4620
- `${TAG14} channel=${channel} verdict=${verdict} promptInjection=${promptInjectionRisk} intent=${intent} language=${language} complexity=${complexity} requiresHistory=${requiresHistory} rewrite=${!isAdmin && processedText !== rawText.trim()} searchQuery=${searchQuery ? "yes" : "null"} latency_ms=${latencyMs}`
4639
+ `${TAG14} channel=${channel} verdict=${verdict} promptInjection=${promptInjectionRisk} intent=${intent} language=${language} complexity=${complexity} retrievalClass=${retrievalClass} requiresHistory=${requiresHistory} rewrite=${!isAdmin && processedText !== rawText.trim()} searchQuery=${searchQuery ? "yes" : "null"} latency_ms=${latencyMs}`
4621
4640
  );
4622
4641
  if (verdict !== "clean") {
4623
4642
  console.warn(
@@ -5078,6 +5097,9 @@ ${lines}]`;
5078
5097
  let gatewayResult = null;
5079
5098
  if (!isGreeting) {
5080
5099
  gatewayResult = await processInbound(message, "web-public");
5100
+ console.log(
5101
+ `[chat-route] session=${session_key.slice(0, 8)} retrievalClass=${gatewayResult.retrievalClass} intent=${gatewayResult.intent}`
5102
+ );
5081
5103
  if (gatewayResult.screening.verdict === "discard") {
5082
5104
  console.log(
5083
5105
  `[chat-route] session=${session_key.slice(0, 8)} gateway=discard reason=${gatewayResult.screening.reason}`
@@ -6480,9 +6502,17 @@ function readUsersFile() {
6480
6502
  if (!raw) return [];
6481
6503
  return JSON.parse(raw);
6482
6504
  }
6505
+ function claudeSpawnPath() {
6506
+ const base = process.env.PATH ?? "/usr/bin:/bin:/usr/sbin:/sbin";
6507
+ return `${base}:/opt/homebrew/bin:/usr/local/bin`;
6508
+ }
6483
6509
  function checkAuthStatus() {
6484
6510
  try {
6485
- execFileSync2("claude", ["auth", "status"], { encoding: "utf-8", timeout: 5e3 });
6511
+ execFileSync2("claude", ["auth", "status"], {
6512
+ encoding: "utf-8",
6513
+ timeout: 5e3,
6514
+ env: { ...process.env, PATH: claudeSpawnPath() }
6515
+ });
6486
6516
  return true;
6487
6517
  } catch {
6488
6518
  return false;
@@ -6537,22 +6567,34 @@ app4.post("/claude-auth", async (c) => {
6537
6567
  return c.json({ launched: cdpReady });
6538
6568
  }
6539
6569
  ensureLogDir();
6570
+ writeFileSync6(logPath("claude-auth"), "");
6571
+ const claudeAuthLogFd = openSync(logPath("claude-auth"), "a");
6572
+ const onClaudeOutput = (chunk) => writeSync(claudeAuthLogFd, chunk);
6573
+ if (process.platform === "darwin") {
6574
+ vncLog("claude-auth", { action: "start", transport: "native", platform: "darwin" });
6575
+ const claudeProc2 = spawn("claude", ["auth", "login"], {
6576
+ env: { ...process.env, PATH: claudeSpawnPath() },
6577
+ stdio: ["ignore", "pipe", "pipe"]
6578
+ });
6579
+ claudeProc2.unref();
6580
+ claudeProc2.stdout?.on("data", onClaudeOutput);
6581
+ claudeProc2.stderr?.on("data", onClaudeOutput);
6582
+ claudeProc2.once("close", () => closeSync(claudeAuthLogFd));
6583
+ return c.json({ started: true, transport: "native" });
6584
+ }
6540
6585
  if (transport === "vnc") {
6541
6586
  const vncReady = await ensureVnc();
6542
6587
  if (!vncReady) return c.json({ error: "VNC display failed to start" }, 500);
6543
6588
  }
6544
6589
  await ensureCdp(transport);
6545
- writeFileSync6(logPath("claude-auth"), "");
6546
6590
  const chromiumWrapper = writeChromiumWrapper();
6547
6591
  const x11Env = buildX11Env(chromiumWrapper, transport);
6548
6592
  vncLog("claude-auth", { action: "start", transport });
6549
- const claudeAuthLogFd = openSync(logPath("claude-auth"), "a");
6550
6593
  const claudeProc = spawn("claude", ["auth", "login"], {
6551
6594
  env: x11Env,
6552
6595
  stdio: ["ignore", "pipe", "pipe"]
6553
6596
  });
6554
6597
  claudeProc.unref();
6555
- const onClaudeOutput = (chunk) => writeSync(claudeAuthLogFd, chunk);
6556
6598
  claudeProc.stdout?.on("data", onClaudeOutput);
6557
6599
  claudeProc.stderr?.on("data", onClaudeOutput);
6558
6600
  claudeProc.once("close", () => closeSync(claudeAuthLogFd));
@@ -8537,7 +8579,12 @@ async function performSpawnWithInitialMessage(args) {
8537
8579
  dormantPlugins,
8538
8580
  activePlugins,
8539
8581
  specialistDomains,
8540
- tunnelUrl
8582
+ tunnelUrl,
8583
+ // Task 382 — thread the active conversation's UUID through to the
8584
+ // manager so the PTY env carries CONVERSATION_NODE_ID. Undefined is
8585
+ // legitimate (autonomous spawns); the manager's UUID gate filters
8586
+ // unshapely values.
8587
+ conversationNodeId: args.conversationNodeId
8541
8588
  });
8542
8589
  console.log(`${TAG18} forward-spawn-start managerBase=${managerBase2()} bytes=${upstreamPayload.length} hidden=${args.hidden} specialist=${args.specialist ?? "none"}`);
8543
8590
  const forwardStart = Date.now();
@@ -8632,6 +8679,7 @@ app15.post("/", async (c) => {
8632
8679
  const specialist = typeof body.specialist === "string" && /^[A-Za-z0-9_-]{1,64}$/.test(body.specialist) ? body.specialist : void 0;
8633
8680
  const model = typeof body.model === "string" && /^[A-Za-z0-9._-]{1,64}$/.test(body.model) ? body.model : void 0;
8634
8681
  console.log(`${TAG18} spawn-request-in surface=${authSurface} accountId=${senderId.slice(0, 8)} userId=${userId ? userId.slice(0, 8) : "absent"} channel=${channel} permissionMode=${permissionMode ?? "default"} specialist=${specialist ?? "none"} model=${model ?? "default"} initialMessage=${initialMessage ? "yes" : "no"}`);
8682
+ const conversationNodeId = cacheKey ? getConversationIdForSession(cacheKey) : void 0;
8635
8683
  const { response, claudeSessionId } = await performSpawnWithInitialMessage({
8636
8684
  senderId,
8637
8685
  userId,
@@ -8641,7 +8689,8 @@ app15.post("/", async (c) => {
8641
8689
  hidden: false,
8642
8690
  specialist,
8643
8691
  model,
8644
- initialMessage
8692
+ initialMessage,
8693
+ conversationNodeId
8645
8694
  });
8646
8695
  mintTranscriptEdge(
8647
8696
  cacheKey ? getConversationIdForSession(cacheKey) : void 0,
@@ -8719,7 +8768,13 @@ app15.post("/resume", async (c) => {
8719
8768
  dormantPlugins,
8720
8769
  activePlugins,
8721
8770
  specialistDomains,
8722
- tunnelUrl
8771
+ tunnelUrl,
8772
+ // Task 382 — resume parity with /spawn: thread the active
8773
+ // conversationId so the re-spawned PTY's MCP env carries
8774
+ // CONVERSATION_NODE_ID. Undefined when no active conversation is
8775
+ // bound to this cacheKey (rare for resume — the user is by
8776
+ // definition continuing a known conversation).
8777
+ conversationNodeId: getConversationIdForSession(cacheKey)
8723
8778
  })
8724
8779
  }).catch((err) => {
8725
8780
  console.error(`${TAG18} fetch-failed op=resume message=${err instanceof Error ? err.message : String(err)}`);
@@ -12133,7 +12188,11 @@ app32.post("/", requireAdminSession, async (c) => {
12133
12188
  hidden: true,
12134
12189
  specialist: "database-operator",
12135
12190
  model: void 0,
12136
- initialMessage
12191
+ initialMessage,
12192
+ // Task 382 — LinkedIn ingest is a hidden autonomous spawn with no
12193
+ // parent admin conversation; the database-operator threads
12194
+ // `producedByTaskId` via work-create instead. Leave undefined.
12195
+ conversationNodeId: void 0
12137
12196
  });
12138
12197
  if (!response.ok) {
12139
12198
  const detail = await response.text().catch(() => "");