@rubytech/create-maxy-code 0.1.219 → 0.1.222

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 (124) hide show
  1. package/dist/index.js +46 -1
  2. package/package.json +1 -1
  3. package/payload/platform/lib/graph-write/dist/__tests__/action-provenance-gate.test.js +60 -23
  4. package/payload/platform/lib/graph-write/dist/__tests__/action-provenance-gate.test.js.map +1 -1
  5. package/payload/platform/lib/graph-write/dist/index.d.ts +4 -1
  6. package/payload/platform/lib/graph-write/dist/index.d.ts.map +1 -1
  7. package/payload/platform/lib/graph-write/dist/index.js +17 -5
  8. package/payload/platform/lib/graph-write/dist/index.js.map +1 -1
  9. package/payload/platform/lib/graph-write/src/__tests__/action-provenance-gate.test.ts +58 -31
  10. package/payload/platform/lib/graph-write/src/index.ts +16 -7
  11. package/payload/platform/plugins/admin/PLUGIN.md +2 -3
  12. package/payload/platform/plugins/admin/__tests__/admin-can-enumerate.test.sh +129 -0
  13. package/payload/platform/plugins/admin/hooks/archive-ingest-surface-gate.sh +1 -2
  14. package/payload/platform/plugins/admin/hooks/lib/hook-emit.sh +3 -2
  15. package/payload/platform/plugins/admin/mcp/dist/index.js +41 -9
  16. package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
  17. package/payload/platform/plugins/admin/references/publish-site-routing.md +44 -0
  18. package/payload/platform/plugins/admin/skills/admin-user-management/SKILL.md +1 -1
  19. package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +11 -4
  20. package/payload/platform/plugins/admin/skills/plugin-management/SKILL.md +2 -2
  21. package/payload/platform/plugins/contacts/PLUGIN.md +2 -2
  22. package/payload/platform/plugins/docs/references/internals.md +1 -1
  23. package/payload/platform/plugins/docs/references/platform.md +2 -2
  24. package/payload/platform/plugins/docs/references/troubleshooting.md +7 -0
  25. package/payload/platform/plugins/memory/PLUGIN.md +1 -1
  26. package/payload/platform/plugins/memory/references/graph-primitives.md +2 -2
  27. package/payload/platform/plugins/url-get/mcp/dist/tools/url-get.d.ts +2 -1
  28. package/payload/platform/plugins/url-get/mcp/dist/tools/url-get.d.ts.map +1 -1
  29. package/payload/platform/plugins/url-get/mcp/dist/tools/url-get.js +27 -21
  30. package/payload/platform/plugins/url-get/mcp/dist/tools/url-get.js.map +1 -1
  31. package/payload/platform/scripts/check-no-esm-require.mjs +3 -0
  32. package/payload/platform/scripts/setup-account.sh +0 -15
  33. package/payload/platform/services/claude-session-manager/dist/auth-snapshot.d.ts +4 -0
  34. package/payload/platform/services/claude-session-manager/dist/auth-snapshot.d.ts.map +1 -0
  35. package/payload/platform/services/claude-session-manager/dist/auth-snapshot.js +50 -0
  36. package/payload/platform/services/claude-session-manager/dist/auth-snapshot.js.map +1 -0
  37. package/payload/platform/services/claude-session-manager/dist/http-server.d.ts.map +1 -1
  38. package/payload/platform/services/claude-session-manager/dist/http-server.js +19 -2
  39. package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -1
  40. package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts.map +1 -1
  41. package/payload/platform/services/claude-session-manager/dist/pty-spawner.js +39 -19
  42. package/payload/platform/services/claude-session-manager/dist/pty-spawner.js.map +1 -1
  43. package/payload/platform/services/claude-session-manager/dist/rc-daemon.d.ts +24 -0
  44. package/payload/platform/services/claude-session-manager/dist/rc-daemon.d.ts.map +1 -1
  45. package/payload/platform/services/claude-session-manager/dist/rc-daemon.js +100 -11
  46. package/payload/platform/services/claude-session-manager/dist/rc-daemon.js.map +1 -1
  47. package/payload/platform/services/claude-session-manager/dist/specialist-drift.d.ts +8 -0
  48. package/payload/platform/services/claude-session-manager/dist/specialist-drift.d.ts.map +1 -1
  49. package/payload/platform/services/claude-session-manager/dist/specialist-drift.js +7 -2
  50. package/payload/platform/services/claude-session-manager/dist/specialist-drift.js.map +1 -1
  51. package/payload/server/public/assets/{AdminShell-BcxEZnpu.js → AdminShell-BiEwDSRL.js} +1 -1
  52. package/payload/server/public/assets/{Checkbox-DL-HdT29.js → Checkbox-COiFXiXL.js} +1 -1
  53. package/payload/server/public/assets/{admin-DMo5DWKH.js → admin-Ca9ckLZx.js} +1 -1
  54. package/payload/server/public/assets/{architectureDiagram-Q4EWVU46-BoUw4oYz.js → architectureDiagram-Q4EWVU46-DI6JZI13.js} +1 -1
  55. package/payload/server/public/assets/{blockDiagram-DXYQGD6D-DOscnO3m.js → blockDiagram-DXYQGD6D-dgDvswLd.js} +1 -1
  56. package/payload/server/public/assets/{brand-SZW-27T7.css → brand-CAMMU5lz.css} +1 -1
  57. package/payload/server/public/assets/{c4Diagram-AHTNJAMY-aZ1IuZ_F.js → c4Diagram-AHTNJAMY-odaGY4GZ.js} +1 -1
  58. package/payload/server/public/assets/channel-DfwLbKck.js +1 -0
  59. package/payload/server/public/assets/{chunk-336JU56O-DS3ksXBG.js → chunk-336JU56O-BpbY8F27.js} +2 -2
  60. package/payload/server/public/assets/{chunk-426QAEUC-BLRn2STO.js → chunk-426QAEUC-BWbVhEVC.js} +1 -1
  61. package/payload/server/public/assets/{chunk-4TB4RGXK-BrhOvJ01.js → chunk-4TB4RGXK-C3IfRdvA.js} +1 -1
  62. package/payload/server/public/assets/{chunk-5FUZZQ4R-BZZwZX5Q.js → chunk-5FUZZQ4R-CSRMBAGr.js} +1 -1
  63. package/payload/server/public/assets/{chunk-5PVQY5BW-YXZKnPCz.js → chunk-5PVQY5BW-CGAe6GJJ.js} +1 -1
  64. package/payload/server/public/assets/{chunk-EDXVE4YY-DAMCJHKQ.js → chunk-EDXVE4YY-BnXC8cYQ.js} +1 -1
  65. package/payload/server/public/assets/{chunk-ENJZ2VHE-DsD95Bqn.js → chunk-ENJZ2VHE-7qfDalAB.js} +1 -1
  66. package/payload/server/public/assets/{chunk-ICPOFSXX-DxtrRClx.js → chunk-ICPOFSXX-TRvVGIDw.js} +1 -1
  67. package/payload/server/public/assets/{chunk-OYMX7WX6-CN4kLK-H.js → chunk-OYMX7WX6-Cfv8vBvI.js} +1 -1
  68. package/payload/server/public/assets/{chunk-U2HBQHQK-BPTv3Oj1.js → chunk-U2HBQHQK-BfLvbEyu.js} +1 -1
  69. package/payload/server/public/assets/{chunk-X2U36JSP-DxBry33v.js → chunk-X2U36JSP-Cj3k-dYC.js} +1 -1
  70. package/payload/server/public/assets/{chunk-YZCP3GAM-BZ4N75oy.js → chunk-YZCP3GAM-CygW6I0h.js} +1 -1
  71. package/payload/server/public/assets/{chunk-ZZ45TVLE-Dqr161Q1.js → chunk-ZZ45TVLE-CBF3hFJH.js} +1 -1
  72. package/payload/server/public/assets/classDiagram-6PBFFD2Q-CE0XZQre.js +1 -0
  73. package/payload/server/public/assets/classDiagram-v2-HSJHXN6E-Bj41Y3XD.js +1 -0
  74. package/payload/server/public/assets/clone-DTd5nONj.js +1 -0
  75. package/payload/server/public/assets/{dagre-DtZ0wKkH.js → dagre-BTGABQJX.js} +1 -1
  76. package/payload/server/public/assets/{dagre-KV5264BT-BPKhT-ZX.js → dagre-KV5264BT-B2lbkuGd.js} +1 -1
  77. package/payload/server/public/assets/{data-D6IQdm0R.js → data-CHdaTjgn.js} +1 -1
  78. package/payload/server/public/assets/{diagram-5BDNPKRD-CATnafi7.js → diagram-5BDNPKRD-BUK4LqT8.js} +1 -1
  79. package/payload/server/public/assets/{diagram-G4DWMVQ6-ChZYQfji.js → diagram-G4DWMVQ6-e49HrvxL.js} +1 -1
  80. package/payload/server/public/assets/{diagram-MMDJMWI5-DHW6kYd3.js → diagram-MMDJMWI5-k4Kx_qWe.js} +1 -1
  81. package/payload/server/public/assets/{diagram-TYMM5635-CnS4Zci8.js → diagram-TYMM5635-DzavOUdK.js} +1 -1
  82. package/payload/server/public/assets/{erDiagram-SMLLAGMA-DKYv2YWp.js → erDiagram-SMLLAGMA-CJKecRhi.js} +1 -1
  83. package/payload/server/public/assets/{flowDiagram-DWJPFMVM-BWvoZWlh.js → flowDiagram-DWJPFMVM-GM7H1TDt.js} +1 -1
  84. package/payload/server/public/assets/{ganttDiagram-T4ZO3ILL-CpCb-xKy.js → ganttDiagram-T4ZO3ILL-kDEA1A-3.js} +1 -1
  85. package/payload/server/public/assets/{gitGraphDiagram-UUTBAWPF-CvHrDKQ-.js → gitGraphDiagram-UUTBAWPF-jVZZdeg2.js} +1 -1
  86. package/payload/server/public/assets/{graph-labels-CWJy0O4w.js → graph-labels-CavkO4DG.js} +1 -1
  87. package/payload/server/public/assets/{graph-PkZKVkWD.js → graph-tqCqSngu.js} +1 -1
  88. package/payload/server/public/assets/{graphlib-DjnyHqGy.js → graphlib-DFU2rJiY.js} +1 -1
  89. package/payload/server/public/assets/{infoDiagram-42DDH7IO-D_MywcVn.js → infoDiagram-42DDH7IO-CFxVoRBy.js} +1 -1
  90. package/payload/server/public/assets/{ishikawaDiagram-UXIWVN3A-N9m-f-Eb.js → ishikawaDiagram-UXIWVN3A-CDvZs3Ua.js} +1 -1
  91. package/payload/server/public/assets/{journeyDiagram-VCZTEJTY-DE0q7uSM.js → journeyDiagram-VCZTEJTY-DUI1m9vW.js} +1 -1
  92. package/payload/server/public/assets/{kanban-definition-6JOO6SKY-Dj5hb9Bq.js → kanban-definition-6JOO6SKY-C_jSVK7W.js} +1 -1
  93. package/payload/server/public/assets/{line-C6ph0i4M.js → line-DfwJeo6J.js} +1 -1
  94. package/payload/server/public/assets/{mermaid-parser.core-BrfmEvCA.js → mermaid-parser.core-BN-2kBfU.js} +1 -1
  95. package/payload/server/public/assets/{mermaid.core-DYnHKfDv.js → mermaid.core-C6amhHAQ.js} +3 -3
  96. package/payload/server/public/assets/{mindmap-definition-QFDTVHPH-DaSaZfhR.js → mindmap-definition-QFDTVHPH-eTKDlw3c.js} +1 -1
  97. package/payload/server/public/assets/{pieDiagram-DEJITSTG-BAu9Ezbv.js → pieDiagram-DEJITSTG-DNX5VeMZ.js} +1 -1
  98. package/payload/server/public/assets/{public-UKgkJxKv.js → public-BADBLzOZ.js} +3 -3
  99. package/payload/server/public/assets/{quadrantDiagram-34T5L4WZ-DRsbL1Xd.js → quadrantDiagram-34T5L4WZ-BFnmYiHX.js} +1 -1
  100. package/payload/server/public/assets/{requirementDiagram-MS252O5E-B9QcsXV6.js → requirementDiagram-MS252O5E-DvHvTyzu.js} +1 -1
  101. package/payload/server/public/assets/{sankeyDiagram-XADWPNL6-DsZbvIlD.js → sankeyDiagram-XADWPNL6-CMFnNT1G.js} +1 -1
  102. package/payload/server/public/assets/{sequenceDiagram-FGHM5R23-BlT4O2N8.js → sequenceDiagram-FGHM5R23-BYon1ouU.js} +1 -1
  103. package/payload/server/public/assets/{stateDiagram-FHFEXIEX-JFgk1K3Y.js → stateDiagram-FHFEXIEX-n6l3hcuJ.js} +1 -1
  104. package/payload/server/public/assets/stateDiagram-v2-QKLJ7IA2-CS46-Mhi.js +1 -0
  105. package/payload/server/public/assets/{timeline-definition-GMOUNBTQ-DOsHw1ip.js → timeline-definition-GMOUNBTQ-DFPFHrQF.js} +1 -1
  106. package/payload/server/public/assets/{useSelectionMode-JDGvwvIk.js → useSelectionMode-iqIyL24g.js} +1 -1
  107. package/payload/server/public/assets/{vennDiagram-DHZGUBPP-CQWIhqZY.js → vennDiagram-DHZGUBPP-DZLturYa.js} +1 -1
  108. package/payload/server/public/assets/{wardleyDiagram-NUSXRM2D-D5Fh7YVL.js → wardleyDiagram-NUSXRM2D-BBt2pdjM.js} +1 -1
  109. package/payload/server/public/assets/{xychartDiagram-5P7HB3ND-C-tINQEs.js → xychartDiagram-5P7HB3ND-DOZxLvEi.js} +1 -1
  110. package/payload/server/public/data.html +5 -5
  111. package/payload/server/public/graph.html +6 -6
  112. package/payload/server/public/index.html +6 -6
  113. package/payload/server/public/public.html +5 -5
  114. package/payload/server/server.js +49 -6
  115. package/payload/platform/plugins/admin/hooks/__tests__/pre-tool-use-admin-tool-gate.test.sh +0 -109
  116. package/payload/platform/plugins/admin/hooks/__tests__/pre-tool-use-base64-guard.test.sh +0 -204
  117. package/payload/platform/plugins/admin/hooks/__tests__/pre-tool-use-memory-write-passthrough.test.sh +0 -118
  118. package/payload/platform/plugins/admin/hooks/pre-tool-use.sh +0 -337
  119. package/payload/server/public/assets/channel-CzcSNxDm.js +0 -1
  120. package/payload/server/public/assets/classDiagram-6PBFFD2Q-DFOM3vCn.js +0 -1
  121. package/payload/server/public/assets/classDiagram-v2-HSJHXN6E-HplDfaL1.js +0 -1
  122. package/payload/server/public/assets/clone-G-2rqnSR.js +0 -1
  123. package/payload/server/public/assets/stateDiagram-v2-QKLJ7IA2-NXz27DaV.js +0 -1
  124. /package/payload/server/public/assets/{brand-SzZmKKjz.js → brand-DfykKss4.js} +0 -0
