@rubytech/create-maxy-code 0.1.505 → 0.1.506

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 (75) hide show
  1. package/package.json +1 -1
  2. package/payload/platform/lib/account-schema-regions/dist/index.d.ts +60 -0
  3. package/payload/platform/lib/account-schema-regions/dist/index.d.ts.map +1 -0
  4. package/payload/platform/lib/account-schema-regions/dist/index.js +101 -0
  5. package/payload/platform/lib/account-schema-regions/dist/index.js.map +1 -0
  6. package/payload/platform/lib/account-schema-regions/package.json +7 -0
  7. package/payload/platform/lib/account-schema-regions/src/index.ts +121 -0
  8. package/payload/platform/lib/account-schema-regions/tsconfig.json +9 -0
  9. package/payload/platform/package.json +3 -3
  10. package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +59 -17
  11. package/payload/platform/plugins/admin/skills/public-agent-manager/SKILL.md +3 -3
  12. package/payload/platform/plugins/admin/skills/whats-new/SKILL.md +6 -0
  13. package/payload/platform/plugins/docs/references/admin-ui.md +58 -16
  14. package/payload/platform/scripts/check-no-esm-require.mjs +5 -0
  15. package/payload/platform/services/claude-session-manager/dist/account-dir-schema-reconcile.d.ts +1 -1
  16. package/payload/platform/services/claude-session-manager/dist/account-dir-schema-reconcile.d.ts.map +1 -1
  17. package/payload/platform/services/claude-session-manager/dist/account-dir-schema-reconcile.js +50 -3
  18. package/payload/platform/services/claude-session-manager/dist/account-dir-schema-reconcile.js.map +1 -1
  19. package/payload/server/{chunk-HN4HFTXE.js → chunk-5ADBNEJA.js} +1410 -1409
  20. package/payload/server/maxy-edge.js +1 -1
  21. package/payload/server/public/activity.html +5 -5
  22. package/payload/server/public/agents.html +4 -4
  23. package/payload/server/public/assets/{AdminLoginScreens-DeWt3H7l.js → AdminLoginScreens-D5KOQA8S.js} +1 -1
  24. package/payload/server/public/assets/{AdminShell-u32d8puE.js → AdminShell-C8wazpnz.js} +2 -2
  25. package/payload/server/public/assets/{activity-DjP45-DD.js → activity-CFqkDhTs.js} +1 -1
  26. package/payload/server/public/assets/admin-B-EGUWS4.js +1 -0
  27. package/payload/server/public/assets/agents-DPulfmrd.js +1 -0
  28. package/payload/server/public/assets/{browser-ULvl_K5x.js → browser-T_5xbnJh.js} +1 -1
  29. package/payload/server/public/assets/{calendar-CGMrT6WZ.js → calendar-CKjuiG8c.js} +1 -1
  30. package/payload/server/public/assets/chat-DWLYAA70.js +1 -0
  31. package/payload/server/public/assets/chevron-left-DBMCTKlT.js +1 -0
  32. package/payload/server/public/assets/chevron-right-SBAr6zzc.js +1 -0
  33. package/payload/server/public/assets/clock-kkzt7Scf.js +1 -0
  34. package/payload/server/public/assets/data-CV8N9qcy.js +1 -0
  35. package/payload/server/public/assets/{file-text-C5K6DMcK.js → file-text-c6cA4Evd.js} +1 -1
  36. package/payload/server/public/assets/{graph-CO6e1bgD.js → graph-DIk-o45J.js} +2 -2
  37. package/payload/server/public/assets/{graph-labels-iliQjlfE.js → graph-labels-5bjxhC_p.js} +1 -1
  38. package/payload/server/public/assets/{maximize-2-D-64XK6V.js → maximize-2-BCQkFb1_.js} +1 -1
  39. package/payload/server/public/assets/{operator-DyXgogJ1.js → operator-0UnNo2ms.js} +1 -1
  40. package/payload/server/public/assets/page-HVtUfub2.js +32 -0
  41. package/payload/server/public/assets/page-JJa2xdzf.js +1 -0
  42. package/payload/server/public/assets/{public-B9NVDBxW.js → public-CKEYyIPn.js} +1 -1
  43. package/payload/server/public/assets/{rotate-ccw-P35h7ogS.js → rotate-ccw-dTpO5dhL.js} +1 -1
  44. package/payload/server/public/assets/{routines-BeiynLF7.js → routines-C6P1-Dto.js} +1 -1
  45. package/payload/server/public/assets/{skills-C5H7ohLp.js → skills-ClrpVqlz.js} +1 -1
  46. package/payload/server/public/assets/tasks-C8SNhSFC.js +1 -0
  47. package/payload/server/public/assets/{time-entry-format-CDhHCMrW.js → time-entry-format-CCO6N6XZ.js} +1 -1
  48. package/payload/server/public/assets/{triangle-alert-OgY71rDV.js → triangle-alert-CHjdKpag.js} +1 -1
  49. package/payload/server/public/assets/{useCopyFeedback-jOGmWwa3.js → useCopyFeedback-BTUu3-l8.js} +1 -1
  50. package/payload/server/public/assets/{useSubAccountSwitcher-6AFuRawP.js → useSubAccountSwitcher-DSW9vDi2.js} +1 -1
  51. package/payload/server/public/assets/{useSubAccountSwitcher-AFSwtthL.css → useSubAccountSwitcher-DyrH8DA8.css} +1 -1
  52. package/payload/server/public/assets/{useVoiceRecorder-CnE3fhfV.js → useVoiceRecorder-BeNqxmm2.js} +1 -1
  53. package/payload/server/public/assets/{wrench-B1hsCj0R.js → wrench-CeNnc7oO.js} +1 -1
  54. package/payload/server/public/browser.html +4 -4
  55. package/payload/server/public/calendar.html +7 -7
  56. package/payload/server/public/chat.html +13 -13
  57. package/payload/server/public/data.html +11 -11
  58. package/payload/server/public/graph.html +9 -9
  59. package/payload/server/public/index.html +14 -14
  60. package/payload/server/public/operator.html +14 -14
  61. package/payload/server/public/public.html +13 -13
  62. package/payload/server/public/routines.html +6 -6
  63. package/payload/server/public/skills.html +5 -5
  64. package/payload/server/public/tasks.html +6 -6
  65. package/payload/server/server.js +1329 -826
  66. package/payload/server/public/assets/admin-Bo-Y6Vjx.js +0 -1
  67. package/payload/server/public/assets/agents-C80aCXd8.js +0 -1
  68. package/payload/server/public/assets/chat-DnWXaWD4.js +0 -1
  69. package/payload/server/public/assets/chevron-left-BB5Iljw-.js +0 -1
  70. package/payload/server/public/assets/chevron-right-DijU96_j.js +0 -1
  71. package/payload/server/public/assets/clock-DsmnhhJh.js +0 -1
  72. package/payload/server/public/assets/data-JPlhOLGC.js +0 -1
  73. package/payload/server/public/assets/page-DrbiAFS-.js +0 -32
  74. package/payload/server/public/assets/page-WeM2ry61.js +0 -1
  75. package/payload/server/public/assets/tasks-CI74_b6H.js +0 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rubytech/create-maxy-code",
3
- "version": "0.1.505",
3
+ "version": "0.1.506",
4
4
  "description": "Install Maxy — AI for Productive People",
