@rubytech/create-maxy-code 0.1.441 → 0.1.443
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/dist/__tests__/converge-client-admins.test.js +50 -0
- package/dist/converge-client-admins.js +67 -0
- package/dist/index.js +107 -1
- package/package.json +1 -1
- package/payload/platform/docs/superpowers/plans/2026-07-13-account-schema-ontology-projection.md +547 -0
- package/payload/platform/docs/superpowers/plans/2026-07-14-graph-top-level-labels-ontology-single-source.md +458 -0
- package/payload/platform/docs/superpowers/specs/2026-07-13-account-schema-ontology-projection-design.md +178 -0
- package/payload/platform/docs/superpowers/specs/2026-07-14-graph-top-level-labels-ontology-single-source-design.md +119 -0
- package/payload/platform/plugins/admin/.claude-plugin/plugin.json +1 -1
- package/payload/platform/plugins/admin/PLUGIN.md +7 -1
- package/payload/platform/plugins/admin/hooks/__tests__/fs-schema-guard.test.sh +19 -2
- package/payload/platform/plugins/admin/mcp/dist/index.js +126 -1
- package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/admin/mcp/dist/tools/account-lifecycle.d.ts +30 -0
- package/payload/platform/plugins/admin/mcp/dist/tools/account-lifecycle.d.ts.map +1 -1
- package/payload/platform/plugins/admin/mcp/dist/tools/account-lifecycle.js +78 -0
- package/payload/platform/plugins/admin/mcp/dist/tools/account-lifecycle.js.map +1 -1
- package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +37 -2
- package/payload/platform/plugins/docs/references/admin-ui.md +1 -1
- package/payload/platform/plugins/docs/references/troubleshooting.md +35 -0
- package/payload/platform/plugins/memory/PLUGIN.md +2 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/resolve-active-vertical.test.js +12 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/resolve-active-vertical.test.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/resolve-active-vertical.d.ts +9 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/resolve-active-vertical.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/resolve-active-vertical.js +19 -2
- package/payload/platform/plugins/memory/mcp/dist/lib/resolve-active-vertical.js.map +1 -1
- package/payload/platform/plugins/memory/references/schema-estate-agent.md +8 -3
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/device-flow.test.js +37 -4
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/device-flow.test.js.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/auth/device-flow.d.ts +27 -3
- package/payload/platform/plugins/outlook/mcp/dist/auth/device-flow.d.ts.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/auth/device-flow.js +52 -11
- package/payload/platform/plugins/outlook/mcp/dist/auth/device-flow.js.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/index.js +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/tools/account-register.d.ts +5 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/account-register.d.ts.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/tools/account-register.js +32 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/account-register.js.map +1 -1
- package/payload/platform/plugins/whatsapp/mcp/dist/index.js +20 -22
- package/payload/platform/plugins/whatsapp/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/whatsapp/references/channels-whatsapp.md +3 -1
- package/payload/platform/scripts/__tests__/account-schema-owned-dirs.test.sh +58 -0
- package/payload/platform/scripts/__tests__/provision-role-stamp.test.sh +6 -0
- package/payload/platform/scripts/lib/account-schema-owned-dirs.py +143 -10
- package/payload/platform/scripts/lib/provision-account-dir.sh +5 -0
- package/payload/platform/scripts/logs-read.sh +24 -6
- package/payload/platform/scripts/logs-read.test.sh +42 -0
- package/payload/platform/scripts/name-glsmith-owner.sh +239 -0
- package/payload/platform/scripts/rss-sampler.sh +107 -0
- package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js +2 -0
- package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js.map +1 -1
- package/payload/server/public/assets/{AdminLoginScreens-CukwQufW.js → AdminLoginScreens-BJVwtpLH.js} +1 -1
- package/payload/server/public/assets/{AdminShell-BwEBH6tT.js → AdminShell-CMu8_Xwn.js} +1 -1
- package/payload/server/public/assets/{Checkbox-B629mOPp.js → Checkbox-CzD0ORqA.js} +1 -1
- package/payload/server/public/assets/{admin-CdFRCUUZ.js → admin-D0yk2LMq.js} +1 -1
- package/payload/server/public/assets/{browser-BWR59IkB.js → browser-CvfbgznO.js} +1 -1
- package/payload/server/public/assets/{calendar-kuTcxX1t.js → calendar-fw4WethJ.js} +1 -1
- package/payload/server/public/assets/chat-DhD-NES_.js +1 -0
- package/payload/server/public/assets/chevron-left-B3xSeEDR.js +1 -0
- package/payload/server/public/assets/data-CUnDzDox.js +1 -0
- package/payload/server/public/assets/{file-download-4mKJxUi-.js → file-download-Px4s0Bhp.js} +1 -1
- package/payload/server/public/assets/{graph-CboWM2_-.js → graph-DbT8nA6R.js} +1 -1
- package/payload/server/public/assets/{graph-labels-B8YAdo3Z.js → graph-labels-C1YIyLiA.js} +1 -1
- package/payload/server/public/assets/{maximize-2-CxstTgoK.js → maximize-2-BwphzI9S.js} +1 -1
- package/payload/server/public/assets/{operator-oSaB1mya.js → operator-zcIfeB14.js} +1 -1
- package/payload/server/public/assets/{page-DMeJHVfA.js → page-6fCnIb45.js} +2 -2
- package/payload/server/public/assets/page-CfQFAJia.js +1 -0
- package/payload/server/public/assets/{public-C54tl6V4.js → public-C0ehZCdE.js} +1 -1
- package/payload/server/public/assets/{rotate-ccw-COGQTi7n.js → rotate-ccw-C7BqkMec.js} +1 -1
- package/payload/server/public/assets/{tasks-B97VpwEJ.js → tasks-DlgiCafL.js} +1 -1
- package/payload/server/public/assets/{time-entry-format-C09rDHo9.js → time-entry-format-CyNYVDkq.js} +1 -1
- package/payload/server/public/assets/{useCopyFeedback-DHoYHiK9.js → useCopyFeedback-ByGUMN94.js} +1 -1
- package/payload/server/public/assets/useSubAccountSwitcher-1izQvPiD.css +1 -0
- package/payload/server/public/assets/useVoiceRecorder-XgpWvXpk.js +2 -0
- package/payload/server/public/browser.html +5 -5
- package/payload/server/public/calendar.html +6 -6
- package/payload/server/public/chat.html +12 -12
- package/payload/server/public/data.html +10 -10
- package/payload/server/public/graph.html +11 -11
- package/payload/server/public/index.html +13 -13
- package/payload/server/public/operator.html +14 -14
- package/payload/server/public/public.html +12 -12
- package/payload/server/public/tasks.html +5 -5
- package/payload/server/server.js +742 -321
- package/payload/platform/plugins/memory/references/schema-trades.md +0 -34
- package/payload/server/public/assets/chat-ejnq2rck.js +0 -1
- package/payload/server/public/assets/chevron-left-u4tjq7fI.js +0 -1
- package/payload/server/public/assets/data-dZbOyLdT.js +0 -1
- package/payload/server/public/assets/page-DEkI-Gl1.js +0 -1
- package/payload/server/public/assets/useSubAccountSwitcher-Dzmd7Aip.css +0 -1
- package/payload/server/public/assets/useVoiceRecorder-b7HzTFs0.js +0 -2
- /package/payload/server/public/assets/{useSubAccountSwitcher-DLJFISqf.js → useSubAccountSwitcher-s6WlRn52.js} +0 -0
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# Single-source the `/graph` top-level label allowlist from the vertical ontology — Design
|
|
2
|
+
|
|
3
|
+
Task: `.tasks/1624-single-source-graph-labels-top-level-from-vertical-ontology.md`.
|
|
4
|
+
|
|
5
|
+
## Problem
|
|
6
|
+
|
|
7
|
+
A businessType's top-level operator-entry node types live in the `## Top-level node
|
|
8
|
+
types` table of each vertical's `schema-<vertical>.md` (established by 1622). A third
|
|
9
|
+
encoding of "top-level entity" still lives apart: `platform/ui/app/lib/graph-labels.ts`
|
|
10
|
+
`FILTER_TOP_LEVEL_LABELS` is a hand-maintained global union of every brand's top-level
|
|
11
|
+
labels — the `/graph` filter-popover chip allowlist. It drifts from the ontology by
|
|
12
|
+
construction: a top-level label added to a vertical schema does not become a chip until
|
|
13
|
+
someone also edits this TypeScript set.
|
|
14
|
+
|
|
15
|
+
## Goal
|
|
16
|
+
|
|
17
|
+
Derive the `/graph` chip allowlist from the same per-vertical ontology source so a
|
|
18
|
+
top-level label added to a vertical schema surfaces as a chip with no second edit, while
|
|
19
|
+
preserving today's exact chip visibility as the acceptance baseline.
|
|
20
|
+
|
|
21
|
+
## Blast radius (evidence)
|
|
22
|
+
|
|
23
|
+
`FILTER_TOP_LEVEL_LABELS` has four real code consumers:
|
|
24
|
+
|
|
25
|
+
1. `server/routes/admin/graph-labels-in-graph.ts` — popover chip allowlist (the route the
|
|
26
|
+
task names). Passes the set as the `$allowed` cypher param; the cypher count-gates.
|
|
27
|
+
2. `server/routes/admin/graph-subgraph.ts:280` — default-view label set.
|
|
28
|
+
3. `server/routes/admin/graph-default-view.ts:158` — default-view eligibility check.
|
|
29
|
+
4. `app/data/searchHelpers.ts:46` — client-side `/data` search chip pre-filter, a
|
|
30
|
+
build-time static import that cannot read files.
|
|
31
|
+
|
|
32
|
+
Scope decision (operator-confirmed): **the three server routes switch to the
|
|
33
|
+
ontology-derived source. The client `searchHelpers.ts` keeps the static set**, with a
|
|
34
|
+
follow-up task filed to close that last second source.
|
|
35
|
+
|
|
36
|
+
## Facts that shape the mechanism
|
|
37
|
+
|
|
38
|
+
- Only `schema-construction.md` and `schema-estate-agent.md` currently carry a
|
|
39
|
+
`## Top-level node types` section. Every other vertical (and 1626) adds theirs later.
|
|
40
|
+
- `schema-base.md` has no top-level section; its `## Node Types` table mixes top-level and
|
|
41
|
+
child types (Message, Section, Chunk). Base labels therefore **cannot** be derived — they
|
|
42
|
+
stay an explicit list. This resolves the task's open base-list decision.
|
|
43
|
+
- The construction top-level table includes `WhatsAppGroup` typed `cdm:Channel`. The 1622
|
|
44
|
+
Python file-bucket generator excludes non-`schema:` rows (a channel gets no file bucket),
|
|
45
|
+
but `WhatsAppGroup` **is** a current graph chip. So the graph derivation takes **every**
|
|
46
|
+
Neo4j Label row and applies **no** namespace filter.
|
|
47
|
+
- The construction table has 5 columns, estate-agent 4. The Neo4j Label column is resolved
|
|
48
|
+
by header name, never by index.
|
|
49
|
+
|
|
50
|
+
## Architecture
|
|
51
|
+
|
|
52
|
+
New server-only module `platform/ui/server/lib/top-level-labels.ts`:
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
getTopLevelLabelAllowlist(opts?: { referencesDir?: string }): ReadonlySet<string>
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
- Resolves `referencesDir` to `${MAXY_PLATFORM_ROOT}/plugins/memory/references` (same
|
|
59
|
+
env/`__dirname` fallback the UI server already uses), or the injected override.
|
|
60
|
+
- Reads every `schema-*.md`. For each, locates the `## Top-level node types` heading by
|
|
61
|
+
case-insensitive prefix, reads the first pipe-table beneath it (before the next `##`),
|
|
62
|
+
and extracts the `Neo4j Label` column by header name, stripping backticks.
|
|
63
|
+
- Unions parsed labels with the module const `STATIC_TOP_LEVEL_LABELS` — the base-schema
|
|
64
|
+
infra plus any vertical whose schema has no top-level section yet (knowledge-work today).
|
|
65
|
+
Documented to shrink as 1626 adds sections.
|
|
66
|
+
- Caches the result at module level (schema files are static per install). An injected
|
|
67
|
+
`referencesDir` bypasses the cache so tests are isolated.
|
|
68
|
+
- Emits one observability line on computation:
|
|
69
|
+
`op=top-level-labels source=ontology count=<n> files=<...> derived=<d> static=<s>`.
|
|
70
|
+
|
|
71
|
+
The three server routes replace `FILTER_TOP_LEVEL_LABELS` with
|
|
72
|
+
`getTopLevelLabelAllowlist()` (a `ReadonlySet<string>`, so `.has()` and spread are
|
|
73
|
+
preserved). Cypher count-gating is unchanged: a non-resident label surfaces no chip.
|
|
74
|
+
|
|
75
|
+
Unchanged: `FILTER_TOP_LEVEL_LABELS` (client), `HIDDEN_BY_DEFAULT_LABELS`,
|
|
76
|
+
`AGENT_ACTION_LABELS`, `EXCLUDED_EDGE_TYPES`.
|
|
77
|
+
|
|
78
|
+
## Byte-identical guarantee
|
|
79
|
+
|
|
80
|
+
Derived union = construction table (11) ∪ estate table (4) ∪ `STATIC_TOP_LEVEL_LABELS`
|
|
81
|
+
(33) = the current 48-member `FILTER_TOP_LEVEL_LABELS`. Because the sets are equal, the
|
|
82
|
+
existing route tests (which assert membership against `FILTER_TOP_LEVEL_LABELS`) keep
|
|
83
|
+
passing unchanged.
|
|
84
|
+
|
|
85
|
+
`STATIC_TOP_LEVEL_LABELS` (top-level labels not yet sourced from a vertical section):
|
|
86
|
+
|
|
87
|
+
- Base-schema infra: `LocalBusiness`, `Service`, `PriceSpecification`,
|
|
88
|
+
`OpeningHoursSpecification`, `Organization`, `Person`, `UserProfile`, `Preference`,
|
|
89
|
+
`AdminUser`, `AccessGrant`, `KnowledgeDocument`, `DigitalDocument`, `CreativeWork`,
|
|
90
|
+
`Question`, `FAQPage`, `DefinedTerm`, `Review`, `ImageObject`, `Invoice`, `Task`,
|
|
91
|
+
`Project`, `Event`, `Workflow`, `Email`, `EmailAccount`, `Agent`.
|
|
92
|
+
- Knowledge-work vertical (no top-level section yet; 1626): `Objective`, `KeyResult`,
|
|
93
|
+
`Decision`, `Risk`, `Source`, `Finding`, `Hypothesis`.
|
|
94
|
+
|
|
95
|
+
## Testing
|
|
96
|
+
|
|
97
|
+
Committed, colocated `platform/ui/server/lib/top-level-labels.test.ts`:
|
|
98
|
+
|
|
99
|
+
1. `getTopLevelLabelAllowlist()` against the real references dir deep-equals
|
|
100
|
+
`FILTER_TOP_LEVEL_LABELS` (regression baseline).
|
|
101
|
+
2. A fixture references dir with an added top-level row surfaces that label with no change
|
|
102
|
+
to any TypeScript set.
|
|
103
|
+
3. A `cdm:`-namespaced row (e.g. `WhatsAppGroup`) is included (no namespace filter).
|
|
104
|
+
|
|
105
|
+
The pre-existing `labels-in-graph` and `graph-default-topLevel` tests must still pass
|
|
106
|
+
after the swap (count-gating and eligibility unchanged).
|
|
107
|
+
|
|
108
|
+
## Observability
|
|
109
|
+
|
|
110
|
+
`op=top-level-labels source=ontology count=<n>` on the server log, emitted when the
|
|
111
|
+
allowlist is resolved, so an operator can confirm the chips come from the ontology, not a
|
|
112
|
+
stale hardcoded list.
|
|
113
|
+
|
|
114
|
+
## Out of scope
|
|
115
|
+
|
|
116
|
+
- Client `searchHelpers.ts` static set — filed as a follow-up task.
|
|
117
|
+
- `HIDDEN_BY_DEFAULT_LABELS`, `AGENT_ACTION_LABELS`, `EXCLUDED_EDGE_TYPES`.
|
|
118
|
+
- Adding `## Top-level node types` sections to verticals that lack one (1626).
|
|
119
|
+
- The file-schema generator and quotation reification (landed in 1622).
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "admin",
|
|
3
|
-
"description": "Platform administration plugin. Provides system-status, public-hostname (deterministic Cloudflare public-URL resolver, single call returning the operator's canonical hostname so agents never guess property names on :CloudflareHostname nodes), admin-hostname (the admin-host twin of public-hostname — resolves the host that serves /calendar and the rest of the admin shell behind sign-in from the same ingress + alias + operator files, so agents point the operator at the admin /calendar URL instead of the public host that 404s it), publish-site (move an extracted static-site tree under <accountDir>/sites/<slug>/, emit one canonical path slug, and refresh llms.txt at the site root — typed refusal taxonomy replaces the prior skill-prose contract), brand-settings, account-manage, capabilities-here (deterministic install-introspection — returns enabledPlugins, tier, models, installed platform + premium specialists, registered Claude Code agent types, and the brand block as a single JSON object; the canonical tool for grounding any claim about what is enabled on the current account), account-update, the multi-account managed-service lifecycle tools (account_list, account_create, account_delete, account_purge, account_channel_routing, account_channel_routing_set — one Rubytech operator hosting N client accounts on one house install; create/delete/purge are operator-only and purge is two-step with a typed confirmation; account_create requires a managing house admin (its userId) and refuses without one, recording it as the sub-account's managingAdminUserId, and also requires the client business owner's real name (ownerName), refusing a blank or the placeholder \"Owner\" and seeding it onto the sub-account's owner AdminUser and owner Person; account_reassign_admin repoints a client sub-account's managing admin to another live house admin (the repair path for a dangling managingAdminUserId); account_channel_routing_set repoints the install's single WhatsApp/Telegram inbound stream to a different sub-account), admin-add (refuses on a client sub-account, where admin access is already install-wide so a per-operator seat grants nothing — the sub-account's one identity, its business owner, is set at account creation; the house account, where operators hold PIN logins and admins[] rows, is unchanged), admin-remove (refuses to remove a house admin who still manages one or more client sub-accounts, naming them so the operator reassigns each with account_reassign_admin first — never silently orphans them), admin-list, admin-update-pin, agent-list, agent-config-read, logs-read, plugin-read, skill-load (one-call resolve+read for SKILL.md by skill name, the canonical primitive for loading a named skill; plugin-read remains the reader for references/* and PLUGIN.md), skill-search (free-text intent → skill resolver: ranks every plugin skill's SKILL.md name+description against a query and returns the top owners with their canonical skills/<slug>/ path, so intent→skill mapping survives the available-skills menu leaving context after a compaction), store-skill (deterministic write counterpart to plugin-read; persists operator-authored skills as plugin files under the active account), session-reset, session-resume, wifi, and action-approval tools (action-pending, action-approve, action-reject, action-edit) for managing the Maxy platform.",
|
|
3
|
+
"description": "Platform administration plugin. Provides system-status, public-hostname (deterministic Cloudflare public-URL resolver, single call returning the operator's canonical hostname so agents never guess property names on :CloudflareHostname nodes), admin-hostname (the admin-host twin of public-hostname — resolves the host that serves /calendar and the rest of the admin shell behind sign-in from the same ingress + alias + operator files, so agents point the operator at the admin /calendar URL instead of the public host that 404s it), publish-site (move an extracted static-site tree under <accountDir>/sites/<slug>/, emit one canonical path slug, and refresh llms.txt at the site root — typed refusal taxonomy replaces the prior skill-prose contract), brand-settings, account-manage, capabilities-here (deterministic install-introspection — returns enabledPlugins, tier, models, installed platform + premium specialists, registered Claude Code agent types, and the brand block as a single JSON object; the canonical tool for grounding any claim about what is enabled on the current account), account-update, the multi-account managed-service lifecycle tools (account_list, account_create, account_delete, account_purge, account_channel_routing, account_channel_routing_set — one Rubytech operator hosting N client accounts on one house install; create/delete/purge are operator-only and purge is two-step with a typed confirmation; account_create requires a managing house admin (its userId) and refuses without one, recording it as the sub-account's managingAdminUserId, and also requires the client business owner's real name (ownerName), refusing a blank or the placeholder \"Owner\" and seeding it onto the sub-account's owner AdminUser and owner Person; account_reassign_admin repoints a client sub-account's managing admin to another live house admin (the repair path for a dangling managingAdminUserId); account_roster is the canonical per-account identity view — one row per sub-account giving business name, role, the managing house admin resolved to a name with status (assigned/absent/dangling), the business owner from the graph with status (named/nameless), and the WhatsApp account-manager binding if any, each field carrying what it means and does not (managing admin is the responsible house team member not the owner; account manager is WhatsApp routing only with no admin rights), so the agent never stitches admin-list + account.json + whatsapp-config; account_set_owner_name names (or corrects) a client sub-account's business owner by writing the owned Person — the canonical name source — refusing a blank or the placeholder \"Owner\", the guarded repair for a nameless owner (the standing identity-audit op=nameless-owned-person detector); account_channel_routing_set repoints the install's single WhatsApp/Telegram inbound stream to a different sub-account), admin-add (refuses on a client sub-account, where admin access is already install-wide so a per-operator seat grants nothing — the sub-account's one identity, its business owner, is set at account creation; the house account, where operators hold PIN logins and admins[] rows, is unchanged), admin-remove (refuses to remove a house admin who still manages one or more client sub-accounts, naming them so the operator reassigns each with account_reassign_admin first — never silently orphans them), admin-list, admin-update-pin, agent-list, agent-config-read, logs-read, plugin-read, skill-load (one-call resolve+read for SKILL.md by skill name, the canonical primitive for loading a named skill; plugin-read remains the reader for references/* and PLUGIN.md), skill-search (free-text intent → skill resolver: ranks every plugin skill's SKILL.md name+description against a query and returns the top owners with their canonical skills/<slug>/ path, so intent→skill mapping survives the available-skills menu leaving context after a compaction), store-skill (deterministic write counterpart to plugin-read; persists operator-authored skills as plugin files under the active account), session-reset, session-resume, wifi, and action-approval tools (action-pending, action-approve, action-reject, action-edit) for managing the Maxy platform.",
|
|
4
4
|
"version": "0.1.0",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Rubytech LLC"
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: admin
|
|
3
3
|
surface: platform
|
|
4
|
-
description: "Platform administration plugin. Provides system-status, public-hostname (deterministic Cloudflare public-URL resolver, single call returning the operator's canonical hostname so agents never guess property names on :CloudflareHostname nodes), admin-hostname (the admin-host twin of public-hostname — resolves the host that serves /calendar and the rest of the admin shell behind sign-in from the same ingress + alias + operator files, so agents point the operator at the admin /calendar URL instead of the public host that 404s it), publish-site (move an extracted static-site tree under <accountDir>/sites/<slug>/, emit one canonical path slug, and refresh llms.txt at the site root — typed refusal taxonomy replaces the prior skill-prose contract), brand-settings, account-manage, capabilities-here (deterministic install-introspection — returns enabledPlugins, tier, models, installed platform + premium specialists, registered Claude Code agent types, and the brand block as a single JSON object; the canonical tool for grounding any claim about what is enabled on the current account), account-update, the multi-account managed-service lifecycle tools (account_list, account_create, account_delete, account_purge, account_channel_routing, account_channel_routing_set — one Rubytech operator hosting N client accounts on one house install; create/delete/purge are operator-only and purge is two-step with a typed confirmation; account_create requires a managing house admin (its userId) and refuses without one, recording it as the sub-account's managingAdminUserId, and also requires the client business owner's real name (ownerName), refusing a blank or the placeholder "Owner" and seeding it onto the sub-account's owner AdminUser and owner Person; account_reassign_admin repoints a client sub-account's managing admin to another live house admin (the repair path for a dangling managingAdminUserId); account_channel_routing_set repoints the install's single WhatsApp/Telegram inbound stream to a different sub-account), admin-add (refuses on a client sub-account, where admin access is already install-wide so a per-operator seat grants nothing — the sub-account's one identity, its business owner, is set at account creation; the house account, where operators hold PIN logins and admins[] rows, is unchanged), admin-remove (refuses to remove a house admin who still manages one or more client sub-accounts, naming them so the operator reassigns each with account_reassign_admin first — never silently orphans them), admin-list, admin-update-pin, agent-list, agent-config-read, logs-read, plugin-read, skill-load (one-call resolve+read for SKILL.md by skill name, the canonical primitive for loading a named skill; plugin-read remains the reader for references/* and PLUGIN.md), skill-search (free-text intent → skill resolver: ranks every plugin skill's SKILL.md name+description against a query and returns the top owners with their canonical skills/<slug>/ path, so intent→skill mapping survives the available-skills menu leaving context after a compaction), store-skill (deterministic write counterpart to plugin-read; persists operator-authored skills as plugin files under the active account), session-reset, session-resume, wifi, and action-approval tools (action-pending, action-approve, action-reject, action-edit) for managing the Maxy platform."
|
|
4
|
+
description: "Platform administration plugin. Provides system-status, public-hostname (deterministic Cloudflare public-URL resolver, single call returning the operator's canonical hostname so agents never guess property names on :CloudflareHostname nodes), admin-hostname (the admin-host twin of public-hostname — resolves the host that serves /calendar and the rest of the admin shell behind sign-in from the same ingress + alias + operator files, so agents point the operator at the admin /calendar URL instead of the public host that 404s it), publish-site (move an extracted static-site tree under <accountDir>/sites/<slug>/, emit one canonical path slug, and refresh llms.txt at the site root — typed refusal taxonomy replaces the prior skill-prose contract), brand-settings, account-manage, capabilities-here (deterministic install-introspection — returns enabledPlugins, tier, models, installed platform + premium specialists, registered Claude Code agent types, and the brand block as a single JSON object; the canonical tool for grounding any claim about what is enabled on the current account), account-update, the multi-account managed-service lifecycle tools (account_list, account_create, account_delete, account_purge, account_channel_routing, account_channel_routing_set — one Rubytech operator hosting N client accounts on one house install; create/delete/purge are operator-only and purge is two-step with a typed confirmation; account_create requires a managing house admin (its userId) and refuses without one, recording it as the sub-account's managingAdminUserId, and also requires the client business owner's real name (ownerName), refusing a blank or the placeholder "Owner" and seeding it onto the sub-account's owner AdminUser and owner Person; account_reassign_admin repoints a client sub-account's managing admin to another live house admin (the repair path for a dangling managingAdminUserId); account_roster is the canonical per-account identity view — one row per sub-account giving business name, role, the managing house admin resolved to a name with status (assigned/absent/dangling), the business owner from the graph with status (named/nameless), and the WhatsApp account-manager binding if any, each field carrying what it means and does not (managing admin is the responsible house team member not the owner; account manager is WhatsApp routing only with no admin rights), so the agent never stitches admin-list + account.json + whatsapp-config; account_set_owner_name names (or corrects) a client sub-account's business owner by writing the owned Person — the canonical name source — refusing a blank or the placeholder "Owner", the guarded repair for a nameless owner (the standing identity-audit op=nameless-owned-person detector); account_channel_routing_set repoints the install's single WhatsApp/Telegram inbound stream to a different sub-account), admin-add (refuses on a client sub-account, where admin access is already install-wide so a per-operator seat grants nothing — the sub-account's one identity, its business owner, is set at account creation; the house account, where operators hold PIN logins and admins[] rows, is unchanged), admin-remove (refuses to remove a house admin who still manages one or more client sub-accounts, naming them so the operator reassigns each with account_reassign_admin first — never silently orphans them), admin-list, admin-update-pin, agent-list, agent-config-read, logs-read, plugin-read, skill-load (one-call resolve+read for SKILL.md by skill name, the canonical primitive for loading a named skill; plugin-read remains the reader for references/* and PLUGIN.md), skill-search (free-text intent → skill resolver: ranks every plugin skill's SKILL.md name+description against a query and returns the top owners with their canonical skills/<slug>/ path, so intent→skill mapping survives the available-skills menu leaving context after a compaction), store-skill (deterministic write counterpart to plugin-read; persists operator-authored skills as plugin files under the active account), session-reset, session-resume, wifi, and action-approval tools (action-pending, action-approve, action-reject, action-edit) for managing the Maxy platform."
|
|
5
5
|
tools:
|
|
6
6
|
- name: system-status
|
|
7
7
|
publicAllowlist: false
|
|
@@ -30,12 +30,18 @@ tools:
|
|
|
30
30
|
- name: account_list
|
|
31
31
|
publicAllowlist: false
|
|
32
32
|
adminAllowlist: true
|
|
33
|
+
- name: account_roster
|
|
34
|
+
publicAllowlist: false
|
|
35
|
+
adminAllowlist: true
|
|
33
36
|
- name: account_create
|
|
34
37
|
publicAllowlist: false
|
|
35
38
|
adminAllowlist: true
|
|
36
39
|
- name: account_reassign_admin
|
|
37
40
|
publicAllowlist: false
|
|
38
41
|
adminAllowlist: true
|
|
42
|
+
- name: account_set_owner_name
|
|
43
|
+
publicAllowlist: false
|
|
44
|
+
adminAllowlist: true
|
|
39
45
|
- name: account_delete
|
|
40
46
|
publicAllowlist: false
|
|
41
47
|
adminAllowlist: true
|
|
@@ -27,13 +27,23 @@ trap 'rm -rf "$ACCT"' EXIT
|
|
|
27
27
|
cp "$TEMPLATE" "$ACCT/SCHEMA.md"
|
|
28
28
|
mkdir -p "$ACCT/projects/Acme" "$ACCT/documents" "$ACCT/output"
|
|
29
29
|
|
|
30
|
+
# A second account modelling a provisioned construction account: jobs/ is a
|
|
31
|
+
# derived domain bucket (the generator appends it to the allowed block, like a
|
|
32
|
+
# plugin-owned dir). Kept separate so the template-parity assertion below still
|
|
33
|
+
# checks the pristine shipped set.
|
|
34
|
+
ACCT_D=$(mktemp -d)
|
|
35
|
+
trap 'rm -rf "$ACCT" "$ACCT_D"' EXIT
|
|
36
|
+
cp "$TEMPLATE" "$ACCT_D/SCHEMA.md"
|
|
37
|
+
awk '1; /^```allowed-top-level$/{print "jobs"}' "$ACCT_D/SCHEMA.md" > "$ACCT_D/SCHEMA.md.tmp" && mv "$ACCT_D/SCHEMA.md.tmp" "$ACCT_D/SCHEMA.md"
|
|
38
|
+
mkdir -p "$ACCT_D/jobs/5-oaktree/Quotations"
|
|
39
|
+
|
|
30
40
|
PASS=0; FAIL=0
|
|
31
41
|
# Run one case in the account dir as cwd.
|
|
32
42
|
# $1 name $2 stdin $3 expected_exit $4 expected_log_re ("" = none)
|
|
33
43
|
run_case() {
|
|
34
|
-
local name="$1" stdin="$2" exp="$3" re="$4" ef actual
|
|
44
|
+
local name="$1" stdin="$2" exp="$3" re="$4" acct="${5:-$ACCT}" ef actual
|
|
35
45
|
ef=$(mktemp)
|
|
36
|
-
( cd "$
|
|
46
|
+
( cd "$acct" && printf '%s' "$stdin" | bash "$HOOK" >/dev/null 2>"$ef" )
|
|
37
47
|
actual=$?
|
|
38
48
|
local sc; sc=$(cat "$ef"); rm -f "$ef"
|
|
39
49
|
local ok=1
|
|
@@ -58,6 +68,13 @@ run_case "empty stdin block" ""
|
|
|
58
68
|
# JSON null file_path must coerce to empty (allow), not the literal string "None".
|
|
59
69
|
run_case "null path allow" '{"hook_event_name":"PreToolUse","tool_name":"Write","tool_input":{"file_path":null}}' 0 ""
|
|
60
70
|
|
|
71
|
+
# Derived domain buckets (construction account $ACCT_D): jobs/ passes at any
|
|
72
|
+
# depth (it is outside the three flat operator-data buckets), an undeclared
|
|
73
|
+
# top-level segment still blocks.
|
|
74
|
+
run_case "domain bucket deep allow" "$(mkenv Write file_path 'jobs/5-oaktree/Quotations/quote.pdf')" 0 "" "$ACCT_D"
|
|
75
|
+
run_case "domain bucket file allow" "$(mkenv Write file_path 'jobs/5-oaktree/notes.md')" 0 "" "$ACCT_D"
|
|
76
|
+
run_case "undeclared still blocks" "$(mkenv Write file_path 'invoices/x.pdf')" 2 "reason=top-level" "$ACCT_D"
|
|
77
|
+
|
|
61
78
|
# Allowed-set parse == documented set.
|
|
62
79
|
EXPECT="projects contacts documents url-get output generated extracted uploads agents specialists sites public cache secrets state logs tmp SCHEMA.md account.json AGENTS.md .claude .git"
|
|
63
80
|
GOT=$(awk '/^```allowed-top-level$/{f=1;next} /^```$/{f=0} f' "$ACCT/SCHEMA.md" | tr '\n' ' ' | sed 's/ *$//')
|
|
@@ -28,7 +28,7 @@ import { publishSite } from "./tools/publish-site.js";
|
|
|
28
28
|
import { resolveCapabilitiesHere } from "./tools/capabilities-here.js";
|
|
29
29
|
import { runCatalogueCensus } from "./lib/catalogue-census.js";
|
|
30
30
|
import { lifelineTool } from "../../../../lib/mcp-lifeline/dist/index.js";
|
|
31
|
-
import { listAccountsFromDir, isHouseAccount, archiveAccountDir, provisionClientAccount, findHouseAccount, getChannelRoutingAccountId, setChannelRoutingAccountId, seedAccountGraphRoot, isPlaceholderOwnerName, refuseClientAdminAdd, readHouseAdminUserIds, setManagingAdmin, findClientAccountsManagedBy, reassignManagingAdmin, ACCOUNT_NODE_COUNT_CYPHER, ACCOUNT_PURGE_CYPHER, } from "./tools/account-lifecycle.js";
|
|
31
|
+
import { listAccountsFromDir, isHouseAccount, archiveAccountDir, provisionClientAccount, findHouseAccount, getChannelRoutingAccountId, setChannelRoutingAccountId, seedAccountGraphRoot, isPlaceholderOwnerName, refuseClientAdminAdd, readHouseAdminUserIds, setManagingAdmin, findClientAccountsManagedBy, reassignManagingAdmin, ACCOUNT_NODE_COUNT_CYPHER, ACCOUNT_PURGE_CYPHER, classifyManagingAdmin, ownerNameStatus, readManagingAdminUserId, setOwnerName, } from "./tools/account-lifecycle.js";
|
|
32
32
|
function refusalGuidance(kind) {
|
|
33
33
|
switch (kind) {
|
|
34
34
|
case "unsafe-slug":
|
|
@@ -3313,6 +3313,98 @@ eagerTool(server, "account_list", "List every sub-account on this install (one h
|
|
|
3313
3313
|
content: [{ type: "text", text: accounts.length ? lines.join("\n") : "No accounts found." }],
|
|
3314
3314
|
};
|
|
3315
3315
|
});
|
|
3316
|
+
eagerTool(server, "account_roster", "Canonical per-account identity roster for this install. One row per sub-account: accountId, business name, role (house/client), the managing house admin (the responsible house team member — NOT the owner) resolved to a name with status assigned/absent/dangling, the business owner (the client's owner identity on the graph) with status named/nameless, and the WhatsApp account-manager binding if any (WhatsApp routing only — carries NO admin rights). This is the single tool for 'who owns each account and which admin manages it'; never stitch admin-list + account.json + whatsapp-config for this. Operator-only.", {}, async () => {
|
|
3317
|
+
if (!ACCOUNT_ID)
|
|
3318
|
+
return refuseNoAccount("account_roster");
|
|
3319
|
+
const TAG = "[account-roster]";
|
|
3320
|
+
const accounts = listAccountsFromDir(ACCOUNTS_ROOT);
|
|
3321
|
+
const liveHouse = readHouseAdminUserIds(ACCOUNTS_ROOT);
|
|
3322
|
+
// Graph reads: business name, owner (role:owner AdminUser's owned Person),
|
|
3323
|
+
// and managing-admin name (house AdminUser's owned Person). Best-effort — a
|
|
3324
|
+
// Neo4j hiccup renders the graph-derived fields unknown, never fails the roster.
|
|
3325
|
+
const businessName = new Map();
|
|
3326
|
+
const ownerName = new Map();
|
|
3327
|
+
const mgrName = new Map();
|
|
3328
|
+
try {
|
|
3329
|
+
const session = getSession();
|
|
3330
|
+
try {
|
|
3331
|
+
const ids = accounts.map((a) => a.accountId);
|
|
3332
|
+
const bn = await session.run(`UNWIND $ids AS id MATCH (b:LocalBusiness {accountId: id}) RETURN id, b.name AS name`, { ids });
|
|
3333
|
+
for (const r of bn.records)
|
|
3334
|
+
businessName.set(r.get("id"), (r.get("name") ?? "").trim());
|
|
3335
|
+
const own = await session.run(`UNWIND $ids AS id
|
|
3336
|
+
MATCH (au:AdminUser {accountId: id, role: 'owner'})-[:OWNS]->(p:Person)
|
|
3337
|
+
WHERE p.accountId = au.accountId
|
|
3338
|
+
RETURN id, p.givenName AS givenName, p.familyName AS familyName`, { ids });
|
|
3339
|
+
for (const r of own.records) {
|
|
3340
|
+
const g = r.get("givenName")?.trim() ?? "";
|
|
3341
|
+
const f = r.get("familyName")?.trim() ?? "";
|
|
3342
|
+
ownerName.set(r.get("id"), g ? (f ? `${g} ${f}` : g) : null);
|
|
3343
|
+
}
|
|
3344
|
+
const mgrIds = accounts.map((a) => readManagingAdminUserId(ACCOUNTS_ROOT, a.accountId)).filter((u) => !!u);
|
|
3345
|
+
if (mgrIds.length) {
|
|
3346
|
+
const mg = await session.run(`UNWIND $ids AS uid MATCH (au:AdminUser {userId: uid})-[:OWNS]->(p:Person)
|
|
3347
|
+
WHERE p.accountId = au.accountId
|
|
3348
|
+
RETURN uid, p.givenName AS givenName, p.familyName AS familyName`, { ids: mgrIds });
|
|
3349
|
+
for (const r of mg.records) {
|
|
3350
|
+
const g = r.get("givenName")?.trim() ?? "";
|
|
3351
|
+
const f = r.get("familyName")?.trim() ?? "";
|
|
3352
|
+
if (g)
|
|
3353
|
+
mgrName.set(r.get("uid"), f ? `${g} ${f}` : g);
|
|
3354
|
+
}
|
|
3355
|
+
}
|
|
3356
|
+
}
|
|
3357
|
+
finally {
|
|
3358
|
+
await session.close();
|
|
3359
|
+
}
|
|
3360
|
+
}
|
|
3361
|
+
catch { /* graph best-effort */ }
|
|
3362
|
+
// WhatsApp account-manager map (phone -> {managesAccount, mode}); reached the
|
|
3363
|
+
// same way admin already calls the UI server internal port (remote-auth).
|
|
3364
|
+
// Best-effort — null column on any failure.
|
|
3365
|
+
const amByAccount = new Map();
|
|
3366
|
+
try {
|
|
3367
|
+
const internalPort = parseInt(PLATFORM_PORT, 10);
|
|
3368
|
+
const res = await fetch(`http://127.0.0.1:${internalPort}/api/whatsapp/config`, {
|
|
3369
|
+
method: "POST",
|
|
3370
|
+
headers: { "content-type": "application/json" },
|
|
3371
|
+
body: JSON.stringify({ action: "list-account-managers" }),
|
|
3372
|
+
signal: AbortSignal.timeout(5000),
|
|
3373
|
+
});
|
|
3374
|
+
if (res.ok) {
|
|
3375
|
+
const body = await res.json();
|
|
3376
|
+
for (const [phone, v] of Object.entries(body.accountManagers ?? {})) {
|
|
3377
|
+
const managesAccount = typeof v === "string" ? v : v.managesAccount;
|
|
3378
|
+
const mode = typeof v === "string" ? "active" : (v.mode ?? "active");
|
|
3379
|
+
amByAccount.set(managesAccount, { phone, mode });
|
|
3380
|
+
}
|
|
3381
|
+
}
|
|
3382
|
+
}
|
|
3383
|
+
catch { /* account-manager column best-effort */ }
|
|
3384
|
+
let namelessOwners = 0, absentMgr = 0, danglingMgr = 0;
|
|
3385
|
+
const lines = accounts.map((a) => {
|
|
3386
|
+
const biz = businessName.get(a.accountId) || "(unnamed)";
|
|
3387
|
+
if (a.isHouse)
|
|
3388
|
+
return `- **${biz}** — accountId: ${a.accountId}, role: house`;
|
|
3389
|
+
const mgrId = readManagingAdminUserId(ACCOUNTS_ROOT, a.accountId);
|
|
3390
|
+
const mgrStatus = classifyManagingAdmin(mgrId, liveHouse);
|
|
3391
|
+
if (mgrStatus === "absent")
|
|
3392
|
+
absentMgr++;
|
|
3393
|
+
else if (mgrStatus === "dangling")
|
|
3394
|
+
danglingMgr++;
|
|
3395
|
+
const oName = ownerName.get(a.accountId) ?? null;
|
|
3396
|
+
const oStatus = ownerNameStatus(oName);
|
|
3397
|
+
if (oStatus === "nameless")
|
|
3398
|
+
namelessOwners++;
|
|
3399
|
+
const am = amByAccount.get(a.accountId);
|
|
3400
|
+
const mgrLabel = mgrStatus === "assigned" ? `${mgrName.get(mgrId) ?? mgrId} (assigned)` : mgrStatus;
|
|
3401
|
+
const ownerLabel = oStatus === "named" ? `${oName} (named)` : "nameless";
|
|
3402
|
+
const amLabel = am ? `${am.phone}${am.mode === "passive" ? " (passive)" : " (active)"}` : "none";
|
|
3403
|
+
return `- **${biz}** — accountId: ${a.accountId}, role: client, managing admin: ${mgrLabel}, owner: ${ownerLabel}, account manager: ${amLabel}`;
|
|
3404
|
+
});
|
|
3405
|
+
process.stderr.write(`${TAG} op=roster accounts=${accounts.length} nameless-owners=${namelessOwners} absent-mgr=${absentMgr} dangling-mgr=${danglingMgr}\n`);
|
|
3406
|
+
return { content: [{ type: "text", text: accounts.length ? `Account roster:\n\n${lines.join("\n")}` : "No accounts found." }] };
|
|
3407
|
+
});
|
|
3316
3408
|
eagerTool(server, "account_create", "Provision a new CLIENT sub-account on this install (managed service). REQUIRES two things: `adminUserId`, the userId of the house team member who will manage this client (get the choices from admin-list; ask the operator which one before creating); and `ownerName`, the real name of the client business's owner (ask the operator for it — never invent it or reuse the house operator's name). Mints a UUID, scaffolds the account directory (agents, specialists, settings, account.json with role:\"client\" and managingAdminUserId), seeds default plugins, and seeds the account's graph root (a :LocalBusiness plus its owner :AdminUser and owner :Person carrying ownerName) so you can populate it immediately. Refuses if adminUserId is missing/not a house admin, or if ownerName is missing/blank/the placeholder \"Owner\". Returns the new accountId. Operator-only; never creates the house sub-account (that is the install itself).", {
|
|
3317
3409
|
adminUserId: z.string().describe("The userId of the house admin who will manage this client sub-account. Must be a current house admin (see admin-list). Required — creation is refused without it."),
|
|
3318
3410
|
ownerName: z.string().describe("The real name of the client business's owner. Applied to both the owner AdminUser and owner Person for the new sub-account. Required — creation is refused when missing, blank, or the placeholder \"Owner\". Ask the operator; never reuse the house operator's name."),
|
|
@@ -3425,6 +3517,39 @@ eagerTool(server, "account_reassign_admin", "Reassign a CLIENT sub-account's man
|
|
|
3425
3517
|
process.stderr.write(`[account-lifecycle] op=reassign accountId=${accountId} from=${res.previousAdminUserId ? res.previousAdminUserId.slice(0, 8) : "-"} to=${adminUserId.slice(0, 8)}\n`);
|
|
3426
3518
|
return { content: [{ type: "text", text: `Reassigned client sub-account ${accountId} to managing admin ${adminUserId}. It takes effect on the next passive intake.` }] };
|
|
3427
3519
|
});
|
|
3520
|
+
eagerTool(server, "account_set_owner_name", "Name (or correct the name of) a CLIENT sub-account's business owner. Pass `accountId` and `name` (the owner's real name; refused if blank or the placeholder \"Owner\"). Writes the name onto the owner's owned Person — the canonical name source — renaming the existing one if present, else creating it. Use to repair a nameless owner (identity-audit flags these as op=nameless-owned-person). Does not touch managingAdminUserId. Operator-only.", {
|
|
3521
|
+
accountId: z.string().describe("The client sub-account whose owner to name."),
|
|
3522
|
+
name: z.string().describe("The owner's real name. Refused if blank or the placeholder \"Owner\"."),
|
|
3523
|
+
}, async ({ accountId, name }) => {
|
|
3524
|
+
if (!ACCOUNT_ID)
|
|
3525
|
+
return refuseNoAccount("account_set_owner_name");
|
|
3526
|
+
const TAG = "[account-set-owner-name]";
|
|
3527
|
+
if (isPlaceholderOwnerName(name)) {
|
|
3528
|
+
process.stderr.write(`${TAG} op=refused reason=no-owner-name account=${accountId.slice(0, 8)}\n`);
|
|
3529
|
+
return { content: [{ type: "text", text: `account_set_owner_name refused: give the owner's real name (not blank or "Owner").` }], isError: true };
|
|
3530
|
+
}
|
|
3531
|
+
// The tool names a CLIENT business owner. The house account's owner is an
|
|
3532
|
+
// operator identity, not a client owner, so refuse it explicitly rather than
|
|
3533
|
+
// renaming a house operator's Person by a mistyped id.
|
|
3534
|
+
if (isHouseAccount(ACCOUNTS_ROOT, accountId)) {
|
|
3535
|
+
process.stderr.write(`${TAG} op=refused reason=house-account account=${accountId.slice(0, 8)}\n`);
|
|
3536
|
+
return { content: [{ type: "text", text: `account_set_owner_name refused: ${accountId} is the house account. This tool names a client sub-account's business owner.` }], isError: true };
|
|
3537
|
+
}
|
|
3538
|
+
const session = getSession();
|
|
3539
|
+
try {
|
|
3540
|
+
const r = await setOwnerName({ session, accountId, name });
|
|
3541
|
+
process.stderr.write(`${TAG} op=named account=${accountId.slice(0, 8)} renamed=${r.personRenamed} created=${r.personCreated}\n`);
|
|
3542
|
+
return { content: [{ type: "text", text: `Owner of ${accountId} set to ${r.givenName}${r.familyName ? " " + r.familyName : ""} (${r.personRenamed ? "renamed existing Person" : "created Person"}).` }] };
|
|
3543
|
+
}
|
|
3544
|
+
catch (err) {
|
|
3545
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
3546
|
+
process.stderr.write(`${TAG} op=failed account=${accountId.slice(0, 8)} error=${msg}\n`);
|
|
3547
|
+
return { content: [{ type: "text", text: `account_set_owner_name FAILED: ${msg}` }], isError: true };
|
|
3548
|
+
}
|
|
3549
|
+
finally {
|
|
3550
|
+
await session.close();
|
|
3551
|
+
}
|
|
3552
|
+
});
|
|
3428
3553
|
eagerTool(server, "account_delete", "Archive a CLIENT sub-account: move its directory to data/accounts/.trash/ so it no longer appears in the sub-account list. Graph data is RETAINED — run account_purge with the typed confirmation to irreversibly delete it. Refuses to archive the house sub-account. Pass the accountId to archive.", {
|
|
3429
3554
|
accountId: z.string().describe("The client accountId (UUID) to archive."),
|
|
3430
3555
|
}, async ({ accountId }) => {
|