@@ -5,12 +5,12 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
6
  <title>Data — Maxy</title>
7
7
  <link rel="icon" href="/favicon.ico">
8
- <script type="module" crossorigin src="/assets/data-D6IQdm0R.js"></script>
8
+ <script type="module" crossorigin src="/assets/data-CHdaTjgn.js"></script>
9
9
  <link rel="modulepreload" crossorigin href="/assets/chunk-DD-I1_y5.js">
10
- <link rel="modulepreload" crossorigin href="/assets/brand-SzZmKKjz.js">
11
- <link rel="modulepreload" crossorigin href="/assets/graph-labels-CWJy0O4w.js">
12
- <link rel="modulepreload" crossorigin href="/assets/AdminShell-BcxEZnpu.js">
13
- <link rel="stylesheet" crossorigin href="/assets/brand-SZW-27T7.css">
10
+ <link rel="modulepreload" crossorigin href="/assets/brand-DfykKss4.js">
11
+ <link rel="modulepreload" crossorigin href="/assets/graph-labels-CavkO4DG.js">
12
+ <link rel="modulepreload" crossorigin href="/assets/AdminShell-BiEwDSRL.js">
13
+ <link rel="stylesheet" crossorigin href="/assets/brand-CAMMU5lz.css">
14
14
  </head>