5
5
  "bin": {
6
6
  "create-maxy-code": "./dist/index.js"
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Parser for the three machine-readable regions of an account's SCHEMA.md.
3
+ *
4
+ * Shared because two separate builds need the same answer: the admin UI server
5
+ * (platform/ui/server/lib/account-root-groups.ts, which groups the /data root
6
+ * into home/system) and the session manager's standing schema reconcile
7
+ * (platform/services/claude-session-manager/src/account-dir-schema-reconcile.ts,
8
+ * which audits declared-versus-on-disk parity). A third reader,
9
+ * platform/plugins/cloudflare/bin/schema-exposed-dirs.mjs, ships in a separate
10
+ * deploy tree and keeps its own copy; a parity test asserts the two agree.
11
+ *
12
+ * The regions are written by platform/scripts/lib/account-schema-owned-dirs.py
13
+ * merge(). Their separators differ:
14
+ *
15
+ * ontology roots: - `jobs/` - one folder per Job record. (ASCII hyphen)
16
+ * plugin-owned: - `pages/` — Owned by the … plugin. (em dash)
17
+ *
18
+ * .docs/data-portal-folder-index.md records that parsing the ontology region on
19
+ * the wrong separator matches nothing and reads as "this account has no
20
+ * deliverables". This parser sidesteps that failure entirely by anchoring on the
21
+ * backticked `dir/` head rather than the separator, and a test pins the
22
+ * behaviour so a future edit cannot reintroduce separator sensitivity.
23
+ */
24
+ /**
25
+ * Operator-data buckets from the shipped template
26
+ * (platform/templates/account-schema/SCHEMA.md). They carry no ontology row
27
+ * because they are not entity buckets, but they hold the operator's own data,
28
+ * so they are home on every brand. On a vertical with no `## Top-level node
29
+ * types` table (schema-knowledge-work) they are the whole of home.
30
+ *
31
+ * Lives here rather than in either consumer because both the /data page and the
32
+ * standing reconcile audit must agree on the same set; a hand-synced copy in
33
+ * each build is precisely the drift this shared module exists to prevent.
34
+ */
35
+ export declare const HOME_FIXED: readonly string[];
36
+ export interface SchemaRegions {
37
+ /** Root bucket dirs from the ontology region, in table order, deduped. */
38
+ ontologyRoots: string[];
39
+ /** Dirs from the plugin-owned region, in file order, deduped. */
40
+ pluginOwned: string[];
41
+ /** Entries of the ```allowed-top-level fence, in file order. */
42
+ allowedTopLevel: string[];
43
+ /**
44
+ * True when the allowed-top-level fence was found with at least one entry.
45
+ * That fence is present in every valid account SCHEMA.md (it ships in
46
+ * platform/templates/account-schema/SCHEMA.md), so its absence means the file
47
+ * is missing or not an account schema. An EMPTY ontology region is NOT a
48
+ * parse failure: a vertical with no `## Top-level node types` table (for
49
+ * example schema-knowledge-work) legitimately projects no buckets.
50
+ */
51
+ parsed: boolean;
52
+ /**
53
+ * Why `parsed` is false, for the caller's log line. The two states need
54
+ * different remediation — provision the account versus regenerate its schema
55
+ * — so a single `status=parse-failed` cannot be acted on.
56
+ */
57
+ reason: 'ok' | 'schema-absent' | 'fence-absent';
58
+ }
59
+ export declare function parseSchemaRegions(schemaMd: string | null): SchemaRegions;
60
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH;;;;;;;;;;GAUG;AACH,eAAO,MAAM,UAAU,EAAE,SAAS,MAAM,EAAyD,CAAA;AAiBjG,MAAM,WAAW,aAAa;IAC5B,0EAA0E;IAC1E,aAAa,EAAE,MAAM,EAAE,CAAA;IACvB,iEAAiE;IACjE,WAAW,EAAE,MAAM,EAAE,CAAA;IACrB,gEAAgE;IAChE,eAAe,EAAE,MAAM,EAAE,CAAA;IACzB;;;;;;;OAOG;IACH,MAAM,EAAE,OAAO,CAAA;IACf;;;;OAIG;IACH,MAAM,EAAE,IAAI,GAAG,eAAe,GAAG,cAAc,CAAA;CAChD;AAiCD,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,aAAa,CAazE"}
@@ -0,0 +1,101 @@
1
+ "use strict";
2
+ /**
3
+ * Parser for the three machine-readable regions of an account's SCHEMA.md.
4
+ *
5
+ * Shared because two separate builds need the same answer: the admin UI server
6
+ * (platform/ui/server/lib/account-root-groups.ts, which groups the /data root
7
+ * into home/system) and the session manager's standing schema reconcile
8
+ * (platform/services/claude-session-manager/src/account-dir-schema-reconcile.ts,
9
+ * which audits declared-versus-on-disk parity). A third reader,
10
+ * platform/plugins/cloudflare/bin/schema-exposed-dirs.mjs, ships in a separate
11
+ * deploy tree and keeps its own copy; a parity test asserts the two agree.
12
+ *
13
+ * The regions are written by platform/scripts/lib/account-schema-owned-dirs.py
14
+ * merge(). Their separators differ:
15
+ *
16
+ * ontology roots: - `jobs/` - one folder per Job record. (ASCII hyphen)
17
+ * plugin-owned: - `pages/` — Owned by the … plugin. (em dash)
18
+ *
19
+ * .docs/data-portal-folder-index.md records that parsing the ontology region on
20
+ * the wrong separator matches nothing and reads as "this account has no
21
+ * deliverables". This parser sidesteps that failure entirely by anchoring on the
22
+ * backticked `dir/` head rather than the separator, and a test pins the
23
+ * behaviour so a future edit cannot reintroduce separator sensitivity.
24
+ */
25
+ Object.defineProperty(exports, "__esModule", { value: true });
26
+ exports.HOME_FIXED = void 0;
27
+ exports.parseSchemaRegions = parseSchemaRegions;
28
+ /**
29
+ * Operator-data buckets from the shipped template
30
+ * (platform/templates/account-schema/SCHEMA.md). They carry no ontology row
31
+ * because they are not entity buckets, but they hold the operator's own data,
32
+ * so they are home on every brand. On a vertical with no `## Top-level node
33
+ * types` table (schema-knowledge-work) they are the whole of home.
34
+ *
35
+ * Lives here rather than in either consumer because both the /data page and the
36
+ * standing reconcile audit must agree on the same set; a hand-synced copy in
37
+ * each build is precisely the drift this shared module exists to prevent.
38
+ */
39
+ exports.HOME_FIXED = Object.freeze(['projects', 'contacts', 'documents']);
40
+ const ONT_START = '<!-- ontology-buckets:start -->';
41
+ const ONT_END = '<!-- ontology-buckets:end -->';
42
+ const PLUGIN_START = '<!-- plugin-owned-dirs:start -->';
43
+ const PLUGIN_END = '<!-- plugin-owned-dirs:end -->';
44
+ const ALLOWED_FENCE = '```allowed-top-level';
45
+ /**
46
+ * A region line declaring a directory: `- \`<dir>/\` …`. The trailing slash
47
+ * inside the backticks is what excludes a nested-entity line
48
+ * (`- \`Quote\` records live under \`jobs/\` …`), whose first backtick span is a
49
+ * bare label. Anything after the closing backtick is ignored, so both
50
+ * separators parse.
51
+ */
52
+ const DIR_LINE = /^-\s+`([^`/]+)\/`/;
53
+ function regionLines(text, start, end) {
54
+ const from = text.indexOf(start);
55
+ if (from === -1)
56
+ return [];
57
+ const to = text.indexOf(end, from);
58
+ if (to === -1)
59
+ return [];
60
+ return text.slice(from + start.length, to).split('\n');
61
+ }
62
+ function dirsIn(lines) {
63
+ const out = [];
64
+ for (const line of lines) {
65
+ const m = DIR_LINE.exec(line.trim());
66
+ if (m && !out.includes(m[1]))
67
+ out.push(m[1]);
68
+ }
69
+ return out;
70
+ }
71
+ function fenceEntries(text) {
72
+ const from = text.indexOf(ALLOWED_FENCE);
73
+ if (from === -1)
74
+ return [];
75
+ const bodyStart = text.indexOf('\n', from);
76
+ if (bodyStart === -1)
77
+ return [];
78
+ const to = text.indexOf('```', bodyStart);
79
+ if (to === -1)
80
+ return [];
81
+ return text
82
+ .slice(bodyStart + 1, to)
83
+ .split('\n')
84
+ .map((s) => s.trim())
85
+ .filter((s) => s.length > 0);
86
+ }
87
+ function parseSchemaRegions(schemaMd) {
88
+ if (schemaMd === null || schemaMd.length === 0) {
89
+ return { ontologyRoots: [], pluginOwned: [], allowedTopLevel: [], parsed: false, reason: 'schema-absent' };
90
+ }
91
+ const allowedTopLevel = fenceEntries(schemaMd);
92
+ const parsed = allowedTopLevel.length > 0;
93
+ return {
94
+ ontologyRoots: dirsIn(regionLines(schemaMd, ONT_START, ONT_END)),
95
+ pluginOwned: dirsIn(regionLines(schemaMd, PLUGIN_START, PLUGIN_END)),
96
+ allowedTopLevel,
97
+ parsed,
98
+ reason: parsed ? 'ok' : 'fence-absent',
99
+ };
100
+ }
101
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;;;AAqFH,gDAaC;AAhGD;;;;;;;;;;GAUG;AACU,QAAA,UAAU,GAAsB,MAAM,CAAC,MAAM,CAAC,CAAC,UAAU,EAAE,UAAU,EAAE,WAAW,CAAC,CAAC,CAAA;AAEjG,MAAM,SAAS,GAAG,iCAAiC,CAAA;AACnD,MAAM,OAAO,GAAG,+BAA+B,CAAA;AAC/C,MAAM,YAAY,GAAG,kCAAkC,CAAA;AACvD,MAAM,UAAU,GAAG,gCAAgC,CAAA;AACnD,MAAM,aAAa,GAAG,sBAAsB,CAAA;AAE5C;;;;;;GAMG;AACH,MAAM,QAAQ,GAAG,mBAAmB,CAAA;AA0BpC,SAAS,WAAW,CAAC,IAAY,EAAE,KAAa,EAAE,GAAW;IAC3D,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAA;IAChC,IAAI,IAAI,KAAK,CAAC,CAAC;QAAE,OAAO,EAAE,CAAA;IAC1B,MAAM,EAAE,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,IAAI,CAAC,CAAA;IAClC,IAAI,EAAE,KAAK,CAAC,CAAC;QAAE,OAAO,EAAE,CAAA;IACxB,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,GAAG,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;AACxD,CAAC;AAED,SAAS,MAAM,CAAC,KAAe;IAC7B,MAAM,GAAG,GAAa,EAAE,CAAA;IACxB,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAA;QACpC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YAAE,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;IAC9C,CAAC;IACD,OAAO,GAAG,CAAA;AACZ,CAAC;AAED,SAAS,YAAY,CAAC,IAAY;IAChC,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,CAAA;IACxC,IAAI,IAAI,KAAK,CAAC,CAAC;QAAE,OAAO,EAAE,CAAA;IAC1B,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,CAAA;IAC1C,IAAI,SAAS,KAAK,CAAC,CAAC;QAAE,OAAO,EAAE,CAAA;IAC/B,MAAM,EAAE,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,SAAS,CAAC,CAAA;IACzC,IAAI,EAAE,KAAK,CAAC,CAAC;QAAE,OAAO,EAAE,CAAA;IACxB,OAAO,IAAI;SACR,KAAK,CAAC,SAAS,GAAG,CAAC,EAAE,EAAE,CAAC;SACxB,KAAK,CAAC,IAAI,CAAC;SACX,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;SACpB,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAA;AAChC,CAAC;AAED,SAAgB,kBAAkB,CAAC,QAAuB;IACxD,IAAI,QAAQ,KAAK,IAAI,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC/C,OAAO,EAAE,aAAa,EAAE,EAAE,EAAE,WAAW,EAAE,EAAE,EAAE,eAAe,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,eAAe,EAAE,CAAA;IAC5G,CAAC;IACD,MAAM,eAAe,GAAG,YAAY,CAAC,QAAQ,CAAC,CAAA;IAC9C,MAAM,MAAM,GAAG,eAAe,CAAC,MAAM,GAAG,CAAC,CAAA;IACzC,OAAO;QACL,aAAa,EAAE,MAAM,CAAC,WAAW,CAAC,QAAQ,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC;QAChE,WAAW,EAAE,MAAM,CAAC,WAAW,CAAC,QAAQ,EAAE,YAAY,EAAE,UAAU,CAAC,CAAC;QACpE,eAAe;QACf,MAAM;QACN,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,cAAc;KACvC,CAAA;AACH,CAAC"}
@@ -0,0 +1,7 @@
1
+ {
2
+ "name": "@maxy/account-schema-regions",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "type": "commonjs",
6
+ "main": "dist/index.js"
7
+ }
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Parser for the three machine-readable regions of an account's SCHEMA.md.
3
+ *
4
+ * Shared because two separate builds need the same answer: the admin UI server
5
+ * (platform/ui/server/lib/account-root-groups.ts, which groups the /data root
6
+ * into home/system) and the session manager's standing schema reconcile
7
+ * (platform/services/claude-session-manager/src/account-dir-schema-reconcile.ts,
8
+ * which audits declared-versus-on-disk parity). A third reader,
9
+ * platform/plugins/cloudflare/bin/schema-exposed-dirs.mjs, ships in a separate
10
+ * deploy tree and keeps its own copy; a parity test asserts the two agree.
11
+ *
12
+ * The regions are written by platform/scripts/lib/account-schema-owned-dirs.py
13
+ * merge(). Their separators differ:
14
+ *
15
+ * ontology roots: - `jobs/` - one folder per Job record. (ASCII hyphen)
16
+ * plugin-owned: - `pages/` — Owned by the … plugin. (em dash)
17
+ *
18
+ * .docs/data-portal-folder-index.md records that parsing the ontology region on
19
+ * the wrong separator matches nothing and reads as "this account has no
20
+ * deliverables". This parser sidesteps that failure entirely by anchoring on the
21
+ * backticked `dir/` head rather than the separator, and a test pins the
22
+ * behaviour so a future edit cannot reintroduce separator sensitivity.
23
+ */
24
+
25
+ /**
26
+ * Operator-data buckets from the shipped template
27
+ * (platform/templates/account-schema/SCHEMA.md). They carry no ontology row
28
+ * because they are not entity buckets, but they hold the operator's own data,
29
+ * so they are home on every brand. On a vertical with no `## Top-level node
30
+ * types` table (schema-knowledge-work) they are the whole of home.
31
+ *
32
+ * Lives here rather than in either consumer because both the /data page and the
33
+ * standing reconcile audit must agree on the same set; a hand-synced copy in
34
+ * each build is precisely the drift this shared module exists to prevent.
35
+ */
36
+ export const HOME_FIXED: readonly string[] = Object.freeze(['projects', 'contacts', 'documents'])
37
+
38
+ const ONT_START = '<!-- ontology-buckets:start -->'
39
+ const ONT_END = '<!-- ontology-buckets:end -->'
40
+ const PLUGIN_START = '<!-- plugin-owned-dirs:start -->'
41
+ const PLUGIN_END = '<!-- plugin-owned-dirs:end -->'
42
+ const ALLOWED_FENCE = '```allowed-top-level'
43
+
44
+ /**
45
+ * A region line declaring a directory: `- \`<dir>/\` …`. The trailing slash
46
+ * inside the backticks is what excludes a nested-entity line
47
+ * (`- \`Quote\` records live under \`jobs/\` …`), whose first backtick span is a
48
+ * bare label. Anything after the closing backtick is ignored, so both
49
+ * separators parse.
50
+ */
51
+ const DIR_LINE = /^-\s+`([^`/]+)\/`/
52
+
53
+ export interface SchemaRegions {
54
+ /** Root bucket dirs from the ontology region, in table order, deduped. */
55
+ ontologyRoots: string[]
56
+ /** Dirs from the plugin-owned region, in file order, deduped. */
57
+ pluginOwned: string[]
58
+ /** Entries of the ```allowed-top-level fence, in file order. */
59
+ allowedTopLevel: string[]
60
+ /**
61
+ * True when the allowed-top-level fence was found with at least one entry.
62
+ * That fence is present in every valid account SCHEMA.md (it ships in
63
+ * platform/templates/account-schema/SCHEMA.md), so its absence means the file
64
+ * is missing or not an account schema. An EMPTY ontology region is NOT a
65
+ * parse failure: a vertical with no `## Top-level node types` table (for
66
+ * example schema-knowledge-work) legitimately projects no buckets.
67
+ */
68
+ parsed: boolean
69
+ /**
70
+ * Why `parsed` is false, for the caller's log line. The two states need
71
+ * different remediation — provision the account versus regenerate its schema
72
+ * — so a single `status=parse-failed` cannot be acted on.
73
+ */
74
+ reason: 'ok' | 'schema-absent' | 'fence-absent'
75
+ }
76
+
77
+ function regionLines(text: string, start: string, end: string): string[] {
78
+ const from = text.indexOf(start)
79
+ if (from === -1) return []
80
+ const to = text.indexOf(end, from)
81
+ if (to === -1) return []
82
+ return text.slice(from + start.length, to).split('\n')
83
+ }
84
+
85
+ function dirsIn(lines: string[]): string[] {
86
+ const out: string[] = []
87
+ for (const line of lines) {
88
+ const m = DIR_LINE.exec(line.trim())
89
+ if (m && !out.includes(m[1])) out.push(m[1])
90
+ }
91
+ return out
92
+ }
93
+
94
+ function fenceEntries(text: string): string[] {
95
+ const from = text.indexOf(ALLOWED_FENCE)
96
+ if (from === -1) return []
97
+ const bodyStart = text.indexOf('\n', from)
98
+ if (bodyStart === -1) return []
99
+ const to = text.indexOf('```', bodyStart)
100
+ if (to === -1) return []
101
+ return text
102
+ .slice(bodyStart + 1, to)
103
+ .split('\n')
104
+ .map((s) => s.trim())
105
+ .filter((s) => s.length > 0)
106
+ }
107
+
108
+ export function parseSchemaRegions(schemaMd: string | null): SchemaRegions {
109
+ if (schemaMd === null || schemaMd.length === 0) {
110
+ return { ontologyRoots: [], pluginOwned: [], allowedTopLevel: [], parsed: false, reason: 'schema-absent' }
111
+ }
112
+ const allowedTopLevel = fenceEntries(schemaMd)
113
+ const parsed = allowedTopLevel.length > 0
114
+ return {
115
+ ontologyRoots: dirsIn(regionLines(schemaMd, ONT_START, ONT_END)),
116
+ pluginOwned: dirsIn(regionLines(schemaMd, PLUGIN_START, PLUGIN_END)),
117
+ allowedTopLevel,
118
+ parsed,
119
+ reason: parsed ? 'ok' : 'fence-absent',
120
+ }
121
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "outDir": "dist",
5
+ "rootDir": "src"
6
+ },
7
+ "include": ["src"],
8
+ "exclude": ["src/**/*.test.ts"]
9
+ }
@@ -6,8 +6,8 @@
6
6
  "services/*"
