@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
|
@@ -33,6 +33,20 @@ make_account() { # $1=tree
|
|
|
33
33
|
echo "$A"
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
# A vertical-aware tree: brand.json carries a `vertical`, and the real memory
|
|
37
|
+
# schema references are symlinked in so the generator exercises the shipped
|
|
38
|
+
# schema-<vertical>.md top-level table.
|
|
39
|
+
REAL_MEMORY_REFS="$REAL_PROJECT_DIR/plugins/memory/references"
|
|
40
|
+
make_vertical_tree() { # $1=ships $2=vertical (schema-basename)
|
|
41
|
+
local T; T="$(mktemp -d)"
|
|
42
|
+
mkdir -p "$T/platform/plugins/memory" "$T/platform/config" "$T/platform/templates/account-schema"
|
|
43
|
+
ln -s "$REAL_PREMIUM" "$T/premium-plugins"
|
|
44
|
+
ln -s "$REAL_MEMORY_REFS" "$T/platform/plugins/memory/references"
|
|
45
|
+
cp "$REAL_TEMPLATE" "$T/platform/templates/account-schema/SCHEMA.md"
|
|
46
|
+
printf '{"installDir":"test-brand","shipsPremiumBundles":%s,"vertical":"%s"}\n' "$1" "$2" > "$T/platform/config/brand.json"
|
|
47
|
+
echo "$T"
|
|
48
|
+
}
|
|
49
|
+
|
|
36
50
|
# --- sitedesk-shipping brand: quoting added ---
|
|
37
51
|
T_SD="$(make_tree '["sitedesk"]')"; A_SD="$(make_account "$T_SD")"
|
|
38
52
|
export PROJECT_DIR="$T_SD/platform"
|
|
@@ -96,5 +110,49 @@ merge_owned_dirs_into_schema "$A_TB" >/dev/null
|
|
|
96
110
|
ALLOWED_TB="$(awk '/^```allowed-top-level$/{f=1;next} /^```$/{f=0} f' "$A_TB/SCHEMA.md")"
|
|
97
111
|
assert_grep "ledger" "$ALLOWED_TB" "shapeb-allowlist-has-ledger"
|
|
98
112
|
|
|
113
|
+
# --- construction vertical: domain buckets derived, no sites-collision ---
|
|
114
|
+
T_C="$(make_vertical_tree '["sitedesk"]' 'schema-construction')"; A_C="$(make_account "$T_C")"
|
|
115
|
+
export PROJECT_DIR="$T_C/platform"
|
|
116
|
+
OUT_C="$(merge_owned_dirs_into_schema "$A_C")"
|
|
117
|
+
# Match a non-empty derived list, not the bare key (which prints even for zero).
|
|
118
|
+
assert_grep "domainBuckets=jobs" "$OUT_C" "con-domain-log"
|
|
119
|
+
ALLOWED_C="$(awk '/^```allowed-top-level$/{f=1;next} /^```$/{f=0} f' "$A_C/SCHEMA.md")"
|
|
120
|
+
for d in jobs customers customer-sites assets visits quotes purchase-orders ppm-contracts; do
|
|
121
|
+
assert_grep "$d" "$ALLOWED_C" "con-allow-$d"
|
|
122
|
+
done
|
|
123
|
+
assert_nogrep "whatsapp-groups" "$ALLOWED_C" "con-no-transport-bucket"
|
|
124
|
+
assert_grep "customer-sites" "$ALLOWED_C" "con-site-renamed"
|
|
125
|
+
assert_grep "Domain entity buckets" "$(cat "$A_C/SCHEMA.md")" "con-region"
|
|
126
|
+
B_C="$(shasum "$A_C/SCHEMA.md" | awk '{print $1}')"
|
|
127
|
+
merge_owned_dirs_into_schema "$A_C" >/dev/null
|
|
128
|
+
assert_eq "$(shasum "$A_C/SCHEMA.md" | awk '{print $1}')" "$B_C" "con-idempotent"
|
|
129
|
+
|
|
130
|
+
# --- estate-agent vertical: its own buckets, not SiteDesk's ---
|
|
131
|
+
T_E="$(make_vertical_tree '["real-agent"]' 'schema-estate-agent')"; A_E="$(make_account "$T_E")"
|
|
132
|
+
export PROJECT_DIR="$T_E/platform"
|
|
133
|
+
merge_owned_dirs_into_schema "$A_E" >/dev/null
|
|
134
|
+
ALLOWED_E="$(awk '/^```allowed-top-level$/{f=1;next} /^```$/{f=0} f' "$A_E/SCHEMA.md")"
|
|
135
|
+
for d in properties listings viewings offers; do
|
|
136
|
+
assert_grep "$d" "$ALLOWED_E" "ea-allow-$d"
|
|
137
|
+
done
|
|
138
|
+
assert_nogrep "jobs" "$ALLOWED_E" "ea-no-sitedesk-jobs"
|
|
139
|
+
|
|
140
|
+
# --- no vertical declared: generic base only ---
|
|
141
|
+
T_N="$(make_tree '[]')"; A_N="$(make_account "$T_N")"
|
|
142
|
+
export PROJECT_DIR="$T_N/platform"
|
|
143
|
+
merge_owned_dirs_into_schema "$A_N" >/dev/null
|
|
144
|
+
ALLOWED_N="$(awk '/^```allowed-top-level$/{f=1;next} /^```$/{f=0} f' "$A_N/SCHEMA.md")"
|
|
145
|
+
assert_nogrep "jobs" "$ALLOWED_N" "novert-no-domain"
|
|
146
|
+
assert_nogrep "Domain entity buckets" "$(cat "$A_N/SCHEMA.md")" "novert-no-region"
|
|
147
|
+
|
|
148
|
+
# --- reconcile reports each ontology entity present/absent ---
|
|
149
|
+
A_RCD="$(make_account "$T_C")"
|
|
150
|
+
export PROJECT_DIR="$T_C/platform"
|
|
151
|
+
RCD_BEFORE="$(PROJECT_DIR="$PROJECT_DIR" python3 "$PLAT_SCRIPTS/lib/account-schema-owned-dirs.py" reconcile "$A_RCD")"
|
|
152
|
+
assert_grep "ontologyEntity=Job bucket=jobs present=false" "$RCD_BEFORE" "rcd-job-absent"
|
|
153
|
+
merge_owned_dirs_into_schema "$A_RCD" >/dev/null
|
|
154
|
+
RCD_AFTER="$(PROJECT_DIR="$PROJECT_DIR" python3 "$PLAT_SCRIPTS/lib/account-schema-owned-dirs.py" reconcile "$A_RCD")"
|
|
155
|
+
assert_grep "ontologyEntity=Job bucket=jobs present=true" "$RCD_AFTER" "rcd-job-present"
|
|
156
|
+
|
|
99
157
|
echo "PASS=$PASS FAIL=$FAIL"
|
|
100
158
|
if [ "$FAIL" -ne 0 ]; then printf '%s\n' "${FAILED[@]}"; exit 1; fi
|
|
@@ -9,6 +9,7 @@ TEMPLATES_DIR="$PROJECT_DIR/templates"
|
|
|
9
9
|
PASS=0; FAIL=0; FAILED=()
|
|
10
10
|
role_of() { python3 -c "import json,sys; print(json.load(open(sys.argv[1])).get('role',''))" "$1"; }
|
|
11
11
|
ms_of() { python3 -c "import json,sys; print(json.load(open(sys.argv[1])).get('managedService'))" "$1"; }
|
|
12
|
+
has_admins() { python3 -c "import json,sys; print('yes' if 'admins' in json.load(open(sys.argv[1])) else 'no')" "$1"; }
|
|
12
13
|
assert_eq() { if [ "$1" = "$2" ]; then PASS=$((PASS+1)); else FAIL=$((FAIL+1)); FAILED+=("$3: expected [$2] got [$1]"); fi; }
|
|
13
14
|
assert_nonempty() { if [ -s "$1" ]; then PASS=$((PASS+1)); else FAIL=$((FAIL+1)); FAILED+=("$2: expected non-empty file [$1]"); fi; }
|
|
14
15
|
|
|
@@ -24,6 +25,8 @@ assert_eq "$(role_of "$A1/account.json")" "house" "fresh-house-role"
|
|
|
24
25
|
assert_eq "$(ms_of "$A1/account.json")" "False" "fresh-managedService-default"
|
|
25
26
|
# Task 1483 — the WhatsApp passive requirement-intake identity is seeded, non-empty.
|
|
26
27
|
assert_nonempty "$A1/agents/passive/IDENTITY.md" "passive-identity-seeded"
|
|
28
|
+
# Task 1623 — the house account keeps admins[] (the install-wide access surface).
|
|
29
|
+
assert_eq "$(has_admins "$A1/account.json")" "yes" "house-keeps-admins"
|
|
27
30
|
rm -rf "$T1"
|
|
28
31
|
|
|
29
32
|
# Legacy account.json with NO role -> migrated to house
|
|
@@ -37,6 +40,9 @@ rm -rf "$T2"
|
|
|
37
40
|
T3=$(mktemp -d); A3="$T3/acct-client"
|
|
38
41
|
provision_account_dir "$A3" "cccccccc-cccc-cccc-cccc-cccccccccccc" "client" >/dev/null 2>&1
|
|
39
42
|
assert_eq "$(role_of "$A3/account.json")" "client" "client-role"
|
|
43
|
+
# Task 1623 — a client sub-account carries no admins[] (retired: seats 999,
|
|
44
|
+
# inbound read 1596, field 1623). Access is install-wide.
|
|
45
|
+
assert_eq "$(has_admins "$A3/account.json")" "no" "client-no-admins"
|
|
40
46
|
rm -rf "$T3"
|
|
41
47
|
|
|
42
48
|
echo "PASS=$PASS FAIL=$FAIL"
|
|
@@ -10,11 +10,34 @@ The owned-dir set for a brand is the union declared by every platform plugin
|
|
|
10
10
|
($PROJECT_DIR/plugins/*/PLUGIN.md) and every premium sub-plugin of a bundle the
|
|
11
11
|
brand ships ($PREMIUM_ROOT/<bundle>/plugins/*/PLUGIN.md, gated by brand.json
|
|
12
12
|
shipsPremiumBundles). Reads PROJECT_DIR from the environment.
|
|
13
|
+
|
|
14
|
+
The account SCHEMA.md also gains one domain-entity bucket per top-level
|
|
15
|
+
operator-entry node type of the brand's business ontology. That set is derived
|
|
16
|
+
from the brand vertical's schema reference (brand.json#vertical ->
|
|
17
|
+
plugins/memory/references/<vertical>.md, the '## Top-level node types' table),
|
|
18
|
+
so the file schema mirrors the graph ontology rather than a hardcoded list.
|
|
13
19
|
"""
|
|
14
20
|
import json, os, re, sys
|
|
15
21
|
|
|
16
22
|
MARK_START = "<!-- plugin-owned-dirs:start -->"
|
|
17
23
|
MARK_END = "<!-- plugin-owned-dirs:end -->"
|
|
24
|
+
ONT_START = "<!-- ontology-buckets:start -->"
|
|
25
|
+
ONT_END = "<!-- ontology-buckets:end -->"
|
|
26
|
+
|
|
27
|
+
LABEL_DIR_OVERRIDES = {"Site": "customer-sites"}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _label_to_dir(label):
|
|
31
|
+
"""Map a Neo4j label to its file-schema bucket dir: kebab-case-plural,
|
|
32
|
+
lowercased. camelCase word boundaries split on case. One override:
|
|
33
|
+
`Site` -> `customer-sites`, so the tool-owned `sites/` published tree is
|
|
34
|
+
untouched."""
|
|
35
|
+
if label in LABEL_DIR_OVERRIDES:
|
|
36
|
+
return LABEL_DIR_OVERRIDES[label]
|
|
37
|
+
kebab = re.sub(r"(?<=[a-z0-9])(?=[A-Z])", "-", label).lower()
|
|
38
|
+
if kebab.endswith("y") and len(kebab) > 1 and kebab[-2] not in "aeiou":
|
|
39
|
+
return kebab[:-1] + "ies"
|
|
40
|
+
return kebab + "s"
|
|
18
41
|
|
|
19
42
|
|
|
20
43
|
def _project_dir():
|
|
@@ -37,6 +60,21 @@ def _read_brand(project_dir):
|
|
|
37
60
|
return "unknown", []
|
|
38
61
|
|
|
39
62
|
|
|
63
|
+
def _read_vertical(project_dir):
|
|
64
|
+
"""Return the brand's default vertical (the schema file basename, e.g.
|
|
65
|
+
'schema-construction'), or None. Read from config/brand.json#vertical."""
|
|
66
|
+
path = os.path.join(project_dir, "config", "brand.json")
|
|
67
|
+
if not os.path.isfile(path):
|
|
68
|
+
return None
|
|
69
|
+
try:
|
|
70
|
+
with open(path) as f:
|
|
71
|
+
v = json.load(f).get("vertical")
|
|
72
|
+
v = (v or "").strip()
|
|
73
|
+
return v or None
|
|
74
|
+
except Exception:
|
|
75
|
+
return None
|
|
76
|
+
|
|
77
|
+
|
|
40
78
|
def _parse_plugin_md(path):
|
|
41
79
|
"""Return (name, [ {dir, description} ]) from a PLUGIN.md, or (None, [])."""
|
|
42
80
|
try:
|
|
@@ -108,6 +146,73 @@ def resolve(project_dir):
|
|
|
108
146
|
return [seen[d] for d in order]
|
|
109
147
|
|
|
110
148
|
|
|
149
|
+
def _top_level_labels(schema_path):
|
|
150
|
+
"""Extract operator-entry labels from the entity table under the
|
|
151
|
+
'## Top-level node types' heading of a vertical schema file. A row is
|
|
152
|
+
included iff its 'Schema.org Type' cell is a schema:-namespaced type
|
|
153
|
+
(excludes transport labels such as WhatsAppGroup -> cdm:Channel). Returns
|
|
154
|
+
labels in table order. No such heading -> empty list."""
|
|
155
|
+
try:
|
|
156
|
+
with open(schema_path) as f:
|
|
157
|
+
lines = f.read().split("\n")
|
|
158
|
+
except Exception:
|
|
159
|
+
return []
|
|
160
|
+
start = None
|
|
161
|
+
for i, ln in enumerate(lines):
|
|
162
|
+
if ln.startswith("## ") and "top-level node types" in ln.lower():
|
|
163
|
+
start = i
|
|
164
|
+
break
|
|
165
|
+
if start is None:
|
|
166
|
+
return []
|
|
167
|
+
hdr = None
|
|
168
|
+
for j in range(start + 1, len(lines)):
|
|
169
|
+
if lines[j].startswith("## "):
|
|
170
|
+
return []
|
|
171
|
+
if "|" in lines[j] and "Neo4j Label" in lines[j] and "Schema.org Type" in lines[j]:
|
|
172
|
+
hdr = j
|
|
173
|
+
break
|
|
174
|
+
if hdr is None:
|
|
175
|
+
return []
|
|
176
|
+
cols = [c.strip() for c in lines[hdr].strip().strip("|").split("|")]
|
|
177
|
+
label_i = cols.index("Neo4j Label")
|
|
178
|
+
type_i = cols.index("Schema.org Type")
|
|
179
|
+
out = []
|
|
180
|
+
for k in range(hdr + 2, len(lines)): # +2 skips the header separator row
|
|
181
|
+
row = lines[k]
|
|
182
|
+
if row.startswith("## "):
|
|
183
|
+
break
|
|
184
|
+
if "|" not in row or not row.strip():
|
|
185
|
+
break
|
|
186
|
+
cells = [c.strip() for c in row.strip().strip("|").split("|")]
|
|
187
|
+
if len(cells) <= max(label_i, type_i):
|
|
188
|
+
continue
|
|
189
|
+
label = cells[label_i].strip("`").strip()
|
|
190
|
+
stype = cells[type_i].strip("`").strip()
|
|
191
|
+
if label and stype.startswith("schema:"):
|
|
192
|
+
out.append(label)
|
|
193
|
+
return out
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def resolve_domain_buckets(project_dir):
|
|
197
|
+
"""Domain operator-entity buckets derived from the brand's vertical
|
|
198
|
+
ontology. Returns [{dir, label}] in table order, deduped by dir. Empty when
|
|
199
|
+
the brand declares no vertical or the vertical file has no top-level
|
|
200
|
+
section."""
|
|
201
|
+
vertical = _read_vertical(project_dir)
|
|
202
|
+
if not vertical:
|
|
203
|
+
return []
|
|
204
|
+
schema_path = os.path.join(project_dir, "plugins", "memory", "references", vertical + ".md")
|
|
205
|
+
seen = set()
|
|
206
|
+
out = []
|
|
207
|
+
for label in _top_level_labels(schema_path):
|
|
208
|
+
d = _label_to_dir(label)
|
|
209
|
+
if d in seen:
|
|
210
|
+
continue
|
|
211
|
+
seen.add(d)
|
|
212
|
+
out.append({"dir": d, "label": label})
|
|
213
|
+
return out
|
|
214
|
+
|
|
215
|
+
|
|
111
216
|
def _allowed_block(text):
|
|
112
217
|
"""Return (list_of_entries, start_idx, end_idx) for the fenced
|
|
113
218
|
allowed-top-level block. end_idx is the closing-fence line index."""
|
|
@@ -125,40 +230,47 @@ def _allowed_block(text):
|
|
|
125
230
|
return None, None, None
|
|
126
231
|
|
|
127
232
|
|
|
128
|
-
def _strip_region(text):
|
|
129
|
-
if
|
|
233
|
+
def _strip_region(text, start=MARK_START, end=MARK_END):
|
|
234
|
+
if start not in text:
|
|
130
235
|
return text
|
|
131
|
-
pre = text.split(
|
|
236
|
+
pre = text.split(start, 1)[0]
|
|
132
237
|
post = ""
|
|
133
|
-
if
|
|
134
|
-
post = text.split(
|
|
238
|
+
if end in text:
|
|
239
|
+
post = text.split(end, 1)[1]
|
|
135
240
|
return (pre.rstrip("\n") + "\n" + post.lstrip("\n")).rstrip("\n") + "\n"
|
|
136
241
|
|
|
137
242
|
|
|
138
243
|
def merge(project_dir, account_dir):
|
|
139
244
|
brand, _ = _read_brand(project_dir)
|
|
140
245
|
owned = resolve(project_dir)
|
|
246
|
+
domain = resolve_domain_buckets(project_dir)
|
|
141
247
|
schema_path = os.path.join(account_dir, "SCHEMA.md")
|
|
142
248
|
if not os.path.isfile(schema_path):
|
|
143
|
-
print(f"brand={brand} added=none")
|
|
249
|
+
print(f"brand={brand} added=none domainBuckets=none")
|
|
144
250
|
return
|
|
145
251
|
with open(schema_path) as f:
|
|
146
252
|
text = f.read()
|
|
147
253
|
entries, start, end = _allowed_block(text)
|
|
148
254
|
added = []
|
|
255
|
+
added_domain = []
|
|
149
256
|
if entries is not None:
|
|
150
257
|
present = set(entries)
|
|
151
258
|
lines = text.split("\n")
|
|
152
259
|
insert = []
|
|
153
260
|
for e in owned:
|
|
154
|
-
if e["dir"] not in present:
|
|
261
|
+
if e["dir"] not in present and e["dir"] not in insert:
|
|
155
262
|
insert.append(e["dir"])
|
|
156
263
|
added.append(e["dir"])
|
|
264
|
+
for b in domain:
|
|
265
|
+
if b["dir"] not in present and b["dir"] not in insert:
|
|
266
|
+
insert.append(b["dir"])
|
|
267
|
+
added_domain.append(b["dir"])
|
|
157
268
|
if insert:
|
|
158
269
|
lines[end:end] = insert
|
|
159
270
|
text = "\n".join(lines)
|
|
160
|
-
# Regenerate
|
|
161
|
-
text = _strip_region(text)
|
|
271
|
+
# Regenerate both descriptive regions wholesale (idempotent).
|
|
272
|
+
text = _strip_region(text, MARK_START, MARK_END)
|
|
273
|
+
text = _strip_region(text, ONT_START, ONT_END)
|
|
162
274
|
if owned:
|
|
163
275
|
region = [MARK_START, "## Plugin-owned top-level workspaces", "",
|
|
164
276
|
"A plugin that owns a top-level workspace declares it. The internal",
|
|
@@ -169,9 +281,23 @@ def merge(project_dir, account_dir):
|
|
|
169
281
|
region.append(f"- `{e['dir']}/` — {desc}")
|
|
170
282
|
region.append(MARK_END)
|
|
171
283
|
text = text.rstrip("\n") + "\n\n" + "\n".join(region) + "\n"
|
|
284
|
+
owned_dirs = {e["dir"] for e in owned}
|
|
285
|
+
# A dir already claimed by a plugin (described in the plugin-owned region)
|
|
286
|
+
# is not also described here, so a name collision cannot double-describe.
|
|
287
|
+
domain_region = [b for b in domain if b["dir"] not in owned_dirs]
|
|
288
|
+
if domain_region:
|
|
289
|
+
region = [ONT_START, "## Domain entity buckets (from the graph ontology)", "",
|
|
290
|
+
"One bucket per top-level operator-entry node type of this brand's",
|
|
291
|
+
"business ontology. File a job's artifacts under its entity folder;",
|
|
292
|
+
"the folder may hold a subtree.", ""]
|
|
293
|
+
for b in domain_region:
|
|
294
|
+
region.append(f"- `{b['dir']}/` - one folder per {b['label']} record.")
|
|
295
|
+
region.append(ONT_END)
|
|
296
|
+
text = text.rstrip("\n") + "\n\n" + "\n".join(region) + "\n"
|
|
172
297
|
with open(schema_path, "w") as f:
|
|
173
298
|
f.write(text)
|
|
174
|
-
print(f"brand={brand} added={','.join(added) if added else 'none'}"
|
|
299
|
+
print(f"brand={brand} added={','.join(added) if added else 'none'} "
|
|
300
|
+
f"domainBuckets={','.join(added_domain) if added_domain else 'none'}")
|
|
175
301
|
|
|
176
302
|
|
|
177
303
|
def reconcile(project_dir, account_dir):
|
|
@@ -186,6 +312,13 @@ def reconcile(project_dir, account_dir):
|
|
|
186
312
|
for e in owned:
|
|
187
313
|
is_present = "true" if e["dir"] in present else "false"
|
|
188
314
|
print(f"brand={brand} plugin={e['plugin']} ownedDir={e['dir']} present={is_present}")
|
|
315
|
+
vertical = _read_vertical(project_dir)
|
|
316
|
+
domain = resolve_domain_buckets(project_dir)
|
|
317
|
+
if vertical and not domain:
|
|
318
|
+
print(f"brand={brand} ontologyVertical={vertical} topLevelSection=absent")
|
|
319
|
+
for b in domain:
|
|
320
|
+
is_present = "true" if b["dir"] in present else "false"
|
|
321
|
+
print(f"brand={brand} ontologyEntity={b['label']} bucket={b['dir']} present={is_present}")
|
|
189
322
|
|
|
190
323
|
|
|
191
324
|
def main():
|
|
@@ -254,6 +254,11 @@ if role and cfg.get("role") != role:
|
|
|
254
254
|
cfg["role"] = role; changed = True
|
|
255
255
|
if "managedService" not in cfg:
|
|
256
256
|
cfg["managedService"] = False; changed = True
|
|
257
|
+
# Task 1623 — the client sub-account admins[] is vestigial (Task 999 retired the
|
|
258
|
+
# seats; Task 1596 retired the inbound read). Access is install-wide, so a client
|
|
259
|
+
# never carries admins[]. The house keeps it (its real access surface).
|
|
260
|
+
if role == "client" and "admins" in cfg:
|
|
261
|
+
del cfg["admins"]; changed = True
|
|
257
262
|
if changed:
|
|
258
263
|
with open(path, "w") as f:
|
|
259
264
|
json.dump(cfg, f, indent=2); f.write("\n")
|
|
@@ -22,10 +22,10 @@
|
|
|
22
22
|
#
|
|
23
23
|
# Types: agent-stream (canonical, per-session tool-use/tool-result archive;
|
|
24
24
|
# `system` is kept as a backwards-compatible alias), session, error,
|
|
25
|
-
# heartbeat, public, server, mcp, vnc, chat-attempts
|
|
25
|
+
# heartbeat, public, server, mcp, vnc, mem, chat-attempts
|
|
26
26
|
# For per-session types (agent-stream/system/session/error/public): sessionKey
|
|
27
27
|
# is required in the first mode. For platform-scoped types (server/vnc/
|
|
28
|
-
# heartbeat): the id is ignored. chat-attempts is --tail-only:
|
|
28
|
+
# heartbeat/mem): the id is ignored. chat-attempts is --tail-only:
|
|
29
29
|
# cross-grep of edge.log + server.log for the chat-ingress quartet within
|
|
30
30
|
# the most recent N minutes.
|
|
31
31
|
#
|
|
@@ -57,6 +57,10 @@ INSTALL_DIR_NAME=$(basename "$INSTALL_DIR")
|
|
|
57
57
|
CONFIG_DIR="$HOME/.$INSTALL_DIR_NAME"
|
|
58
58
|
SERVER_LOG="$CONFIG_DIR/logs/server.log"
|
|
59
59
|
VNC_LOG="$CONFIG_DIR/logs/vnc-boot.log"
|
|
60
|
+
# Task 1593 — the standing memory sampler (rss-sampler.sh) writes its persistent
|
|
61
|
+
# log directly under the configDir (persistDir == CONFIG_DIR), NOT under logs/.
|
|
62
|
+
# `--tail mem` surfaces it so the approach-to-OOM diagnostic is one SSH command.
|
|
63
|
+
MEM_LOG="$CONFIG_DIR/rss-sampler.log"
|
|
60
64
|
|
|
61
65
|
# --- Resolve the Claude Code state tree (Task 585) ---
|
|
62
66
|
# rc-spawn per-session evidence is NOT under data/accounts/<id>/logs/ — it is
|
|
@@ -148,7 +152,7 @@ is_per_conversation_type() {
|
|
|
148
152
|
}
|
|
149
153
|
|
|
150
154
|
SEARCH_TYPES="agent-stream error session public server mcp vnc"
|
|
151
|
-
VALID_TYPES="agent-stream, system, session, error, heartbeat, public, server, mcp, vnc, chat-attempts"
|
|
155
|
+
VALID_TYPES="agent-stream, system, session, error, heartbeat, public, server, mcp, vnc, mem, chat-attempts"
|
|
152
156
|
|
|
153
157
|
# chat-attempts cross-grep prefixes. One row per chat-ingress
|
|
154
158
|
# checkpoint emitted across edge.log + server.log: edge-side inbound/outcome,
|
|
@@ -188,7 +192,7 @@ prefix), and prints the parent transcript merged with every subagent
|
|
|
188
192
|
transcript, flagging subagent is_error results inline. An explicit <type>
|
|
189
193
|
keeps the legacy per-account stream-log read.
|
|
190
194
|
Per-session stream-log types (agent-stream, system, session, error, public)
|
|
191
|
-
require sessionKey; platform-scoped types (server, vnc, heartbeat) ignore the id.
|
|
195
|
+
require sessionKey; platform-scoped types (server, vnc, heartbeat, mem) ignore the id.
|
|
192
196
|
chat-attempts is --tail-only — it aggregates the four ingress checkpoints
|
|
193
197
|
([edge-admin] inbound, [admin-auth] outcome, [chat-route] entered,
|
|
194
198
|
[edge-admin] outcome) into one timeline.
|
|
@@ -199,7 +203,7 @@ EOF
|
|
|
199
203
|
validate_type() {
|
|
200
204
|
local t="$1"
|
|
201
205
|
case "$t" in
|
|
202
|
-
agent-stream|system|session|error|heartbeat|public|server|mcp|vnc|chat-attempts) return 0 ;;
|
|
206
|
+
agent-stream|system|session|error|heartbeat|public|server|mcp|vnc|mem|chat-attempts) return 0 ;;
|
|
203
207
|
*) echo "Error: invalid type '$t'. Valid types: $VALID_TYPES" >&2; exit 2 ;;
|
|
204
208
|
esac
|
|
205
209
|
}
|
|
@@ -336,7 +340,7 @@ per_conversation_mode() {
|
|
|
336
340
|
|
|
337
341
|
# Platform-scoped types shortcut to their fixed files regardless of id.
|
|
338
342
|
case "$filter_type" in
|
|
339
|
-
server|vnc|heartbeat|mcp)
|
|
343
|
+
server|vnc|heartbeat|mcp|mem)
|
|
340
344
|
echo "# note: type=$filter_type is platform-scoped; sessionKey ignored — showing fixed file" >&2
|
|
341
345
|
tail_mode "$filter_type" "50"
|
|
342
346
|
return
|
|
@@ -566,6 +570,20 @@ tail_mode() {
|
|
|
566
570
|
exit 0
|
|
567
571
|
fi
|
|
568
572
|
|
|
573
|
+
# Task 1593 — the standing memory sampler log. Platform-scoped fixed file
|
|
574
|
+
# (like server/vnc): the sessionKey is ignored. Filter with `grep op=mem-...`
|
|
575
|
+
# per the diagnostic path in troubleshooting.md.
|
|
576
|
+
if [[ "$log_type" == "mem" ]]; then
|
|
577
|
+
if [[ ! -f "$MEM_LOG" ]]; then
|
|
578
|
+
echo "No mem sampler log found at $MEM_LOG"
|
|
579
|
+
exit 1
|
|
580
|
+
fi
|
|
581
|
+
echo "# rss-sampler.log (mem)"
|
|
582
|
+
echo ""
|
|
583
|
+
tail -n "$lines" "$MEM_LOG"
|
|
584
|
+
exit 0
|
|
585
|
+
fi
|
|
586
|
+
|
|
569
587
|
# Reached only for per-session stream-log types (agent-stream/error/session/
|
|
570
588
|
# public/mcp); these live under the per-account log dirs.
|
|
571
589
|
require_account_logs
|
|
@@ -148,9 +148,51 @@ case_ambiguous() {
|
|
|
148
148
|
return 0
|
|
149
149
|
}
|
|
150
150
|
|
|
151
|
+
# --- Case 4 (Task 1593): --tail mem present → exit 0, returns sampler lines ---
|
|
152
|
+
case_mem_present() {
|
|
153
|
+
local root="/tmp/maxy-logs-read-test-4-$$"
|
|
154
|
+
local script
|
|
155
|
+
script=$(setup_install_tree "$root")
|
|
156
|
+
# The mem log lives directly under the configDir (persistDir == CONFIG_DIR),
|
|
157
|
+
# NOT under logs/ — mirror the installer's join(persistDir, "rss-sampler.log").
|
|
158
|
+
local mem_log="$HOME/.$(basename "$root")/rss-sampler.log"
|
|
159
|
+
{
|
|
160
|
+
echo "[mem-sampler] op=mem-sample ts=2026-07-13T10:00:00Z MemAvailable=900 MemFree=400 SwapUsed=100 ptys=2048 neo4j=900 brand=900 edge=800 vnc=0"
|
|
161
|
+
echo "[mem-sampler] op=mem-pressure MemAvailable=600 threshold=800 topSlice=ptys topRss=2048"
|
|
162
|
+
} > "$mem_log"
|
|
163
|
+
|
|
164
|
+
local stdout rc=0
|
|
165
|
+
stdout=$("$script" --tail mem 10 2>/dev/null) || rc=$?
|
|
166
|
+
|
|
167
|
+
cleanup_install_tree "$root"
|
|
168
|
+
|
|
169
|
+
if [[ $rc -ne 0 ]]; then echo " expected exit 0, got $rc"; return 1; fi
|
|
170
|
+
if [[ "$stdout" != *"op=mem-sample"* ]]; then echo " stdout missing op=mem-sample"; return 1; fi
|
|
171
|
+
if [[ "$stdout" != *"op=mem-pressure"* ]]; then echo " stdout missing op=mem-pressure"; return 1; fi
|
|
172
|
+
return 0
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
# --- Case 5 (Task 1593): --tail mem absent → exit 1 with a clear message ---
|
|
176
|
+
case_mem_absent() {
|
|
177
|
+
local root="/tmp/maxy-logs-read-test-5-$$"
|
|
178
|
+
local script
|
|
179
|
+
script=$(setup_install_tree "$root")
|
|
180
|
+
|
|
181
|
+
local stdout rc=0
|
|
182
|
+
stdout=$("$script" --tail mem 10 2>/dev/null) || rc=$?
|
|
183
|
+
|
|
184
|
+
cleanup_install_tree "$root"
|
|
185
|
+
|
|
186
|
+
if [[ $rc -ne 1 ]]; then echo " expected exit 1, got $rc"; return 1; fi
|
|
187
|
+
if [[ "$stdout" != *"No mem sampler log found"* ]]; then echo " missing not-found message: $stdout"; return 1; fi
|
|
188
|
+
return 0
|
|
189
|
+
}
|
|
190
|
+
|
|
151
191
|
run_case "sessionKey file present → exit 0" case_file_present
|
|
152
192
|
run_case "no file → exit 1 + missing-on-resolve" case_missing
|
|
153
193
|
run_case "ambiguous prefix → exit 1, refuses to pick" case_ambiguous
|
|
194
|
+
run_case "--tail mem present → exit 0 + sampler lines" case_mem_present
|
|
195
|
+
run_case "--tail mem absent → exit 1 + not-found message" case_mem_absent
|
|
154
196
|
|
|
155
197
|
echo ""
|
|
156
198
|
echo "================================================"
|