15
15
  <body>
16
16
  <div id="root"></div>
@@ -5,13 +5,13 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
6
  <title>Graph — Maxy</title>
7
7
  <link rel="icon" href="/favicon.ico">
8
- <script type="module" crossorigin src="/assets/graph-PkZKVkWD.js"></script>
8
+ <script type="module" crossorigin src="/assets/graph-tqCqSngu.js"></script>
9
9
  <link rel="modulepreload" crossorigin href="/assets/chunk-DD-I1_y5.js">
10
- <link rel="modulepreload" crossorigin href="/assets/brand-SzZmKKjz.js">
11
- <link rel="modulepreload" crossorigin href="/assets/graph-labels-CWJy0O4w.js">
12
- <link rel="modulepreload" crossorigin href="/assets/AdminShell-BcxEZnpu.js">
13
- <link rel="modulepreload" crossorigin href="/assets/Checkbox-DL-HdT29.js">
14
- <link rel="stylesheet" crossorigin href="/assets/brand-SZW-27T7.css">
10
+ <link rel="modulepreload" crossorigin href="/assets/brand-DfykKss4.js">
11
+ <link rel="modulepreload" crossorigin href="/assets/graph-labels-CavkO4DG.js">
12
+ <link rel="modulepreload" crossorigin href="/assets/AdminShell-BiEwDSRL.js">
13
+ <link rel="modulepreload" crossorigin href="/assets/Checkbox-COiFXiXL.js">
14
+ <link rel="stylesheet" crossorigin href="/assets/brand-CAMMU5lz.css">
15
15
  </head>
16
16
  <body>
17
17
  <div id="root"></div>
@@ -5,13 +5,13 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
6
  <title>Maxy</title>
7
7
  <link rel="icon" href="/favicon.ico">
8
- <script type="module" crossorigin src="/assets/admin-DMo5DWKH.js"></script>
8
+ <script type="module" crossorigin src="/assets/admin-Ca9ckLZx.js"></script>
9
9
  <link rel="modulepreload" crossorigin href="/assets/chunk-DD-I1_y5.js">
10
- <link rel="modulepreload" crossorigin href="/assets/brand-SzZmKKjz.js">
11
- <link rel="modulepreload" crossorigin href="/assets/AdminShell-BcxEZnpu.js">
12
- <link rel="modulepreload" crossorigin href="/assets/useSelectionMode-JDGvwvIk.js">
13
- <link rel="modulepreload" crossorigin href="/assets/Checkbox-DL-HdT29.js">
14
- <link rel="stylesheet" crossorigin href="/assets/brand-SZW-27T7.css">
10
+ <link rel="modulepreload" crossorigin href="/assets/brand-DfykKss4.js">
11
+ <link rel="modulepreload" crossorigin href="/assets/AdminShell-BiEwDSRL.js">
12
+ <link rel="modulepreload" crossorigin href="/assets/useSelectionMode-iqIyL24g.js">
13
+ <link rel="modulepreload" crossorigin href="/assets/Checkbox-COiFXiXL.js">
14
+ <link rel="stylesheet" crossorigin href="/assets/brand-CAMMU5lz.css">
15
15
  <link rel="stylesheet" crossorigin href="/assets/admin-CWMpccrR.css">