7
7
  ],
8
8
  "scripts": {
9
- "build": "tsc -p lib/models/tsconfig.json && tsc -p lib/mcp-spawn-tee/tsconfig.json && tsc -p lib/mcp-lifeline/tsconfig.json && tsc -p lib/mcp-eager/tsconfig.json && tsc -p lib/account-enumeration/tsconfig.json && tsc -p lib/account-scope/tsconfig.json && tsc -p lib/graph-write/tsconfig.json && tsc -p lib/ledger-core/tsconfig.json && tsc -p lib/embed-client/tsconfig.json && tsc -p lib/graph-mcp/tsconfig.json && tsc -p lib/graph-trash/tsconfig.json && tsc -p lib/active-rules/tsconfig.json && tsc -p lib/admin-conversation-purge/tsconfig.json && tsc -p lib/graph-search/tsconfig.json && tsc -p lib/graph-style/tsconfig.json && tsc -p lib/device-url/tsconfig.json && tsc -p lib/brand-templating/tsconfig.json && tsc -p lib/entitlement/tsconfig.json && tsc -p lib/task-secrets/tsconfig.json && tsc -p lib/admins-write/tsconfig.json && tsc -p lib/admin-access-password/tsconfig.json && tsc -p lib/persistent-components/tsconfig.json && tsc -p lib/require-port-env/tsconfig.json && tsc -p lib/aeo-llms-txt-writer/tsconfig.json && tsc -p lib/obsidian-parser/tsconfig.json && tsc -p lib/storage-broker/tsconfig.json && tsc -p services/claude-session-manager/tsconfig.json && tsc -p services/whatsapp-channel/tsconfig.json && tsc -p services/webchat-channel/tsconfig.json && tsc -p services/telegram-channel/tsconfig.json && NODE_OPTIONS='--max-old-space-size=8192' tsc -b plugins/*/mcp/tsconfig.json",
10
- "build:lib": "tsc -p lib/models/tsconfig.json && tsc -p lib/mcp-spawn-tee/tsconfig.json && tsc -p lib/mcp-lifeline/tsconfig.json && tsc -p lib/mcp-eager/tsconfig.json && tsc -p lib/account-enumeration/tsconfig.json && tsc -p lib/account-scope/tsconfig.json && tsc -p lib/graph-write/tsconfig.json && tsc -p lib/ledger-core/tsconfig.json && tsc -p lib/embed-client/tsconfig.json && tsc -p lib/graph-mcp/tsconfig.json && tsc -p lib/graph-trash/tsconfig.json && tsc -p lib/active-rules/tsconfig.json && tsc -p lib/admin-conversation-purge/tsconfig.json && tsc -p lib/graph-search/tsconfig.json && tsc -p lib/graph-style/tsconfig.json && tsc -p lib/device-url/tsconfig.json && tsc -p lib/brand-templating/tsconfig.json && tsc -p lib/entitlement/tsconfig.json && tsc -p lib/task-secrets/tsconfig.json && tsc -p lib/admins-write/tsconfig.json && tsc -p lib/admin-access-password/tsconfig.json && tsc -p lib/persistent-components/tsconfig.json && tsc -p lib/require-port-env/tsconfig.json && tsc -p lib/aeo-llms-txt-writer/tsconfig.json && tsc -p lib/obsidian-parser/tsconfig.json && tsc -p lib/storage-broker/tsconfig.json",
9
+ "build": "tsc -p lib/account-schema-regions/tsconfig.json && tsc -p lib/models/tsconfig.json && tsc -p lib/mcp-spawn-tee/tsconfig.json && tsc -p lib/mcp-lifeline/tsconfig.json && tsc -p lib/mcp-eager/tsconfig.json && tsc -p lib/account-enumeration/tsconfig.json && tsc -p lib/account-scope/tsconfig.json && tsc -p lib/graph-write/tsconfig.json && tsc -p lib/ledger-core/tsconfig.json && tsc -p lib/embed-client/tsconfig.json && tsc -p lib/graph-mcp/tsconfig.json && tsc -p lib/graph-trash/tsconfig.json && tsc -p lib/active-rules/tsconfig.json && tsc -p lib/admin-conversation-purge/tsconfig.json && tsc -p lib/graph-search/tsconfig.json && tsc -p lib/graph-style/tsconfig.json && tsc -p lib/device-url/tsconfig.json && tsc -p lib/brand-templating/tsconfig.json && tsc -p lib/entitlement/tsconfig.json && tsc -p lib/task-secrets/tsconfig.json && tsc -p lib/admins-write/tsconfig.json && tsc -p lib/admin-access-password/tsconfig.json && tsc -p lib/persistent-components/tsconfig.json && tsc -p lib/require-port-env/tsconfig.json && tsc -p lib/aeo-llms-txt-writer/tsconfig.json && tsc -p lib/obsidian-parser/tsconfig.json && tsc -p lib/storage-broker/tsconfig.json && tsc -p services/claude-session-manager/tsconfig.json && tsc -p services/whatsapp-channel/tsconfig.json && tsc -p services/webchat-channel/tsconfig.json && tsc -p services/telegram-channel/tsconfig.json && NODE_OPTIONS='--max-old-space-size=8192' tsc -b plugins/*/mcp/tsconfig.json",
10
+ "build:lib": "tsc -p lib/account-schema-regions/tsconfig.json && tsc -p lib/models/tsconfig.json && tsc -p lib/mcp-spawn-tee/tsconfig.json && tsc -p lib/mcp-lifeline/tsconfig.json && tsc -p lib/mcp-eager/tsconfig.json && tsc -p lib/account-enumeration/tsconfig.json && tsc -p lib/account-scope/tsconfig.json && tsc -p lib/graph-write/tsconfig.json && tsc -p lib/ledger-core/tsconfig.json && tsc -p lib/embed-client/tsconfig.json && tsc -p lib/graph-mcp/tsconfig.json && tsc -p lib/graph-trash/tsconfig.json && tsc -p lib/active-rules/tsconfig.json && tsc -p lib/admin-conversation-purge/tsconfig.json && tsc -p lib/graph-search/tsconfig.json && tsc -p lib/graph-style/tsconfig.json && tsc -p lib/device-url/tsconfig.json && tsc -p lib/brand-templating/tsconfig.json && tsc -p lib/entitlement/tsconfig.json && tsc -p lib/task-secrets/tsconfig.json && tsc -p lib/admins-write/tsconfig.json && tsc -p lib/admin-access-password/tsconfig.json && tsc -p lib/persistent-components/tsconfig.json && tsc -p lib/require-port-env/tsconfig.json && tsc -p lib/aeo-llms-txt-writer/tsconfig.json && tsc -p lib/obsidian-parser/tsconfig.json && tsc -p lib/storage-broker/tsconfig.json",
11
11
  "gen:canonical-tools": "node scripts/generate-canonical-tool-names.mjs",
12
12
  "build:memory": "tsc -p plugins/memory/mcp/tsconfig.json",
13
13
  "build:contacts": "tsc -p plugins/contacts/mcp/tsconfig.json",
@@ -22,7 +22,7 @@
22
22
  "build:replicate": "tsc -p plugins/replicate/mcp/tsconfig.json",
23
23
  "build:graph-viewer": "tsc -p plugins/graph-viewer/mcp/tsconfig.json",
24
24
  "build:claude-session-manager": "tsc -p services/claude-session-manager/tsconfig.json",
25
- "build:whatsapp-channel": "tsc -p services/whatsapp-channel/tsconfig.json",
25
+ "build:whatsapp-channel": "tsc -p services/whatsapp-channel/tsconfig.json",
26
26
  "build:webchat-channel": "tsc -p services/webchat-channel/tsconfig.json",
27
27
  "build:telegram-channel": "tsc -p services/telegram-channel/tsconfig.json"
28
28
  },
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: platform-architecture
3
3
  description: Use when grounding any documented-surface claim about what Maxy ships — plugins, skills, specialists, install/deploy flows, internals. This is the install catalogue, not evidence of what is enabled on the current account. For install state on this account, call `capabilities-here`; for documented surface, cite the `Source:` URL inline.