16
16
  <link rel="stylesheet" href="/brand-defaults.css">
17
17
  </head>
@@ -5,12 +5,12 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
6
  <title>Maxy</title>
7
7
  <link rel="icon" href="/favicon.ico">
8
- <script type="module" crossorigin src="/assets/public-UKgkJxKv.js"></script>
8
+ <script type="module" crossorigin src="/assets/public-BADBLzOZ.js"></script>
9
9
  <link rel="modulepreload" crossorigin href="/assets/chunk-DD-I1_y5.js">
10
- <link rel="modulepreload" crossorigin href="/assets/brand-SzZmKKjz.js">
11
- <link rel="modulepreload" crossorigin href="/assets/useSelectionMode-JDGvwvIk.js">
12
- <link rel="modulepreload" crossorigin href="/assets/Checkbox-DL-HdT29.js">
13
- <link rel="stylesheet" crossorigin href="/assets/brand-SZW-27T7.css">
10
+ <link rel="modulepreload" crossorigin href="/assets/brand-DfykKss4.js">
11
+ <link rel="modulepreload" crossorigin href="/assets/useSelectionMode-iqIyL24g.js">
12
+ <link rel="modulepreload" crossorigin href="/assets/Checkbox-COiFXiXL.js">
13
+ <link rel="stylesheet" crossorigin href="/assets/brand-CAMMU5lz.css">
14
14
  <link rel="stylesheet" href="/brand-defaults.css">
15
15
  </head>
16
16
  <body>
@@ -824,13 +824,10 @@ import { monitorEventLoopDelay } from "perf_hooks";
824
824
  // app/lib/agent-slug-pattern.ts
825
825
  var AGENT_SLUG_PATTERN = /^\/([a-z][a-z0-9-]{2,49})$/;
826
826
 
827
- // server/routes/health.ts
828
- import { existsSync as existsSync2, readFileSync as readFileSync4 } from "fs";
829
- import { createConnection as createConnection2 } from "net";
830
-
831
827
  // app/lib/claude-auth.ts
832
828
  import { readFileSync, writeFileSync } from "fs";
833
829
  import { sep } from "path";
830
+ import { lock } from "proper-lockfile";
834
831
  if (!CLAUDE_CREDENTIALS_FILE.startsWith(MAXY_DIR + sep)) {
835
832
  console.error(
836
833
  `[claude-auth] WARN cross-brand-path-detected resolved=${CLAUDE_CREDENTIALS_FILE} brand=${BRAND_NAME} maxyDir=${MAXY_DIR}`
@@ -929,11 +926,39 @@ function logRefreshOutcome(outcome) {
929
926
  );
930
927
  }
931
928
  async function doRefresh() {
929
+ const pre = readCredentials();
930
+ if (!pre) return { status: "missing" };
931
+ if (!pre.refreshToken) return { status: "dead", expiresAt: pre.expiresAt };
932
+ let release = null;
933
+ try {
934
+ release = await lock(CLAUDE_CREDENTIALS_FILE, {
935
+ retries: { retries: 8, minTimeout: 100, maxTimeout: 1e3, factor: 1.5 },
936
+ stale: 3e4
937
+ });
938
+ } catch (err) {
939
+ console.error(
940
+ `[auth-refresh] op=lock-acquire-failed err=${err instanceof Error ? err.message : String(err)}`
941
+ );
942
+ logRefreshOutcome("fail-network");
943
+ return { status: "expired", expiresAt: pre.expiresAt };
944
+ }
945
+ console.log(`[auth-refresh] op=lock-acquired pid=${process.pid} path=${CLAUDE_CREDENTIALS_FILE}`);
946
+ try {
947
+ return await doRefreshLocked();
948
+ } finally {
949
+ await release().catch(() => {
950
+ });
951
+ }
952
+ }
953
+ async function doRefreshLocked() {
932
954
  const creds = readCredentials();
933
955
  if (!creds) return { status: "missing" };
934
956
  if (!creds.refreshToken) return { status: "dead", expiresAt: creds.expiresAt };
935
957
  const now = Date.now();
936
958
  if (creds.expiresAt - now > EXPIRING_THRESHOLD_MS) {
959
+ console.log(
960
+ `[auth-refresh] op=skipped-fresh expiresAt=${new Date(creds.expiresAt).toISOString()}`
961
+ );
937
962
  return { status: "ok", expiresAt: creds.expiresAt };
938
963
  }
939
964
  let res;
@@ -989,13 +1014,26 @@ async function doRefresh() {
989
1014
  expiresIn: typeof expiresIn === "number" && expiresIn > 0 ? expiresIn : void 0
990
1015
  });
991
1016
  if (newExpiresAt) {
992
- console.log(`[auth-refresh] success, expires ${new Date(newExpiresAt).toISOString()}`);
1017
+ console.log(`[auth-refresh] op=renewed expiresAt=${new Date(newExpiresAt).toISOString()}`);
993
1018
  } else {
994
- console.warn("[auth-refresh] write-back failed");
1019
+ console.warn("[auth-refresh] op=renewed-write-back-failed");
995
1020
  }
996
1021
  logRefreshOutcome("ok");
997
1022
  return { status: "ok", expiresAt: newExpiresAt ?? Date.now() + 3600 * 1e3 };
998
1023
  }