4
- content-hash: sha256:1c3857d846036719bbed45a9411a5027e90fc1d28d0a6230e2ef6a7f20b645ec
4
+ content-hash: sha256:7e164ac0a4a7c3a6ab0fead2f2f109cb9a4f9e9c89b4761ecdee9c7166dca4ed
5
5
  brand: maxy-code
6
6
  product-name: Maxy
7
7
  ---
@@ -3101,25 +3101,49 @@ kinds, each card tagged `Public` or `Specialist`:
3101
3101
  link.
3102
3102
 
3103
3103
  The former "Public" flyout toggle in the account menu was removed; this page is
3104
- the sole agent surface. The route (`server/routes/admin/agents.ts`) resolves the
3105
- current account via `resolveAccount`; `:slug` params reject `/`, `..`, `\` (the
3106
- traversal guard). A slug containing `--` routes to the specialist branch.
3104
+ the sole agent surface.
3105
+
3106
+ **Account scope.** The route (`server/routes/admin/agents.ts`) never infers an
3107
+ account from device state. The two reads resolve from the caller's admin session
3108
+ (`requireAdminSession` + the shared `accountDirForSession` in
3109
+ `server/routes/admin/_account-scope.ts`), returning 401 when the session maps to
3110
+ no valid account. The two writes carry no admin session (the
3111
+ `public-agent-manager` skill calls them), so they take an explicit `accountId`
3112
+ query parameter validated against the account list and refuse a missing or
3113
+ unknown one with 400 — never defaulting to a resolved account. `:slug` params
3114
+ reject `/`, `..`, `\` (the traversal guard); specialist detail is selected by an
3115
+ explicit `?plugin=` parameter, not inferred from `--` in the slug.
3107
3116
 
3108
3117
  | Route | Behaviour |
3109
3118
  |-------|-----------|
3110
- | `GET /api/admin/agents` | Lists public `agents/*/` dirs (never the `admin` agent) plus user-created specialists from `plugins/*/agents/*.md`, each row tagged `kind`. Returns `{agents, skipped, specialistsSkipped}` — `skipped` counts public dirs whose `config.json` failed to parse; `specialistsSkipped` counts specialist `.md` files with no parseable `name`. |
3111
- | `GET /api/admin/agents/:slug` | A `--`-free slug returns a public agent's config fields + four owned docs + a `present` map (a missing doc is `''`/`present.<role>=false`, never a 500). A `--` slug returns a specialist's `{kind, model, tools, systemPrompt, skills}`. Unknown 404s. |
3112
- | `DELETE /api/admin/agents/:slug` | Public agents only: removes the dir after `deleteAgentProjection`; refuses the `admin` slug. Loud-fail: a graph-cleanup throw aborts the file removal. |
3113
-
3114
- Observability rides `[admin/agents]`: `op=list agents=<n> specialists=<m>
3115
- skipped=<x> specialistsSkipped=<y>` (the heartbeat `skipped>0`/`specialistsSkipped>0`
3116
- is a malformed-source signal the page renders as a banner; `specialists=0` with
3117
- user plugins present is the broken-walk signature), `op=detail slug=<…>
3118
- kind=<public|specialist>` (with `present=<comma-list|none>` on the public branch —
3119
- a role absent for an agent expected complete is the missing-doc signature;
3120
- `op=list-specialist-skip plugin=<p> file=<f>` per unreadable specialist), and the
3121
- existing delete lines (`deleted agent "<slug>"` / `graph cleanup failed …
3122
- aborting file removal`).
3119
+ | `GET /api/admin/agents` | Session-scoped. Lists the session account's public `agents/*/` dirs (never the `admin` agent) plus its user-created specialists from `plugins/*/agents/*.md`, each row tagged `kind`. Returns `{agents, accountId, skipped, specialistsSkipped}` — `accountId` names the account resolved (the client sends it back on a delete); `skipped` counts public dirs whose `config.json` failed to parse; `specialistsSkipped` counts specialist `.md` files with no parseable `name`. 401 when the session maps to no account. |
3120
+ | `GET /api/admin/agents/:slug` | Session-scoped. Without `?plugin=`, returns a public agent's config fields + four owned docs + a `present` map (a missing or unreadable doc is `''`/`present.<role>=false`, never a 500). With `?plugin=`, returns that plugin's specialist `{kind, model, tools, systemPrompt, skills}`. Unknown 404s, 401 as above. |
3121
+ | `DELETE /api/admin/agents/:slug?accountId=` | Public agents only, on the named validated account. Removes the dir after `deleteAgentProjection`; refuses the `admin` slug (403) and a missing/unknown `accountId` (400) with no write. Loud-fail: a graph-cleanup throw aborts the file removal. |
3122
+ | `POST /api/admin/agents/:slug/project?accountId=` | Re-projects the named account's on-disk agent into the graph. Same `accountId` contract as delete. |
3123
+
3124
+ Observability rides `[admin/agents]`, and **every line names the account it acted
3125
+ on** (`accountId=<id8>`) — a well-formed answer about the wrong account is
3126
+ otherwise indistinguishable from a correct one, which is exactly how an empty
3127
+ Agents page survived undiagnosed on a multi-account install.
3128
+
3129
+ - `op=list accountId=<id8> agents=<n> specialists=<m> skipped=<x> specialistsSkipped=<y>`
3130
+ the heartbeat. Compare `accountId` against the account the operator is
3131
+ viewing; `skipped>0`/`specialistsSkipped>0` is a malformed-source signal the
3132
+ page renders as a banner, and `specialists=0` with user plugins present is the
3133
+ broken-walk signature.
3134
+ - `op=detail accountId=<id8> slug=<…> kind=<public|specialist>` — with
3135
+ `present=<comma-list|none>` on the public branch, where a role absent for an
3136
+ agent expected complete is the missing-doc signature. One
3137
+ `op=list-specialist-skip plugin=<p> file=<f>` per unreadable specialist.
3138
+ - `op=<list|detail> auth-rejected reason=no-account-for-session` — the session
3139
+ maps to no valid account. No `op=list` line at all for a page load means the
3140
+ request never reached the handler, a distinct signature from a zero count.
3141
+ - `op=<delete|project> rejected reason=<missing-accountId|unknown-accountId>` —
3142
+ a caller that did not name its account. A skill that was not updated produces
3143
+ this on its first call instead of writing to the wrong account.
3144
+ - `op=delete accountId=<id8> slug=<…> outcome=<ok|failed>` with
3145
+ `reason=<graph-cleanup-failed|rm-error>` on failure (files preserved on a
3146
+ graph-cleanup throw).
3123
3147
 
3124
3148
  ### Graph
3125
3149
 
@@ -3253,6 +3277,24 @@ the Web Share API and is disabled where unavailable. Signals:
3253
3277
  `[data-ui] op=mount header=operator home=absent`, `op=select-enter`,
3254
3278
  `op=select-exit reason=<cancel|action>`, `op=share supported=<bool> count=<n>`.
3255
3279
 
3280
+ **`/data` opens on the account's own folders, as cards.** With no `#path=` in
3281
+ the URL the browser lands on **home**: the account's business folders (the
3282
+ entity folders of its trade, plus `projects`, `contacts` and `documents`) shown
3283
+ one per card, each listing the first 20 things inside it and a `+N more` row
3284
+ past that. Tapping a card opens that folder in the ordinary list or grid. There
3285
+ are never loose files at this level. Everything the platform and its plugins
3286
+ keep for themselves — caches, logs, published sites, plugin workspaces, hidden
3287
+ folders — sits behind a single **system** card at the end, and opens as the
3288
+ ordinary list too. The split comes from the account's own `SCHEMA.md`, so it
3289
+ follows the graph ontology rather than a hardcoded list, and nothing moves on
3290
+ disk. Upload and New folder are disabled on the cards view because it names no
3291
+ single folder to write into; open a folder first. The flat account root is still
3292
+ reachable from the account name in the breadcrumb, which is the way back if a
3293
+ folder is ever grouped wrongly. Signals: `[data-schema] op=classify` on every
3294
+ account-root listing (`home=`, `system=`, `unknown=`, `status=ok|parse-failed`),
3295
+ `op=home-files` when a name that should be a folder is a file, and
3296
+ `op=card-children … status=error` when one card's folder could not be read.
3297
+
3256
3298
  **`/data` list ↔ grid view + image thumbnails.** A toolbar toggle switches the
3257
3299
  file browser between the two-line list and a **grid**. In grid view, image
3258
3300
  files render an inline thumbnail; folders render a folder tile (still navigate
@@ -176,7 +176,7 @@ After creation, no template metadata persists in the agent's files. The resultin
176
176
  8. **KNOWLEDGE.md generation** — populate from the now-tagged set plus keyword matches using the `update-knowledge` skill workflow. KNOWLEDGE.md is required and must be non-empty; an empty or missing KNOWLEDGE is refused at spawn.
177
177
  9. Write `config.json` with selected model, status "active", and `knowledgeKeywords`. This is the last gated write — placed after IDENTITY.md, SOUL.md, and KNOWLEDGE.md to prevent cascade failure if one gate stalls.
178
178
  10. Check context budget — auto-summarise if over threshold
179
- 11. **Project the agent into the graph** — delegate to the `specialists:database-operator` specialist with the instruction: "Project public agent `{slug}` into the graph by POSTing to `/api/admin/agents/{slug}/project`." The route reads the on-disk files and idempotently MERGEs the `:Agent` node, the four owned `:KnowledgeDocument` projections (IDENTITY/SOUL/KNOWLEDGE/KNOWLEDGE-SUMMARY when present, namespaced `attachmentId="agent:<slug>:<role>"`), the `HAS_*` edges, and the `USES_KNOWLEDGE` edges to every operator-tagged doc. Loud-fail: if the route returns non-2xx, surface the error to the user verbatim — the agent's files exist on disk but its graph projection is incomplete, which means the operator's /graph view will not show this agent. Re-running the projection is safe; it is the same idempotent MERGE.
179
+ 11. **Project the agent into the graph** — delegate to the `specialists:database-operator` specialist with the instruction: "Project public agent `{slug}` into the graph by POSTing to `/api/admin/agents/{slug}/project?accountId=<the literal ACCOUNT_ID value you read>`." The route reads the on-disk files and idempotently MERGEs the `:Agent` node, the four owned `:KnowledgeDocument` projections (IDENTITY/SOUL/KNOWLEDGE/KNOWLEDGE-SUMMARY when present, namespaced `attachmentId="agent:<slug>:<role>"`), the `HAS_*` edges, and the `USES_KNOWLEDGE` edges to every operator-tagged doc. Loud-fail: if the route returns non-2xx, surface the error to the user verbatim — the agent's files exist on disk but its graph projection is incomplete, which means the operator's /graph view will not show this agent. Re-running the projection is safe; it is the same idempotent MERGE. The `accountId` query parameter is required on every write to this route (project and delete). The route no longer infers an account from device state, so it returns 400 without one and 403 when the caller is not entitled to the account it named. Source the value from the `ACCOUNT_ID` environment variable of this session (`echo $ACCOUNT_ID`), and substitute the literal id into the URL before delegating, since the specialist you delegate to does not share your shell.
180
180
  12. **Set the default if none exists.** Read `defaultAgent` from `account-manage`. If it is empty, set it to this new slug via `account-update` (`field: "defaultAgent"`, `value: "{slug}"`) — otherwise the agent answers at `/{slug}` but the bare root serves nothing. If a default already exists, do not change it silently: tell the operator the current default and ask whether to switch the root to this new agent; call `account-update` only if they say yes.
181
181
  13. Confirm creation: "Agent created and reachable at `/{slug}`." If you set or changed the default in step 12, add: "The site root also serves this agent." If a different default remains, add: "The site root still serves `{currentDefault}`."
182
182
 
@@ -213,7 +213,7 @@ For knowledge scope changes:
213
213
  - Allow adding/removing keyword subscriptions (update `knowledgeKeywords` in `config.json`).
214
214
  - After changes, offer to refresh KNOWLEDGE.md using the `update-knowledge` skill.
215
215
 
216
- **After every Edit operation that touches IDENTITY/SOUL/KNOWLEDGE/KNOWLEDGE-SUMMARY/`config.json` (including `knowledgeKeywords` or direct-tag mutations), delegate to `specialists:database-operator` to re-project: POST `/api/admin/agents/{slug}/project`.** Without re-projection the on-disk files diverge from the graph state — the operator sees stale `:Agent` properties and stale `USES_KNOWLEDGE` edges in /graph. Loud-fail: surface non-2xx errors verbatim.
216
+ **After every Edit operation that touches IDENTITY/SOUL/KNOWLEDGE/KNOWLEDGE-SUMMARY/`config.json` (including `knowledgeKeywords` or direct-tag mutations), delegate to `specialists:database-operator` to re-project: POST `/api/admin/agents/{slug}/project?accountId={accountId}`.** Without re-projection the on-disk files diverge from the graph state — the operator sees stale `:Agent` properties and stale `USES_KNOWLEDGE` edges in /graph. Loud-fail: surface non-2xx errors verbatim.
217
217
 
218
218
  ### Delete
219
219
 
@@ -226,7 +226,7 @@ Deletion removes the entire agent directory (`agents/{slug}/`) — not individua
226
226
  - If no other agents remain (or the user declines to set a new default), clear the `defaultAgent` field by calling `account-update` with `field: "defaultAgent"`, `value: ""`. Tell the user that visitors to the root URL will see a "no agents" message until a new agent is created.
227
227
 
228
228
  **Cleanup sequence:**
229
- - Issue `DELETE /api/admin/agents/{slug}` — the route removes the `:Agent` node and its four owned `:KnowledgeDocument` projections FIRST, then deletes the directory. Loud-fail: if graph cleanup throws, files are preserved and a 500 is returned with the error text. Re-issuing the DELETE is safe (idempotent on both layers). Operator-tagged docs survive the projection delete — only the `USES_KNOWLEDGE` edges from this Agent are removed.
229
+ - Issue `DELETE /api/admin/agents/{slug}?accountId={accountId}` — the route removes the `:Agent` node and its four owned `:KnowledgeDocument` projections FIRST, then deletes the directory. Loud-fail: if graph cleanup throws, files are preserved and a 500 is returned with the error text. Re-issuing the DELETE is safe (idempotent on both layers). Operator-tagged docs survive the projection delete — only the `USES_KNOWLEDGE` edges from this Agent are removed.
230
230
  - Verify the directory no longer exists
231
231
  - Report what was removed (list the files that were in the directory)
232
232
 
@@ -9,6 +9,12 @@ Invoked by the admin agent directly.
9
9
 
10
10
  This is the platform's release timeline, newest first. Each entry shows the date it shipped and the version it shipped in, so you can tell the operator how current their install is. To compare, read the installed version from `capabilities-here` and match it against the versions below. Keep answers high level and in plain English; this is a summary, not a full commit log.
11
11
 
12
+ ## 2026-07-25 (0.1.506)
13
+
14
+ - Fixed a bug where the Agents page and a few other admin screens could resolve the wrong account.
15
+ - The dashboard and sidebar now update instantly when something changes, instead of waiting for the next refresh.
16
+ - The Data page now groups your files into Home and System, with a clearer card layout.
17
+
12
18
  ## 2026-07-25 (0.1.505)
13
19
 
14
20
  - Upgraded to Claude Opus 5, the assistant's most capable model.
@@ -174,25 +174,49 @@ kinds, each card tagged `Public` or `Specialist`:
174
174
  link.
175
175
 
176
176
  The former "Public" flyout toggle in the account menu was removed; this page is
177
- the sole agent surface. The route (`server/routes/admin/agents.ts`) resolves the
178
- current account via `resolveAccount`; `:slug` params reject `/`, `..`, `\` (the
179
- traversal guard). A slug containing `--` routes to the specialist branch.
177
+ the sole agent surface.
178
+
179
+ **Account scope.** The route (`server/routes/admin/agents.ts`) never infers an
180
+ account from device state. The two reads resolve from the caller's admin session
181
+ (`requireAdminSession` + the shared `accountDirForSession` in
182
+ `server/routes/admin/_account-scope.ts`), returning 401 when the session maps to
183
+ no valid account. The two writes carry no admin session (the
184
+ `public-agent-manager` skill calls them), so they take an explicit `accountId`
185
+ query parameter validated against the account list and refuse a missing or
186
+ unknown one with 400 — never defaulting to a resolved account. `:slug` params
187
+ reject `/`, `..`, `\` (the traversal guard); specialist detail is selected by an
188
+ explicit `?plugin=` parameter, not inferred from `--` in the slug.
180
189
 
181
190
  | Route | Behaviour |
182
191
  |-------|-----------|
183
- | `GET /api/admin/agents` | Lists public `agents/*/` dirs (never the `admin` agent) plus user-created specialists from `plugins/*/agents/*.md`, each row tagged `kind`. Returns `{agents, skipped, specialistsSkipped}` — `skipped` counts public dirs whose `config.json` failed to parse; `specialistsSkipped` counts specialist `.md` files with no parseable `name`. |
184
- | `GET /api/admin/agents/:slug` | A `--`-free slug returns a public agent's config fields + four owned docs + a `present` map (a missing doc is `''`/`present.<role>=false`, never a 500). A `--` slug returns a specialist's `{kind, model, tools, systemPrompt, skills}`. Unknown 404s. |
185
- | `DELETE /api/admin/agents/:slug` | Public agents only: removes the dir after `deleteAgentProjection`; refuses the `admin` slug. Loud-fail: a graph-cleanup throw aborts the file removal. |
186
-
187
- Observability rides `[admin/agents]`: `op=list agents=<n> specialists=<m>
188
- skipped=<x> specialistsSkipped=<y>` (the heartbeat `skipped>0`/`specialistsSkipped>0`
189
- is a malformed-source signal the page renders as a banner; `specialists=0` with
190
- user plugins present is the broken-walk signature), `op=detail slug=<…>
191
- kind=<public|specialist>` (with `present=<comma-list|none>` on the public branch —
192
- a role absent for an agent expected complete is the missing-doc signature;
193
- `op=list-specialist-skip plugin=<p> file=<f>` per unreadable specialist), and the
194
- existing delete lines (`deleted agent "<slug>"` / `graph cleanup failed …
195
- aborting file removal`).
192
+ | `GET /api/admin/agents` | Session-scoped. Lists the session account's public `agents/*/` dirs (never the `admin` agent) plus its user-created specialists from `plugins/*/agents/*.md`, each row tagged `kind`. Returns `{agents, accountId, skipped, specialistsSkipped}` — `accountId` names the account resolved (the client sends it back on a delete); `skipped` counts public dirs whose `config.json` failed to parse; `specialistsSkipped` counts specialist `.md` files with no parseable `name`. 401 when the session maps to no account. |
193
+ | `GET /api/admin/agents/:slug` | Session-scoped. Without `?plugin=`, returns a public agent's config fields + four owned docs + a `present` map (a missing or unreadable doc is `''`/`present.<role>=false`, never a 500). With `?plugin=`, returns that plugin's specialist `{kind, model, tools, systemPrompt, skills}`. Unknown 404s, 401 as above. |
194
+ | `DELETE /api/admin/agents/:slug?accountId=` | Public agents only, on the named validated account. Removes the dir after `deleteAgentProjection`; refuses the `admin` slug (403) and a missing/unknown `accountId` (400) with no write. Loud-fail: a graph-cleanup throw aborts the file removal. |
195
+ | `POST /api/admin/agents/:slug/project?accountId=` | Re-projects the named account's on-disk agent into the graph. Same `accountId` contract as delete. |
196
+
197
+ Observability rides `[admin/agents]`, and **every line names the account it acted
198
+ on** (`accountId=<id8>`) — a well-formed answer about the wrong account is
199
+ otherwise indistinguishable from a correct one, which is exactly how an empty
200
+ Agents page survived undiagnosed on a multi-account install.
201
+
202
+ - `op=list accountId=<id8> agents=<n> specialists=<m> skipped=<x> specialistsSkipped=<y>`
203
+ the heartbeat. Compare `accountId` against the account the operator is
204
+ viewing; `skipped>0`/`specialistsSkipped>0` is a malformed-source signal the
205
+ page renders as a banner, and `specialists=0` with user plugins present is the
206
+ broken-walk signature.
207
+ - `op=detail accountId=<id8> slug=<…> kind=<public|specialist>` — with
208
+ `present=<comma-list|none>` on the public branch, where a role absent for an
209
+ agent expected complete is the missing-doc signature. One
210
+ `op=list-specialist-skip plugin=<p> file=<f>` per unreadable specialist.
211
+ - `op=<list|detail> auth-rejected reason=no-account-for-session` — the session
212
+ maps to no valid account. No `op=list` line at all for a page load means the
213
+ request never reached the handler, a distinct signature from a zero count.
214
+ - `op=<delete|project> rejected reason=<missing-accountId|unknown-accountId>` —
215
+ a caller that did not name its account. A skill that was not updated produces
216
+ this on its first call instead of writing to the wrong account.
217
+ - `op=delete accountId=<id8> slug=<…> outcome=<ok|failed>` with
218
+ `reason=<graph-cleanup-failed|rm-error>` on failure (files preserved on a
219
+ graph-cleanup throw).
196
220
 
197
221
  ### Graph
198
222
 
@@ -326,6 +350,24 @@ the Web Share API and is disabled where unavailable. Signals:
326
350
  `[data-ui] op=mount header=operator home=absent`, `op=select-enter`,
327
351
  `op=select-exit reason=<cancel|action>`, `op=share supported=<bool> count=<n>`.
328
352
 
353
+ **`/data` opens on the account's own folders, as cards.** With no `#path=` in
354
+ the URL the browser lands on **home**: the account's business folders (the
355
+ entity folders of its trade, plus `projects`, `contacts` and `documents`) shown
356
+ one per card, each listing the first 20 things inside it and a `+N more` row
357
+ past that. Tapping a card opens that folder in the ordinary list or grid. There
358
+ are never loose files at this level. Everything the platform and its plugins
359
+ keep for themselves — caches, logs, published sites, plugin workspaces, hidden
360
+ folders — sits behind a single **system** card at the end, and opens as the
361
+ ordinary list too. The split comes from the account's own `SCHEMA.md`, so it
362
+ follows the graph ontology rather than a hardcoded list, and nothing moves on
363
+ disk. Upload and New folder are disabled on the cards view because it names no
364
+ single folder to write into; open a folder first. The flat account root is still
365
+ reachable from the account name in the breadcrumb, which is the way back if a
366
+ folder is ever grouped wrongly. Signals: `[data-schema] op=classify` on every
367
+ account-root listing (`home=`, `system=`, `unknown=`, `status=ok|parse-failed`),
368
+ `op=home-files` when a name that should be a folder is a file, and
369
+ `op=card-children … status=error` when one card's folder could not be read.
370
+
329
371
  **`/data` list ↔ grid view + image thumbnails.** A toolbar toggle switches the
330
372
  file browser between the two-line list and a **grid**. In grid view, image
331
373
  files render an inline thumbnail; folders render a folder tile (still navigate
@@ -85,6 +85,11 @@ const ALLOWLIST = new Set([
85
85
  // top-level fs/os/path imports initialize), so it requires node builtins
86
86
  // directly there.
87
87
  'platform/ui/server/routes/admin/__tests__/files-scratchpad-download.test.ts',
88
+ // Same vi.hoisted pattern: the files-root-groups test builds a tmp
89
+ // MAXY_PLATFORM_ROOT inside the hoisted block (before the route module's
90
+ // data-path deps read the env at import), so it requires node builtins
91
+ // directly there.
92
+ 'platform/ui/server/routes/admin/__tests__/files-root-groups.test.ts',
88
93
  ])
89
94
 
90
95
  const REQUIRE_CALL_RE = /\brequire\s*\(/
@@ -15,7 +15,7 @@ export interface ReconcileResult extends ReconcileCounts {
15
15
  /** The single per-run summary line body. A non-zero count is the standing signal
16
16
  * that an agent authored folders the hard guards could not prevent. Pure
17
17
  * formatter so the emitter and its test share one wording. */
18
- export declare function formatReconcileLine(c: ReconcileCounts): string;
18
+ export declare function formatReconcileLine(c: ReconcileCounts, homeMissing?: number, homeUnaudited?: number): string;
19
19
  /** Parse the fenced ```allowed-top-level block from an account's SCHEMA.md —
20
20
  * the same structured set fs-schema-guard.sh parses with awk. Returns the
21
21
  * entry names (blank lines dropped). An absent block yields an empty set. */