1024
+ function startAuthHealthHeartbeat(intervalMs = 5 * 60 * 1e3) {
1025
+ const tick = () => {
1026
+ const h = computeAuthHealth();
1027
+ const expiresIn = h.expiresAt != null ? Math.round((h.expiresAt - Date.now()) / 1e3) : null;
1028
+ console.log(
1029
+ `[auth-health] brand=${BRAND_NAME} status=${h.status} expiresIn=${expiresIn != null ? expiresIn + "s" : "n/a"}`
1030
+ );
1031
+ };
1032
+ tick();
1033
+ const handle = setInterval(tick, intervalMs);
1034
+ if (typeof handle.unref === "function") handle.unref();
1035
+ return () => clearInterval(handle);
1036
+ }
999
1037
  async function ensureAuth() {
1000
1038
  const health = assessAuthHealth();
1001
1039
  if (health.status === "ok" || health.status === "missing" || health.status === "dead") {
@@ -1004,6 +1042,10 @@ async function ensureAuth() {
1004
1042
  return refreshAccessToken();
1005
1043
  }
1006
1044
 
1045
+ // server/routes/health.ts
1046
+ import { existsSync as existsSync2, readFileSync as readFileSync4 } from "fs";
1047
+ import { createConnection as createConnection2 } from "net";
1048
+
1007
1049
  // app/lib/network.ts
1008
1050
  import { networkInterfaces } from "os";
1009
1051
  function getLanIp() {
@@ -15161,6 +15203,7 @@ var port = requirePortEnv("MAXY_UI_INTERNAL_PORT", { tag: "ui-server" });
15161
15203
  var hostname = process.env.HOSTNAME ?? "127.0.0.1";
15162
15204
  var httpServer = serve({ fetch: app41.fetch, port, hostname });
15163
15205
  console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
15206
+ startAuthHealthHeartbeat();
15164
15207
  {
15165
15208
  const loopHist = monitorEventLoopDelay({ resolution: 50 });
15166
15209
  loopHist.enable();
@@ -1,109 +0,0 @@
1
- #!/usr/bin/env bash
2
- # Task 571 — the orchestration-only block has been removed. Admin's standard
3
- # tool surface (Write/Edit/MultiEdit/Bash/WebFetch/WebSearch) is callable
4
- # directly from the admin seat; delegation is a doctrine bias enforced in
5
- # IDENTITY.md, not a platform refusal. This test pins the post-571 contract:
6
- #
7
- # - Admin-direct Bash/Edit/Write/WebFetch all return exit 0 with no
8
- # `reason=orchestration-only` or `reason=specialist-owned` line.
9
- # - The resource-specific hook blocks (code-dir, entitlement, account.json,
10
- # pending-actions, base64-guard, approval-queue) are unaffected and live
11
- # in their own dedicated tests.
12
- #
13
- # Reintroducing the orchestration-only block requires a doctrine flip — this
14
- # test fails first and surfaces the change loudly.
15
-
16
- set -u
17
-
18
- HOOK="$(cd "$(dirname "$0")/.." && pwd)/pre-tool-use.sh"
19
- if [[ ! -x "$HOOK" ]]; then
20
- echo "FAIL: $HOOK not executable" >&2
21
- exit 1
22
- fi
23
-
24
- TMPFILES=()
25
- cleanup() {
26
- for f in "${TMPFILES[@]:-}"; do
27
- [[ -n "$f" ]] && rm -f "$f" 2>/dev/null || true
28
- done
29
- }
30
- trap cleanup EXIT
31
-
32
- PASS=0; FAIL=0
33
- pass() { PASS=$((PASS+1)); echo "PASS: $1"; }
34
- fail() { FAIL=$((FAIL+1)); echo "FAIL: $1" >&2; }
35
-
36
- SESSION_TRANSCRIPT="/tmp/sess/44283ae1-2b82-4bd7-b8af-99a11141ed4b.jsonl"
37
-
38
- # run_admin_direct <tool> <expected_rc> <label>
39
- # Pipes a minimal PreToolUse payload to the hook with MAXY_SESSION_ROLE=admin
40
- # (the realistic admin spawn shape) and asserts the rc and that no
41
- # orchestration-only / specialist-owned refusal line appears on stderr.
42
- run_admin_direct() {
43
- local tool="$1" expected_rc="$2" label="$3"
44
- local input_json
45
- input_json=$(python3 -c '
46
- import json, sys
47
- print(json.dumps({
48
- "hook_event_name": "PreToolUse",
49
- "tool_name": sys.argv[1],
50
- "tool_input": {"file_path": "/tmp/x", "content": "x", "command": "echo hi", "url": "https://example.com"},
51
- "transcript_path": sys.argv[2],
52
- }, separators=(",", ":")))
53
- ' "$tool" "$SESSION_TRANSCRIPT")
54
- local stdout_file; stdout_file=$(mktemp); TMPFILES+=("$stdout_file")
55
- local stderr_file; stderr_file=$(mktemp); TMPFILES+=("$stderr_file")
56
- printf '%s' "$input_json" | \
57
- env -u MAXY_SPECIALIST MAXY_SESSION_ROLE=admin \
58
- bash "$HOOK" admin >"$stdout_file" 2>"$stderr_file"
59
- local rc=$?
60
- if [[ "$rc" -ne "$expected_rc" ]]; then
61
- fail "$label: expected exit $expected_rc, got $rc. Stderr: $(cat "$stderr_file")"
62
- return
63
- fi
64
- if grep -qE 'reason=orchestration-only|reason=specialist-owned|reason=missing-marker' "$stderr_file"; then
65
- fail "$label: stderr carries a removed-gate reason line. Got: $(cat "$stderr_file")"
66
- return
67
- fi
68
- pass "$label"
69
- }
70
-
71
- # ── Admin-direct passthrough (the Task 571 regression boundary) ──────────
72
- run_admin_direct Bash 0 "Test 1: admin-direct Bash passes through"
73
- run_admin_direct Edit 0 "Test 2: admin-direct Edit passes through (path /tmp/x is outside protected dirs)"
74
- run_admin_direct Write 0 "Test 3: admin-direct Write passes through"
75
- run_admin_direct MultiEdit 0 "Test 4: admin-direct MultiEdit passes through"
76
- run_admin_direct WebFetch 0 "Test 5: admin-direct WebFetch passes through"
77
- run_admin_direct WebSearch 0 "Test 6: admin-direct WebSearch passes through"
78
- run_admin_direct Read 0 "Test 7: admin-direct Read passes through"
79
-
80
- # ── Resource-specific blocks unaffected: protected code-dir Write still refuses ──
81
- # Verifies the deletion was surgical — code-dir-protection-ui still fires.
82
- PROTECTED_INPUT=$(python3 -c '
83
- import json, sys
84
- print(json.dumps({
85
- "hook_event_name": "PreToolUse",
86
- "tool_name": "Write",
87
- "tool_input": {"file_path": "/srv/maxy/platform/ui/app/page.tsx", "content": "x"},
88
- "transcript_path": sys.argv[1],
89
- }, separators=(",", ":")))
90
- ' "$SESSION_TRANSCRIPT")
91
- PROT_STDOUT=$(mktemp); TMPFILES+=("$PROT_STDOUT")
92
- PROT_STDERR=$(mktemp); TMPFILES+=("$PROT_STDERR")
93
- printf '%s' "$PROTECTED_INPUT" | \
94
- env -u MAXY_SPECIALIST MAXY_SESSION_ROLE=admin \
95
- bash "$HOOK" admin >"$PROT_STDOUT" 2>"$PROT_STDERR"
96
- PROT_RC=$?
97
- if [[ "$PROT_RC" -eq 2 ]] && grep -q 'cannot modify platform UI source' "$PROT_STDERR"; then
98
- pass "Test 8: admin-direct Write to platform/ui/ still blocked (resource-specific block intact)"
99
- else
100
- fail "Test 8: expected platform/ui code-dir block, got rc=$PROT_RC stdout=$(cat "$PROT_STDOUT") stderr=$(cat "$PROT_STDERR")"
101
- fi
102
-
103
- echo
104
- echo "──────── pre-tool-use admin-tool-gate test summary (post-Task 571) ────────"
105
- echo "PASS: $PASS"
106
- echo "FAIL: $FAIL"
107
-
108
- [[ "$FAIL" -gt 0 ]] && exit 1
109
- exit 0
@@ -1,204 +0,0 @@
1
- #!/usr/bin/env bash
2
- # Regression test for the base64 context-overflow guard.
3
- #
4
- # Covers two PreToolUse rejection paths in pre-tool-use.sh:
5
- #
6
- # 1. Bash producer guard — `tool_input.command` invoking `base64` (encode
7
- # direction) or `xxd -p` is rejected; `base64 -d|-D|--decode` is allowed.
8
- # 2. Write/Edit consumer guard — `tool_input.content` (Write) or
9
- # `tool_input.new_string` (Edit) carrying `data:<mime>;base64,<≥4096 chars>`
10
- # is rejected; small inline data URIs and plain content are allowed.
11
- #
12
- # Plus a fail-open case (malformed stdin → exit 0 silent) to pin the contract
13
- # established by the playwright-file-guard test (terminal-stdin guard + parse-
14
- # error fail-open).
15
-
16
- set -u
17
-
18
- HOOK="$(cd "$(dirname "$0")/.." && pwd)/pre-tool-use.sh"
19
- if [[ ! -x "$HOOK" ]]; then
20
- echo "FAIL: $HOOK not executable" >&2
21
- exit 1
22
- fi
23
-
24
- TMPFILES=()
25
- cleanup_test_state() {
26
- for f in "${TMPFILES[@]:-}"; do
27
- [[ -n "$f" ]] && rm -f "$f" 2>/dev/null || true
28
- done
29
- }
30
- trap cleanup_test_state EXIT
31
-
32
- PASS=0
33
- FAIL=0
34
- pass() { echo "PASS: $1"; PASS=$((PASS + 1)); }
35
- fail() { echo "FAIL: $1" >&2; FAIL=$((FAIL + 1)); }
36
-
37
- # Helper: run hook with Bash tool_input.command and assert exit code + stderr.
38
- run_bash() {
39
- local command_text="$1"; local expected_rc="$2"; local stderr_pattern="$3"; local label="$4"
40
- local input_json
41
- # Build the input JSON via python3 so command_text with quotes / specials is safe.
42
- input_json=$(python3 -c '
43
- import json, sys
44
- print(json.dumps({"hook_event_name": "PreToolUse", "tool_name": "Bash", "tool_input": {"command": sys.argv[1]}, "transcript_path": "/tmp/sess/parent.jsonl", "parent_tool_use_id": "toolu_test_subagent"}, separators=(",", ":")))
45
- ' "$command_text")
46
- local stdout_file; stdout_file=$(mktemp); TMPFILES+=("$stdout_file")
47
- local stderr_file; stderr_file=$(mktemp); TMPFILES+=("$stderr_file")
48
- printf '%s' "$input_json" | bash "$HOOK" admin >"$stdout_file" 2>"$stderr_file"
49
- local rc=$?
50
- if [[ "$rc" -ne "$expected_rc" ]]; then
51
- fail "$label: expected exit $expected_rc, got $rc. Stderr: $(cat "$stderr_file")"
52
- return
53
- fi
54
- if [[ -n "$stderr_pattern" ]] && ! grep -qE "$stderr_pattern" "$stderr_file"; then
55
- fail "$label: stderr missing pattern '$stderr_pattern'. Got: $(cat "$stderr_file")"
56
- return
57
- fi
58
- pass "$label"
59
- }
60
-
61
- # Helper: run hook with Write tool_input.content and assert exit code + stderr.
62
- run_write() {
63
- local content="$1"; local expected_rc="$2"; local stderr_pattern="$3"; local label="$4"
64
- local input_json
65
- input_json=$(python3 -c '
66
- import json, sys
67
- print(json.dumps({"hook_event_name": "PreToolUse", "tool_name": "Write", "tool_input": {"file_path": "/tmp/test.html", "content": sys.argv[1]}, "transcript_path": "/tmp/sess/parent.jsonl", "parent_tool_use_id": "toolu_test_subagent"}, separators=(",", ":")))
68
- ' "$content")
69
- local stdout_file; stdout_file=$(mktemp); TMPFILES+=("$stdout_file")
70
- local stderr_file; stderr_file=$(mktemp); TMPFILES+=("$stderr_file")
71
- printf '%s' "$input_json" | bash "$HOOK" admin >"$stdout_file" 2>"$stderr_file"
72
- local rc=$?
73
- if [[ "$rc" -ne "$expected_rc" ]]; then
74
- fail "$label: expected exit $expected_rc, got $rc. Stderr: $(cat "$stderr_file")"
75
- return
76
- fi
77
- if [[ -n "$stderr_pattern" ]] && ! grep -qE "$stderr_pattern" "$stderr_file"; then
78
- fail "$label: stderr missing pattern '$stderr_pattern'. Got: $(cat "$stderr_file")"
79
- return
80
- fi
81
- pass "$label"
82
- }
83
-
84
- # Helper: run hook with Edit tool_input.new_string and assert exit code.
85
- run_edit() {
86
- local new_string="$1"; local expected_rc="$2"; local stderr_pattern="$3"; local label="$4"
87
- local input_json
88
- input_json=$(python3 -c '
89
- import json, sys
90
- print(json.dumps({"hook_event_name": "PreToolUse", "tool_name": "Edit", "tool_input": {"file_path": "/tmp/test.html", "old_string": "OLD", "new_string": sys.argv[1]}, "transcript_path": "/tmp/sess/parent.jsonl", "parent_tool_use_id": "toolu_test_subagent"}, separators=(",", ":")))
91
- ' "$new_string")
92
- local stdout_file; stdout_file=$(mktemp); TMPFILES+=("$stdout_file")
93
- local stderr_file; stderr_file=$(mktemp); TMPFILES+=("$stderr_file")
94
- printf '%s' "$input_json" | bash "$HOOK" admin >"$stdout_file" 2>"$stderr_file"
95
- local rc=$?
96
- if [[ "$rc" -ne "$expected_rc" ]]; then
97
- fail "$label: expected exit $expected_rc, got $rc. Stderr: $(cat "$stderr_file")"
98
- return
99
- fi
100
- if [[ -n "$stderr_pattern" ]] && ! grep -qE "$stderr_pattern" "$stderr_file"; then
101
- fail "$label: stderr missing pattern '$stderr_pattern'. Got: $(cat "$stderr_file")"
102
- return
103
- fi
104
- pass "$label"
105
- }
106
-
107
- # Generate a base64-character blob >= 4096 chars (data-URI body trigger).
108
- LARGE_B64=$(python3 -c "print('A' * 5000)")
109
-
110
- # ───────── Bash producer guard ──────────────────────────────────────────────
111
- run_bash "echo hello world" 0 "" \
112
- "Test 1: bare Bash command (no base64) allowed"
113
-
114
- run_bash "ls -la" 0 "" \
115
- "Test 2: ls -la (no base64 token) allowed"
116
-
117
- run_bash "base64 /tmp/foo.png" 2 '\[pre-tool-use\] guard=base64-tool-result.*tool=Bash.*reason=base64-encoder.*action=reject' \
118
- "Test 3: 'base64 file' (encode) rejected"
119
-
120
- run_bash "cat foo.png | base64" 2 '\[pre-tool-use\] guard=base64-tool-result.*action=reject' \
121
- "Test 4: 'cat | base64' (encode pipeline) rejected"
122
-
123
- run_bash "cat foo.png|base64 -w0" 2 '\[pre-tool-use\] guard=base64-tool-result.*action=reject' \
124
- "Test 5: 'base64 -w0' (encode with line-wrap flag) rejected"
125
-
126
- run_bash "xxd -p file.bin" 2 '\[pre-tool-use\] guard=base64-tool-result.*reason=xxd-plain-hex.*action=reject' \
127
- "Test 6: 'xxd -p' (plain hex encode) rejected"
128
-
129
- run_bash "base64 -d input.b64 > output.bin" 0 "" \
130
- "Test 7: 'base64 -d' (decode direction) allowed"
131
-
132
- run_bash "base64 --decode < x.b64 > y.bin" 0 "" \
133
- "Test 8: 'base64 --decode' (decode long-form) allowed"
134
-
135
- run_bash "echo Zm9v | base64 -d" 0 "" \
136
- "Test 9: 'base64 -d' decode pipeline allowed"
137
-
138
- run_bash "echo '--debug-base64-foo'" 0 "" \
139
- "Test 10: 'base64' substring inside flag name does NOT false-match"
140
-
141
- run_bash "ls mybase64tool" 0 "" \
142
- "Test 11: 'base64' substring inside identifier does NOT false-match"
143
-
144
- run_bash "cat in.b64 | base64 -d > /tmp/foo.bin; cat /tmp/bar.png | base64" 2 '\[pre-tool-use\] guard=base64-tool-result.*reason=base64-encoder.*action=reject' \
145
- "Test 11b: compound (decode ; encode) rejects encoder segment (per-segment scan)"
146
-
147
- run_bash "echo data | base64 -d > x.bin && cat y.png | base64 > y.b64" 2 '\[pre-tool-use\] guard=base64-tool-result.*action=reject' \
148
- "Test 11c: compound (decode && encode) rejects encoder segment"
149
-
150
- run_bash "base64 -d in.b64 > out.bin; base64 -d in2.b64 > out2.bin" 0 "" \
151
- "Test 11d: compound (decode ; decode) allowed"
152
-
153
- # ───────── Write/Edit consumer guard ────────────────────────────────────────
154
- run_write "<html><body>hello world</body></html>" 0 "" \
155
- "Test 12: Write small HTML content (no data URI) allowed"
156
-
157
- run_write "<img src='data:image/png;base64,AAAA'>" 0 "" \
158
- "Test 13: Write content with small inline data URI (<4096 chars) allowed"
159
-
160
- run_write "<img src='data:image/png;base64,${LARGE_B64}'>" 2 '\[pre-tool-use\] guard=base64-write-content.*action=reject' \
161
- "Test 14: Write content with large inline data URI (>4096 chars) rejected"
162
-
163
- run_edit "<img src='data:image/png;base64,${LARGE_B64}'>" 2 '\[pre-tool-use\] guard=base64-write-content.*action=reject' \
164
- "Test 15: Edit new_string with large inline data URI rejected"
165
-
166
- run_edit "<p>just text replacement</p>" 0 "" \
167
- "Test 16: Edit new_string with plain text allowed"
168
-
169
- # ───────── Fail-open / structural ───────────────────────────────────────────
170
- STDOUT_FILE=$(mktemp); STDERR_FILE=$(mktemp); TMPFILES+=("$STDOUT_FILE" "$STDERR_FILE")
171
- printf '%s' 'not json at all { ' | bash "$HOOK" admin >"$STDOUT_FILE" 2>"$STDERR_FILE"
172
- RC=$?
173
- if [[ "$RC" -ne 0 ]]; then
174
- fail "Test 17: malformed stdin should fail open (exit 0), got $RC. Stderr: $(cat "$STDERR_FILE")"
175
- else
176
- pass "Test 17: malformed stdin → silent passthrough (fail-open)"
177
- fi
178
-
179
- # Terminal-stdin guard preserved (no -t 0 test runs in test harness; assert
180
- # the guard line exists in source).
181
- if ! grep -q '\[ -t 0 \]' "$HOOK"; then
182
- fail "Test 18: terminal stdin guard missing from hook source"
183
- else
184
- pass "Test 18: terminal stdin guard present in source"
185
- fi
186
-
187
- # Pre-existing guards still active — entitlement file edit still rejected.
188
- ENT_JSON=$(python3 -c 'import json; print(json.dumps({"hook_event_name":"PreToolUse","tool_name":"Write","tool_input":{"file_path":"/srv/entitlement.json","content":"{\"tier\":\"max\"}"},"transcript_path":"/tmp/sess/parent.jsonl","parent_tool_use_id":"toolu_test_subagent"}, separators=(",", ":")))')
189
- STDOUT_FILE=$(mktemp); STDERR_FILE=$(mktemp); TMPFILES+=("$STDOUT_FILE" "$STDERR_FILE")
190
- printf '%s' "$ENT_JSON" | bash "$HOOK" admin >"$STDOUT_FILE" 2>"$STDERR_FILE"
191
- RC=$?
192
- if [[ "$RC" -ne 2 ]]; then
193
- fail "Test 19: pre-existing entitlement guard regressed (expected exit 2, got $RC)"
194
- else
195
- pass "Test 19: pre-existing entitlement guard still rejects entitlement.json"
196
- fi
197
-
198
- echo
199
- echo "──────── pre-tool-use base64 guard test summary ────────"
200
- echo "PASS: $PASS"
201
- echo "FAIL: $FAIL"
202
-
203
- [[ "$FAIL" -gt 0 ]] && exit 1
204
- exit 0
@@ -1,118 +0,0 @@
1
- #!/usr/bin/env bash
2
- # Task 225 — passthrough regression test for memory-write / memory-update
3
- # under the admin agent's pre-tool-use.sh.
4
- #
5
- # The Task 213→222 chain previously gated both writers at the top of the
6
- # admin branch with an exit-2 block (admin direct) and an exit-0 allow
7
- # (Task-subagent). Task 225 reversed the doctrine: admin holds the
8
- # writers in its own surface and the case-block was deleted. This test
9
- # pins the new behaviour so a future re-introduction of the block
10
- # cannot land silently.
11
- #
12
- # Five cases:
13
- # 1. mcp__plugin_memory_memory__memory-write admin-direct → exit 0, no rejection stderr
14
- # 2. mcp__plugin_memory_memory__memory-update admin-direct → exit 0, no rejection stderr
15
- # 3. mcp__plugin_memory_memory__memory-search admin-direct → exit 0, no rejection stderr (read tool control)
16
- # 4. mcp__memory__memory-write admin-direct → exit 0 (pre-209 namespace, falls through harmlessly)
17
- # 5. malformed JSON stdin → exit 0 (no crash; case-block removal does not break fall-through)
18
-
19
- set -u
20
-
21
- HOOK="$(cd "$(dirname "$0")/.." && pwd)/pre-tool-use.sh"
22
- if [[ ! -x "$HOOK" ]]; then
23
- echo "FAIL: $HOOK not executable" >&2
24
- exit 1
25
- fi
26
-
27
- TMPFILES=()
28
- cleanup() {
29
- for f in "${TMPFILES[@]:-}"; do
30
- [[ -n "$f" ]] && rm -f "$f" 2>/dev/null || true
31
- done
32
- }
33
- trap cleanup EXIT
34
-
35
- PASS=0
36
- FAIL=0
37
- pass() { echo "PASS: $1"; PASS=$((PASS + 1)); }
38
- fail() { echo "FAIL: $1" >&2; FAIL=$((FAIL + 1)); }
39
-
40
- # Admin-direct calls land with a non-subagent transcript path. A synthetic
41
- # empty file in a temp dir is enough — the hook no longer reads the path
42
- # for the deleted writers' case-block, but other gates may still touch it.
43
- ADMIN_TRANSCRIPT=$(mktemp); TMPFILES+=("$ADMIN_TRANSCRIPT")
44
- : > "$ADMIN_TRANSCRIPT"
45
-
46
- run_with_tool() {
47
- local tool="$1"
48
- local sid="$2"
49
- local transcript_path="${3:-}"
50
- local stdout_file; stdout_file=$(mktemp); TMPFILES+=("$stdout_file")
51
- local stderr_file; stderr_file=$(mktemp); TMPFILES+=("$stderr_file")
52
- python3 -c '
53
- import json, sys
54
- payload = {
55
- "hook_event_name": "PreToolUse",
56
- "session_id": sys.argv[1],
57
- "tool_name": sys.argv[2],
58
- "tool_input": {"name":"Smalleys","accountId":"acct-x","scope":"shared"},
59
- }
60
- if sys.argv[3]:
61
- payload["transcript_path"] = sys.argv[3]
62
- print(json.dumps(payload, separators=(",", ":")))
63
- ' "$sid" "$tool" "$transcript_path" | bash "$HOOK" admin >"$stdout_file" 2>"$stderr_file"
64
- HOOK_RC=$?
65
- HOOK_STDERR=$(cat "$stderr_file")
66
- }
67
-
68
- assert_passthrough() {
69
- local label="$1"
70
- if [[ "$HOOK_RC" -ne 0 ]]; then
71
- fail "${label}: expected exit 0, got $HOOK_RC stderr: $HOOK_STDERR"
72
- return
73
- fi
74
- if echo "$HOOK_STDERR" | grep -qF "does not write to the graph directly"; then
75
- fail "${label}: stderr carries deleted-block rejection message: $HOOK_STDERR"
76
- return
77
- fi
78
- pass "${label}: exit 0, no rejection stderr"
79
- }
80
-
81
- # --- 1. memory-write admin-direct passthrough ---------------------------
82
- run_with_tool "mcp__plugin_memory_memory__memory-write" "sess-write-1" "$ADMIN_TRANSCRIPT"
83
- assert_passthrough "admin-direct memory-write"
84
-
85
- # --- 2. memory-update admin-direct passthrough --------------------------
86
- run_with_tool "mcp__plugin_memory_memory__memory-update" "sess-update-1" "$ADMIN_TRANSCRIPT"
87
- assert_passthrough "admin-direct memory-update"
88
-
89
- # --- 3. memory-search admin-direct passthrough (read tool control) -----
90
- run_with_tool "mcp__plugin_memory_memory__memory-search" "sess-search-1" "$ADMIN_TRANSCRIPT"
91
- assert_passthrough "admin-direct memory-search (read tool control)"
92
-
93
- # --- 4. pre-209 namespace mcp__memory__memory-write ---------------------
94
- # The bare mcp__memory__ namespace was never in the admin runtime surface
95
- # after Task 203's plugin_ prefix rename; this case pins that it still
96
- # falls through harmlessly after the case-block removal.
97
- run_with_tool "mcp__memory__memory-write" "sess-old-ns-1" "$ADMIN_TRANSCRIPT"
98
- assert_passthrough "pre-209 namespace mcp__memory__memory-write"
99
-
100
- # --- 5. malformed JSON stdin — hook must not crash ----------------------
101
- stderr_file=$(mktemp); TMPFILES+=("$stderr_file")
102
- stdout_file=$(mktemp); TMPFILES+=("$stdout_file")
103
- echo "not-valid-json{{{" | bash "$HOOK" admin >"$stdout_file" 2>"$stderr_file"
104
- HOOK_RC=$?
105
- HOOK_STDERR=$(cat "$stderr_file")
106
- if [[ "$HOOK_RC" -ne 0 ]]; then
107
- fail "malformed payload: expected exit 0, got $HOOK_RC stderr: $HOOK_STDERR"
108
- elif echo "$HOOK_STDERR" | grep -qF "does not write to the graph directly"; then
109
- fail "malformed payload: stderr carries deleted-block rejection message: $HOOK_STDERR"
110
- else
111
- pass "malformed payload: exit 0, hook does not crash"
112
- fi
113
-
114
- # --- Summary -----------------------------------------------------------
115
- echo "---"
116
- echo "PASSED: $PASS FAILED: $FAIL"
117
- [[ "$FAIL" -eq 0 ]] || exit 1
118
- exit 0