@rubytech/create-maxy-code 0.1.488 → 0.1.490
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/payload/platform/docs/superpowers/plans/2026-07-22-task-1899-reconcile-report-only.md +748 -0
- package/payload/platform/docs/superpowers/plans/2026-07-22-task-1910-two-way-portal-exchange.md +1716 -0
- package/payload/platform/docs/superpowers/specs/2026-07-22-task-1899-reconcile-report-only-design.md +121 -0
- package/payload/platform/docs/superpowers/specs/2026-07-22-task-1910-two-way-portal-exchange-design.md +103 -0
- package/payload/platform/lib/graph-mcp/dist/__tests__/schema-cypher-parser.test.js +51 -0
- package/payload/platform/lib/graph-mcp/dist/__tests__/schema-cypher-parser.test.js.map +1 -1
- package/payload/platform/lib/graph-mcp/dist/schema-cypher-parser.d.ts +2 -2
- package/payload/platform/lib/graph-mcp/dist/schema-cypher-parser.d.ts.map +1 -1
- package/payload/platform/lib/graph-mcp/dist/schema-cypher-parser.js +20 -4
- package/payload/platform/lib/graph-mcp/dist/schema-cypher-parser.js.map +1 -1
- package/payload/platform/lib/graph-mcp/src/__tests__/schema-cypher-parser.test.ts +55 -0
- package/payload/platform/lib/graph-mcp/src/schema-cypher-parser.ts +19 -4
- package/payload/platform/lib/graph-style/dist/index.d.ts +24 -0
- package/payload/platform/lib/graph-style/dist/index.d.ts.map +1 -1
- package/payload/platform/lib/graph-style/dist/index.js +25 -1
- package/payload/platform/lib/graph-style/dist/index.js.map +1 -1
- package/payload/platform/lib/graph-style/src/__tests__/additional-base-labels.test.ts +22 -0
- package/payload/platform/lib/graph-style/src/index.ts +27 -0
- package/payload/platform/lib/storage-broker/dist/__tests__/audit.test.js +15 -0
- package/payload/platform/lib/storage-broker/dist/__tests__/audit.test.js.map +1 -1
- package/payload/platform/lib/storage-broker/dist/audit.d.ts +34 -1
- package/payload/platform/lib/storage-broker/dist/audit.d.ts.map +1 -1
- package/payload/platform/lib/storage-broker/dist/audit.js +28 -4
- package/payload/platform/lib/storage-broker/dist/audit.js.map +1 -1
- package/payload/platform/lib/storage-broker/dist/cf-exec.d.ts +13 -0
- package/payload/platform/lib/storage-broker/dist/cf-exec.d.ts.map +1 -1
- package/payload/platform/lib/storage-broker/dist/cf-exec.js +22 -0
- package/payload/platform/lib/storage-broker/dist/cf-exec.js.map +1 -1
- package/payload/platform/lib/storage-broker/src/__tests__/audit.test.ts +21 -0
- package/payload/platform/lib/storage-broker/src/audit.ts +67 -3
- package/payload/platform/lib/storage-broker/src/cf-exec.ts +48 -0
- package/payload/platform/neo4j/schema.cypher +105 -0
- package/payload/platform/plugins/admin/skills/agent-builder/references/agent-pattern.md +34 -5
- package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +8 -2
- package/payload/platform/plugins/cloudflare/PLUGIN.md +22 -2
- package/payload/platform/plugins/cloudflare/bin/portal-index-push.mjs +161 -41
- package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-delete-withdraw.test.ts +95 -0
- package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-directory-listing.test.ts +75 -13
- package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-index-push.test.ts +216 -16
- package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-indexed-download.test.ts +38 -2
- package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-moved-download.test.ts +83 -0
- package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-schema-shape.test.ts +48 -0
- package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-sign.test.ts +62 -0
- package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-target.test.ts +103 -0
- package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-upload-multipart.test.ts +211 -0
- package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-upload-target.test.ts +168 -0
- package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-uploads-listing.test.ts +138 -0
- package/payload/platform/plugins/cloudflare/mcp/__tests__/template-config.test.ts +179 -0
- package/payload/platform/plugins/cloudflare/mcp/__tests__/upload-route.test.ts +14 -13
- package/payload/platform/plugins/cloudflare/skills/data-portal/SKILL.md +87 -13
- package/payload/platform/plugins/cloudflare/skills/data-portal/schema.sql +87 -9
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/_lib/portal-sign.mjs +74 -0
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/_lib/target.ts +91 -0
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/_lib/types.ts +23 -0
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/delete.ts +45 -0
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/download.ts +72 -5
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/files.ts +80 -13
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/upload.ts +358 -16
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/index.html +12 -1
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/portal.css +45 -0
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/portal.js +258 -21
- package/payload/platform/plugins/docs/references/calendar-booking.md +5 -1
- package/payload/platform/plugins/docs/references/internals.md +2 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-cypher-drift.test.js +61 -31
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-cypher-drift.test.js.map +1 -1
- package/payload/platform/plugins/scheduling/PLUGIN.md +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/index.js +2 -2
- package/payload/platform/plugins/scheduling/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/booking-reconcile.test.js +70 -5
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/booking-reconcile.test.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/getUserTimezone.test.js +31 -6
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/getUserTimezone.test.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/lib/booking-reconcile.d.ts +31 -4
- package/payload/platform/plugins/scheduling/mcp/dist/lib/booking-reconcile.d.ts.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/lib/booking-reconcile.js +18 -7
- package/payload/platform/plugins/scheduling/mcp/dist/lib/booking-reconcile.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/lib/neo4j.d.ts +15 -6
- package/payload/platform/plugins/scheduling/mcp/dist/lib/neo4j.d.ts.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/lib/neo4j.js +24 -11
- package/payload/platform/plugins/scheduling/mcp/dist/lib/neo4j.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.js +5 -122
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/reconcile-bookings.js +84 -5
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/reconcile-bookings.js.map +1 -1
- package/payload/platform/scripts/__tests__/quarantine-restore.test.sh +143 -0
- package/payload/platform/scripts/quarantine-restore.mjs +175 -0
- package/payload/platform/services/claude-session-manager/dist/account-dir-schema-reconcile.d.ts +24 -7
- package/payload/platform/services/claude-session-manager/dist/account-dir-schema-reconcile.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/account-dir-schema-reconcile.js +52 -74
- package/payload/platform/services/claude-session-manager/dist/account-dir-schema-reconcile.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/index.js +11 -8
- package/payload/platform/services/claude-session-manager/dist/index.js.map +1 -1
- package/payload/platform/services/whatsapp-channel/dist/notification.d.ts +32 -0
- package/payload/platform/services/whatsapp-channel/dist/notification.d.ts.map +1 -1
- package/payload/platform/services/whatsapp-channel/dist/notification.js +32 -2
- package/payload/platform/services/whatsapp-channel/dist/notification.js.map +1 -1
- package/payload/platform/services/whatsapp-channel/dist/targets.d.ts +6 -1
- package/payload/platform/services/whatsapp-channel/dist/targets.d.ts.map +1 -1
- package/payload/platform/services/whatsapp-channel/dist/targets.js +6 -0
- package/payload/platform/services/whatsapp-channel/dist/targets.js.map +1 -1
- package/payload/platform/templates/account-schema/SCHEMA.md +7 -4
- package/payload/platform/templates/agents/admin/IDENTITY.md +1 -1
- package/payload/platform/templates/agents/passive/IDENTITY.md +6 -0
- package/payload/platform/templates/agents/public/IDENTITY.md +6 -0
- package/payload/platform/templates/specialists/agents/citation-auditor.md +6 -0
- package/payload/platform/templates/specialists/agents/coding-assistant.md +6 -0
- package/payload/platform/templates/specialists/agents/compiled-truth-rewriter.md +6 -0
- package/payload/platform/templates/specialists/agents/content-producer.md +6 -0
- package/payload/platform/templates/specialists/agents/data-manager.md +6 -0
- package/payload/platform/templates/specialists/agents/database-operator.md +6 -0
- package/payload/platform/templates/specialists/agents/librarian.md +6 -0
- package/payload/platform/templates/specialists/agents/personal-assistant.md +6 -0
- package/payload/platform/templates/specialists/agents/project-manager.md +6 -0
- package/payload/platform/templates/specialists/agents/public-session-reviewer.md +6 -0
- package/payload/platform/templates/specialists/agents/research-assistant.md +7 -1
- package/payload/platform/templates/specialists/agents/typed-edge-classifier.md +6 -0
- package/payload/premium-plugins/writer-craft/agents/writer-craft--manuscript-reviewer.md +6 -0
- package/payload/server/{chunk-REWXLAPC.js → chunk-KZFE3MOY.js} +52 -4
- package/payload/server/public/activity.html +5 -6
- package/payload/server/public/assets/AdminLoginScreens-DgVFsHtd.js +1 -0
- package/payload/server/public/assets/AdminShell-BwNqwb4k.js +2 -0
- package/payload/server/public/assets/activity-2DL2DwLr.js +1 -0
- package/payload/server/public/assets/admin-COe0dT2U.js +1 -0
- package/payload/server/public/assets/{arc-D-QzyndF.js → arc-CFMJyMm-.js} +1 -1
- package/payload/server/public/assets/architecture-YZFGNWBL-Cypt5qHL.js +1 -0
- package/payload/server/public/assets/{architectureDiagram-Q4EWVU46-W7f0Us4S.js → architectureDiagram-Q4EWVU46-CiSSgdL8.js} +1 -1
- package/payload/server/public/assets/{blockDiagram-DXYQGD6D-CfPS4FXg.js → blockDiagram-DXYQGD6D-_sXtIW5c.js} +1 -1
- package/payload/server/public/assets/{bot-DnuyRsLS.js → bot-CpBuDQlF.js} +1 -1
- package/payload/server/public/assets/{browser-q4EFqKvR.js → browser-CSApuZrv.js} +1 -1
- package/payload/server/public/assets/{c4Diagram-AHTNJAMY-Dq60cLVI.js → c4Diagram-AHTNJAMY-Bh7OAMXs.js} +1 -1
- package/payload/server/public/assets/{calendar-C-q9kWmA.js → calendar-DlvwZnS7.js} +1 -1
- package/payload/server/public/assets/channel-BMyXE4zc.js +1 -0
- package/payload/server/public/assets/chat-DWvYv9jq.js +1 -0
- package/payload/server/public/assets/chevron-down-Dnuuw6Q2.js +1 -0
- package/payload/server/public/assets/chevron-left-CC4zmWOr.js +1 -0
- package/payload/server/public/assets/chevron-right-CG_lHTx_.js +1 -0
- package/payload/server/public/assets/{chunk-2KRD3SAO-CXDjuDN0.js → chunk-2KRD3SAO-D4fktAwW.js} +1 -1
- package/payload/server/public/assets/{chunk-336JU56O-926ox0tj.js → chunk-336JU56O-CcQoKH2G.js} +2 -2
- package/payload/server/public/assets/chunk-426QAEUC-Xu6JKP4d.js +1 -0
- package/payload/server/public/assets/{chunk-4BX2VUAB-C2nrPCfN.js → chunk-4BX2VUAB-C13Wz3zS.js} +1 -1
- package/payload/server/public/assets/{chunk-4TB4RGXK-CuW3FOR6.js → chunk-4TB4RGXK-BL48D7gN.js} +1 -1
- package/payload/server/public/assets/{chunk-55IACEB6-B4_-In90.js → chunk-55IACEB6-TmOKdy8a.js} +1 -1
- package/payload/server/public/assets/{chunk-5FUZZQ4R-DnYGr9CP.js → chunk-5FUZZQ4R-DZ_jSF6N.js} +1 -1
- package/payload/server/public/assets/{chunk-5PVQY5BW-BYCOi9bf.js → chunk-5PVQY5BW-De4G1vfV.js} +1 -1
- package/payload/server/public/assets/{chunk-67CJDMHE-CBxsC4JP.js → chunk-67CJDMHE-Cif9Uu5C.js} +1 -1
- package/payload/server/public/assets/{chunk-7N4EOEYR-CAACaqFr.js → chunk-7N4EOEYR-WjTHCXnd.js} +1 -1
- package/payload/server/public/assets/{chunk-AA7GKIK3-BoamZdS6.js → chunk-AA7GKIK3-CwCfdQmb.js} +1 -1
- package/payload/server/public/assets/{chunk-BSJP7CBP-CHk82eFy.js → chunk-BSJP7CBP-9A67yXwT.js} +1 -1
- package/payload/server/public/assets/{chunk-CIAEETIT-DdeW9zxl.js → chunk-CIAEETIT-DPMiWSBi.js} +1 -1
- package/payload/server/public/assets/{chunk-EDXVE4YY-DJqSRv2w.js → chunk-EDXVE4YY-C2f_Qvx-.js} +1 -1
- package/payload/server/public/assets/{chunk-ENJZ2VHE-BRi4BwM5.js → chunk-ENJZ2VHE-B-Q9EbSQ.js} +1 -1
- package/payload/server/public/assets/{chunk-FMBD7UC4-OJp2CtUE.js → chunk-FMBD7UC4-Bmt_ZRlV.js} +1 -1
- package/payload/server/public/assets/{chunk-FOC6F5B3-BUAQ6spG.js → chunk-FOC6F5B3-DfGJGxpX.js} +1 -1
- package/payload/server/public/assets/{chunk-ICPOFSXX-DeRggD-Q.js → chunk-ICPOFSXX-Wyym1Q8K.js} +2 -2
- package/payload/server/public/assets/{chunk-K5T4RW27-EHiXZ8Py.js → chunk-K5T4RW27-DMpoaqw2.js} +1 -1
- package/payload/server/public/assets/{chunk-KGLVRYIC-tOajPcpn.js → chunk-KGLVRYIC-DYUFgri2.js} +1 -1
- package/payload/server/public/assets/{chunk-LIHQZDEY-uwO6oHIU.js → chunk-LIHQZDEY-x3LmeKmx.js} +1 -1
- package/payload/server/public/assets/{chunk-ORNJ4GCN-Dpa1_BCA.js → chunk-ORNJ4GCN-BeuNAWkW.js} +1 -1
- package/payload/server/public/assets/{chunk-OYMX7WX6-ChdDqaQL.js → chunk-OYMX7WX6-BUWVAfQA.js} +1 -1
- package/payload/server/public/assets/chunk-QZHKN3VN-7_kS3RBy.js +1 -0
- package/payload/server/public/assets/{chunk-U2HBQHQK-5JgWosPv.js → chunk-U2HBQHQK-DKS5adQo.js} +1 -1
- package/payload/server/public/assets/{chunk-X2U36JSP-BGJqysB2.js → chunk-X2U36JSP-CT_UM4IN.js} +1 -1
- package/payload/server/public/assets/{chunk-XPW4576I-GERE2ekx.js → chunk-XPW4576I-ByFnN4JO.js} +1 -1
- package/payload/server/public/assets/{chunk-YZCP3GAM-BskqX1V5.js → chunk-YZCP3GAM-DCetC20J.js} +1 -1
- package/payload/server/public/assets/{chunk-ZZ45TVLE-DG1iY-L9.js → chunk-ZZ45TVLE-Bh1QIv8N.js} +1 -1
- package/payload/server/public/assets/classDiagram-6PBFFD2Q-DJqbNcuC.js +1 -0
- package/payload/server/public/assets/classDiagram-v2-HSJHXN6E-CGglqfQA.js +1 -0
- package/payload/server/public/assets/clone-BiTMTzp_.js +1 -0
- package/payload/server/public/assets/{cose-bilkent-S5V4N54A-C8Rl4ykh.js → cose-bilkent-S5V4N54A-DgLEaApT.js} +1 -1
- package/payload/server/public/assets/{dagre-KV5264BT-C0YrJPF3.js → dagre-KV5264BT-BfkUPrEV.js} +1 -1
- package/payload/server/public/assets/{dagre-DTlv-FFy.js → dagre-unhMIyVo.js} +1 -1
- package/payload/server/public/assets/data-CJbC_cVy.js +1 -0
- package/payload/server/public/assets/{diagram-5BDNPKRD-Dfk5oepI.js → diagram-5BDNPKRD-BjjwcTD9.js} +1 -1
- package/payload/server/public/assets/{diagram-G4DWMVQ6-QRj55DiB.js → diagram-G4DWMVQ6-BbgWtVSS.js} +1 -1
- package/payload/server/public/assets/{diagram-MMDJMWI5-BAPKsohU.js → diagram-MMDJMWI5-DyAnkWGy.js} +1 -1
- package/payload/server/public/assets/{diagram-TYMM5635-C3iZXPkC.js → diagram-TYMM5635-SAMMoLxq.js} +1 -1
- package/payload/server/public/assets/{erDiagram-SMLLAGMA-DIbI7g_5.js → erDiagram-SMLLAGMA-Cvo-5-fD.js} +1 -1
- package/payload/server/public/assets/{flatten-DlAJeN0L.js → flatten-BXzaHgtL.js} +1 -1
- package/payload/server/public/assets/{flowDiagram-DWJPFMVM-l3eJJr8R.js → flowDiagram-DWJPFMVM-y3o2Ycld.js} +1 -1
- package/payload/server/public/assets/{ganttDiagram-T4ZO3ILL-aaEzHxIq.js → ganttDiagram-T4ZO3ILL-BEm_CQaK.js} +1 -1
- package/payload/server/public/assets/gitGraph-7Q5UKJZL-C_-XjxLd.js +1 -0
- package/payload/server/public/assets/{gitGraphDiagram-UUTBAWPF-Di-1eg_m.js → gitGraphDiagram-UUTBAWPF-CqmOxS7F.js} +1 -1
- package/payload/server/public/assets/{graph-CNgt7ygi.js → graph-kaxS9a_7.js} +2 -2
- package/payload/server/public/assets/graph-labels-GgejRtgV.js +1 -0
- package/payload/server/public/assets/{graphlib-D5awC2yp.js → graphlib-_HfbQcBf.js} +1 -1
- package/payload/server/public/assets/info-OMHHGYJF-BH8yDCfL.js +1 -0
- package/payload/server/public/assets/infoDiagram-42DDH7IO-DyDXyJ6U.js +2 -0
- package/payload/server/public/assets/{isEmpty-Dh1dF-tM.js → isEmpty-uB1dEu_Z.js} +1 -1
- package/payload/server/public/assets/{ishikawaDiagram-UXIWVN3A-CLOd_cNf.js → ishikawaDiagram-UXIWVN3A-C_d0vcZC.js} +1 -1
- package/payload/server/public/assets/{journeyDiagram-VCZTEJTY-cGyfW6Tg.js → journeyDiagram-VCZTEJTY-DsjRBqlW.js} +1 -1
- package/payload/server/public/assets/{kanban-definition-6JOO6SKY-CRTKWPlh.js → kanban-definition-6JOO6SKY-BwtK6LS-.js} +1 -1
- package/payload/server/public/assets/{line-B8Brm0TD.js → line-szBCFOLJ.js} +1 -1
- package/payload/server/public/assets/{linear-BHnSKXBe.js → linear-X3O7lwK3.js} +1 -1
- package/payload/server/public/assets/{maximize-2-k3Fu5hdP.js → maximize-2-CPq3nwZA.js} +1 -1
- package/payload/server/public/assets/{mermaid-parser.core-BtNpdQct.js → mermaid-parser.core-BavvMAhU.js} +2 -2
- package/payload/server/public/assets/{mermaid.core-BY87anQZ.js → mermaid.core-Br2F9Hdm.js} +3 -3
- package/payload/server/public/assets/{mindmap-definition-QFDTVHPH-rUSv9qN1.js → mindmap-definition-QFDTVHPH-4qlwXoJ-.js} +1 -1
- package/payload/server/public/assets/operator-CldXXilj.js +1 -0
- package/payload/server/public/assets/{ordinal-Bnl5GL-x.js → ordinal-CZY9laxg.js} +1 -1
- package/payload/server/public/assets/packet-4T2RLAQJ-Bajcns4r.js +1 -0
- package/payload/server/public/assets/{page-Bvx0A2jB.js → page-BE6DkJP9.js} +1 -1
- package/payload/server/public/assets/{page-DhwPG5Zf.js → page-BIwdWXnv.js} +3 -3
- package/payload/server/public/assets/{pdf-render-CrpAi4Ba.js → pdf-render-H884L3EI.js} +1 -1
- package/payload/server/public/assets/pie-ZZUOXDRM-D7hjM5w1.js +1 -0
- package/payload/server/public/assets/{pieDiagram-DEJITSTG-Cjn9IP-C.js → pieDiagram-DEJITSTG-BbXryIg0.js} +1 -1
- package/payload/server/public/assets/{public-BJ5Oo2Fw.js → public-BKQEQGPE.js} +1 -1
- package/payload/server/public/assets/{quadrantDiagram-34T5L4WZ-BkSHkSsn.js → quadrantDiagram-34T5L4WZ-BHLjcvh3.js} +1 -1
- package/payload/server/public/assets/radar-PYXPWWZC-B46d5x7a.js +1 -0
- package/payload/server/public/assets/{reduce-Bp5oNw1n.js → reduce-BGQzAenn.js} +1 -1
- package/payload/server/public/assets/{requirementDiagram-MS252O5E-hLwgyB-F.js → requirementDiagram-MS252O5E-BXaVO0lk.js} +1 -1
- package/payload/server/public/assets/{rotate-ccw-D-ov5jM4.js → rotate-ccw-DieZzl10.js} +1 -1
- package/payload/server/public/assets/{sankeyDiagram-XADWPNL6-D6Dl1RIa.js → sankeyDiagram-XADWPNL6-CmN1s17Q.js} +1 -1
- package/payload/server/public/assets/{sequenceDiagram-FGHM5R23-j0QBqkbL.js → sequenceDiagram-FGHM5R23-DC08e5NY.js} +1 -1
- package/payload/server/public/assets/{stateDiagram-FHFEXIEX-TvEBNsuV.js → stateDiagram-FHFEXIEX-DnoQMuOW.js} +1 -1
- package/payload/server/public/assets/stateDiagram-v2-QKLJ7IA2-CrstH34p.js +1 -0
- package/payload/server/public/assets/{tasks-DHMxTZVD.js → tasks-DYyCGz8W.js} +1 -1
- package/payload/server/public/assets/{time-entry-format-EmV5lz6o.js → time-entry-format-CC_9jFuR.js} +1 -1
- package/payload/server/public/assets/{timeline-definition-GMOUNBTQ-BfivOP-j.js → timeline-definition-GMOUNBTQ-eTn3uRaH.js} +1 -1
- package/payload/server/public/assets/treeView-SZITEDCU-3XQh5xQ1.js +1 -0
- package/payload/server/public/assets/treemap-W4RFUUIX-DwR-_l5z.js +1 -0
- package/payload/server/public/assets/{triangle-alert-CivyPfAL.js → triangle-alert-7mQfCQWP.js} +1 -1
- package/payload/server/public/assets/{useCopyFeedback-hbnPS-r9.js → useCopyFeedback-os7N0yCx.js} +1 -1
- package/payload/server/public/assets/useSubAccountSwitcher-Bgfqig1V.css +1 -0
- package/payload/server/public/assets/{useVoiceRecorder-QN9_GqB_.js → useVoiceRecorder-ZFwt5i6d.js} +2 -2
- package/payload/server/public/assets/{vennDiagram-DHZGUBPP-Bvfw9aFJ.js → vennDiagram-DHZGUBPP-DNx6o_ZQ.js} +1 -1
- package/payload/server/public/assets/wardley-RL74JXVD-DZp9oCGv.js +1 -0
- package/payload/server/public/assets/{wardleyDiagram-NUSXRM2D-C9h04G6b.js → wardleyDiagram-NUSXRM2D-BZOchqF3.js} +1 -1
- package/payload/server/public/assets/{xychartDiagram-5P7HB3ND-BMvi2m2q.js → xychartDiagram-5P7HB3ND-ZBEGH6ZI.js} +1 -1
- package/payload/server/public/browser.html +4 -4
- package/payload/server/public/calendar.html +6 -6
- package/payload/server/public/chat.html +14 -14
- package/payload/server/public/data.html +11 -11
- package/payload/server/public/graph.html +10 -11
- package/payload/server/public/index.html +16 -17
- package/payload/server/public/operator.html +15 -16
- package/payload/server/public/public.html +14 -14
- package/payload/server/public/tasks.html +6 -6
- package/payload/server/server.js +1836 -998
- package/payload/server/{src-JJ6PHO5I.js → src-UYDFMWF4.js} +1 -1
- package/payload/server/public/assets/AdminLoginScreens-Dic_fPt6.js +0 -1
- package/payload/server/public/assets/AdminShell-JN7rZ8aG.js +0 -2
- package/payload/server/public/assets/Checkbox-CB74kINA.js +0 -1
- package/payload/server/public/assets/activity-DbfpHg3i.js +0 -1
- package/payload/server/public/assets/admin-CThbXaYH.js +0 -1
- package/payload/server/public/assets/architecture-YZFGNWBL-CGvD4r9F.js +0 -1
- package/payload/server/public/assets/channel-Bpg-ADC0.js +0 -1
- package/payload/server/public/assets/chat-DcefIV9q.js +0 -1
- package/payload/server/public/assets/chevron-down-Blma_CKl.js +0 -1
- package/payload/server/public/assets/chevron-left-xeYDxFiC.js +0 -1
- package/payload/server/public/assets/chevron-right-BonbtjqJ.js +0 -1
- package/payload/server/public/assets/chunk-426QAEUC-1poG7Nd3.js +0 -1
- package/payload/server/public/assets/chunk-QZHKN3VN-GN1tIO8J.js +0 -1
- package/payload/server/public/assets/classDiagram-6PBFFD2Q-DMGVq8Jv.js +0 -1
- package/payload/server/public/assets/classDiagram-v2-HSJHXN6E-DfddT8UV.js +0 -1
- package/payload/server/public/assets/clone-Cc9vFIEk.js +0 -1
- package/payload/server/public/assets/data-D5dp6nj6.js +0 -1
- package/payload/server/public/assets/gitGraph-7Q5UKJZL-DtjXE0UI.js +0 -1
- package/payload/server/public/assets/graph-labels-DA48HrRR.js +0 -1
- package/payload/server/public/assets/info-OMHHGYJF-CmArhGYf.js +0 -1
- package/payload/server/public/assets/infoDiagram-42DDH7IO-utWz7oZy.js +0 -2
- package/payload/server/public/assets/operator-8NVpNLeD.js +0 -1
- package/payload/server/public/assets/packet-4T2RLAQJ-BTfK0Zw4.js +0 -1
- package/payload/server/public/assets/pie-ZZUOXDRM-BvtPeMQY.js +0 -1
- package/payload/server/public/assets/radar-PYXPWWZC-D2UXvQgn.js +0 -1
- package/payload/server/public/assets/stateDiagram-v2-QKLJ7IA2-Ur4eM6rb.js +0 -1
- package/payload/server/public/assets/treeView-SZITEDCU-0dE7FvM2.js +0 -1
- package/payload/server/public/assets/treemap-W4RFUUIX-BrwlXpr5.js +0 -1
- package/payload/server/public/assets/useSubAccountSwitcher-kwhG-aJj.css +0 -1
- package/payload/server/public/assets/wardley-RL74JXVD-CO9HdfJ-.js +0 -1
- /package/payload/server/public/assets/{_baseFor-BBkmqPEk.js → _baseFor-CMWvz0uA.js} +0 -0
- /package/payload/server/public/assets/{array-Bjtb2C_5.js → array-Cny_NT6T.js} +0 -0
- /package/payload/server/public/assets/{cytoscape.esm-B1dQ664k.js → cytoscape.esm-De0CCmB2.js} +0 -0
- /package/payload/server/public/assets/{defaultLocale-iwYA7ne8.js → defaultLocale-BahRht8U.js} +0 -0
- /package/payload/server/public/assets/{dist-CLmrZbY7.js → dist-C9D_xz81.js} +0 -0
- /package/payload/server/public/assets/{init-CxnBAgcf.js → init-DoIvjoJ_.js} +0 -0
- /package/payload/server/public/assets/{katex-DojHoLIr.js → katex-Bfn1OZEl.js} +0 -0
- /package/payload/server/public/assets/{path-JZiojYzQ.js → path-CTezU8L5.js} +0 -0
- /package/payload/server/public/assets/{preload-helper-CT1Z6Pdu.js → preload-helper-D_sPnjeL.js} +0 -0
- /package/payload/server/public/assets/{rough.esm-D5NinLFK.js → rough.esm-BJo4pvXu.js} +0 -0
- /package/payload/server/public/assets/{src-DQ0MSD6q.js → src-BTKk8KAG.js} +0 -0
- /package/payload/server/public/assets/{useSubAccountSwitcher-DotTrvX3.js → useSubAccountSwitcher-Q941cZYV.js} +0 -0
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
var crumbsEl = document.getElementById('dp-crumbs');
|
|
21
21
|
var entriesEl = document.getElementById('dp-entries');
|
|
22
22
|
var upEl = document.getElementById('dp-up');
|
|
23
|
+
var refreshEl = document.getElementById('dp-refresh');
|
|
23
24
|
var uploadOpenEl = document.getElementById('dp-upload-open');
|
|
24
25
|
var modalEl = document.getElementById('dp-modal');
|
|
25
26
|
var modalCloseEl = document.getElementById('dp-modal-close');
|
|
@@ -106,11 +107,23 @@
|
|
|
106
107
|
|
|
107
108
|
var a = document.createElement('a');
|
|
108
109
|
a.className = 'dp-file-name';
|
|
109
|
-
|
|
110
|
+
var href = API + '/api/download?key=' + encodeURIComponent(f.objectKey);
|
|
111
|
+
a.href = href;
|
|
110
112
|
// Full name on hover, since the middle of a long name is clipped away.
|
|
111
113
|
a.title = f.filename;
|
|
112
114
|
var p = nameParts(f.filename);
|
|
113
115
|
a.appendChild(nameNode(p.main, p.ext));
|
|
116
|
+
// Drag out to the file system, the same affordance the device-published
|
|
117
|
+
// rows already have. An <a> is draggable by default but hands over its
|
|
118
|
+
// href as a link; DownloadURL is what makes the OS write real bytes.
|
|
119
|
+
a.addEventListener('dragstart', function (ev) {
|
|
120
|
+
var abs = new URL(href, location.href).href;
|
|
121
|
+
ev.dataTransfer.setData(
|
|
122
|
+
'DownloadURL',
|
|
123
|
+
'application/octet-stream:' + f.filename + ':' + abs
|
|
124
|
+
);
|
|
125
|
+
ev.dataTransfer.setData('text/uri-list', abs);
|
|
126
|
+
});
|
|
114
127
|
li.appendChild(a);
|
|
115
128
|
|
|
116
129
|
var del = document.createElement('button');
|
|
@@ -221,6 +234,28 @@
|
|
|
221
234
|
openShared(e.relPath);
|
|
222
235
|
}
|
|
223
236
|
});
|
|
237
|
+
if (e.kind === 'directory') {
|
|
238
|
+
/* Drop a file straight onto the folder it belongs in. This is the
|
|
239
|
+
interaction the whole two-way exchange exists for: no modal, no
|
|
240
|
+
picker, no guessing where it went. The row highlights so the target
|
|
241
|
+
is unambiguous while the pointer is over it. */
|
|
242
|
+
li.addEventListener('dragover', function (ev) {
|
|
243
|
+
ev.preventDefault();
|
|
244
|
+
li.classList.add('dp-drop-target');
|
|
245
|
+
});
|
|
246
|
+
li.addEventListener('dragleave', function (ev) {
|
|
247
|
+
if (!li.contains(ev.relatedTarget)) { li.classList.remove('dp-drop-target'); }
|
|
248
|
+
});
|
|
249
|
+
li.addEventListener('drop', function (ev) {
|
|
250
|
+
ev.preventDefault();
|
|
251
|
+
// Marked so the listing's own handler does not ALSO upload it into
|
|
252
|
+
// the folder that is merely open — one drop is one upload.
|
|
253
|
+
ev.dpHandled = true;
|
|
254
|
+
li.classList.remove('dp-drop-target');
|
|
255
|
+
var dt = ev.dataTransfer;
|
|
256
|
+
if (dt && dt.files && dt.files.length) { uploadMany(dt.files, e.relPath); }
|
|
257
|
+
});
|
|
258
|
+
}
|
|
224
259
|
if (e.kind !== 'directory') {
|
|
225
260
|
b.draggable = true;
|
|
226
261
|
b.addEventListener('dragstart', function (ev) {
|
|
@@ -269,13 +304,22 @@
|
|
|
269
304
|
});
|
|
270
305
|
}
|
|
271
306
|
|
|
307
|
+
/* Resolves true when the list was re-rendered, false when it was not. The
|
|
308
|
+
boolean exists for the refresh control: this function catches its own
|
|
309
|
+
failure, so the promise resolves either way, and a caller that cleared the
|
|
310
|
+
status on resolve would erase the message written a moment earlier. The
|
|
311
|
+
`!d.ok` branch used to return silently, which left a failed re-list
|
|
312
|
+
indistinguishable from an unchanged one. */
|
|
272
313
|
function refresh() {
|
|
273
314
|
return api('/api/files?prefix=' + encodeURIComponent(prefix))
|
|
274
315
|
.then(function (r) {
|
|
275
316
|
return r.json();
|
|
276
317
|
})
|
|
277
318
|
.then(function (d) {
|
|
278
|
-
if (!d.ok)
|
|
319
|
+
if (!d.ok) {
|
|
320
|
+
say('Could not load your files.');
|
|
321
|
+
return false;
|
|
322
|
+
}
|
|
279
323
|
render(d.files || []);
|
|
280
324
|
var entries = d.entries || [];
|
|
281
325
|
// Hidden at the root only: once inside a folder the section stays put,
|
|
@@ -283,9 +327,11 @@
|
|
|
283
327
|
// vanishing.
|
|
284
328
|
sharedEl.hidden = prefix === '' && entries.length === 0;
|
|
285
329
|
renderEntries(entries);
|
|
330
|
+
return true;
|
|
286
331
|
})
|
|
287
332
|
.catch(function () {
|
|
288
333
|
say('Could not load your files.');
|
|
334
|
+
return false;
|
|
289
335
|
});
|
|
290
336
|
}
|
|
291
337
|
|
|
@@ -300,8 +346,15 @@
|
|
|
300
346
|
return r.json();
|
|
301
347
|
})
|
|
302
348
|
.then(function (d) {
|
|
303
|
-
|
|
304
|
-
|
|
349
|
+
var done = d.ok ? 'Deleted.' : 'Could not delete that.';
|
|
350
|
+
say(done);
|
|
351
|
+
// refresh() writes its own failure text, which would otherwise bury what
|
|
352
|
+
// just happened to the file. Compose the two, so a list that did not
|
|
353
|
+
// reload is reported without the client losing the delete's outcome and
|
|
354
|
+
// reading a load error as though the delete itself had failed.
|
|
355
|
+
return refresh().then(function (ok) {
|
|
356
|
+
if (!ok) { say(done + ' The list could not be reloaded.'); }
|
|
357
|
+
});
|
|
305
358
|
})
|
|
306
359
|
.catch(function () {
|
|
307
360
|
say('Could not delete that.');
|
|
@@ -370,12 +423,35 @@
|
|
|
370
423
|
refresh();
|
|
371
424
|
});
|
|
372
425
|
|
|
426
|
+
/* Re-read the index in the folder that is already open. refresh() reads the
|
|
427
|
+
current prefix, so this does not reset to the root. The status clears only
|
|
428
|
+
on a reported success, otherwise refresh()'s own error text stays up.
|
|
429
|
+
refresh() returns false from its catch rather than rejecting, so the button
|
|
430
|
+
cannot be left stuck disabled and needs no catch here. `disabled` in flight
|
|
431
|
+
is what stops a double click issuing two reads. */
|
|
432
|
+
refreshEl.addEventListener('click', function () {
|
|
433
|
+
refreshEl.disabled = true;
|
|
434
|
+
say('Refreshing…');
|
|
435
|
+
refresh().then(function (ok) {
|
|
436
|
+
refreshEl.disabled = false;
|
|
437
|
+
if (ok) { say(''); }
|
|
438
|
+
});
|
|
439
|
+
});
|
|
440
|
+
|
|
373
441
|
/* The upload modal. Hidden via the `hidden` attribute so
|
|
374
442
|
[hidden]{display:none!important} stays authoritative over .dp-modal's
|
|
375
443
|
display:flex. It dismisses three ways: the close button, a click on the
|
|
376
444
|
backdrop (never the panel), and Escape while open. */
|
|
377
445
|
function openModal() {
|
|
378
446
|
modalEl.hidden = false;
|
|
447
|
+
// Name the destination rather than leaving it implicit. An upload whose
|
|
448
|
+
// folder the client has to infer is the defect this whole task removes.
|
|
449
|
+
var t = document.getElementById('dp-upload-target');
|
|
450
|
+
if (t) {
|
|
451
|
+
t.textContent = prefix
|
|
452
|
+
? 'Uploading into ' + prefix
|
|
453
|
+
: 'Uploading into your own files. Open a folder first to send it there.';
|
|
454
|
+
}
|
|
379
455
|
document.getElementById('dp-file').focus();
|
|
380
456
|
}
|
|
381
457
|
function closeModal() {
|
|
@@ -397,29 +473,151 @@
|
|
|
397
473
|
not), so the picker is cleared only when a retry with the same selection is
|
|
398
474
|
pointless; `ok` is true only on a kept upload, which is what closes the
|
|
399
475
|
modal. A network error is { responded:false, ok:false }. */
|
|
400
|
-
|
|
476
|
+
/* One part is 25 MiB, matching the server's per-part gate. A part is read
|
|
477
|
+
wholly into the Function's memory, so this is the isolate's bound, not a
|
|
478
|
+
preference. The whole-file cap is 1 GiB. */
|
|
479
|
+
var PART_BYTES = 25 * 1024 * 1024;
|
|
480
|
+
var MAX_TOTAL_BYTES = 1024 * 1024 * 1024;
|
|
481
|
+
|
|
482
|
+
/* Every failure used to read 'Upload failed.', which tells a client nothing
|
|
483
|
+
about whether to retry, sign in again, or pick a smaller file. */
|
|
484
|
+
function uploadFailureMessage(status) {
|
|
485
|
+
if (status === 401) return 'Your session expired. Sign in again.';
|
|
486
|
+
if (status === 403) return 'You cannot upload to that folder.';
|
|
487
|
+
if (status === 413) return 'That file is too large.';
|
|
488
|
+
if (status === 400) return 'That file could not be read.';
|
|
489
|
+
return 'Upload failed.';
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
function showProgress(fraction) {
|
|
493
|
+
var wrap = document.getElementById('dp-progress');
|
|
494
|
+
var bar = document.getElementById('dp-progress-bar');
|
|
495
|
+
if (!wrap || !bar) return;
|
|
496
|
+
if (fraction === null) { wrap.hidden = true; return; }
|
|
497
|
+
wrap.hidden = false;
|
|
498
|
+
bar.style.width = Math.round(fraction * 100) + '%';
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
/* Slice a large file across a multipart session. Three stages: open, send
|
|
502
|
+
each part, assemble. Anything at or below one part takes the single-request
|
|
503
|
+
path below, so the common case stays one round trip. */
|
|
504
|
+
function uploadLarge(file, target) {
|
|
505
|
+
var created;
|
|
401
506
|
var fd = new FormData();
|
|
402
|
-
fd.append('
|
|
403
|
-
|
|
507
|
+
fd.append('action', 'create');
|
|
508
|
+
fd.append('filename', file.name);
|
|
509
|
+
fd.append('targetPath', target);
|
|
510
|
+
fd.append('size', String(file.size));
|
|
404
511
|
return api('/api/upload', { method: 'POST', body: fd })
|
|
405
|
-
.then(function (r) {
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
512
|
+
.then(function (r) { return r.json().then(function (d) { return { s: r.status, d: d }; }); })
|
|
513
|
+
.then(function (res) {
|
|
514
|
+
if (!res.d.ok) { throw { status: res.s }; }
|
|
515
|
+
created = res.d;
|
|
516
|
+
var parts = [];
|
|
517
|
+
var total = Math.ceil(file.size / PART_BYTES);
|
|
518
|
+
var chain = Promise.resolve();
|
|
519
|
+
var _loop = function (i) {
|
|
520
|
+
chain = chain.then(function () {
|
|
521
|
+
var slice = file.slice(i * PART_BYTES, Math.min((i + 1) * PART_BYTES, file.size));
|
|
522
|
+
var pf = new FormData();
|
|
523
|
+
pf.append('action', 'part');
|
|
524
|
+
pf.append('key', created.key);
|
|
525
|
+
pf.append('r2UploadId', created.r2UploadId);
|
|
526
|
+
pf.append('partNumber', String(i + 1));
|
|
527
|
+
pf.append('part', slice);
|
|
528
|
+
say('Uploading ' + file.name + ' (' + (i + 1) + ' of ' + total + ')…');
|
|
529
|
+
showProgress(i / total);
|
|
530
|
+
return api('/api/upload', { method: 'POST', body: pf })
|
|
531
|
+
.then(function (r) { return r.json().then(function (d) { return { s: r.status, d: d }; }); })
|
|
532
|
+
.then(function (pr) {
|
|
533
|
+
if (!pr.d.ok) { throw { status: pr.s }; }
|
|
534
|
+
parts.push({ partNumber: pr.d.partNumber, etag: pr.d.etag, size: slice.size });
|
|
535
|
+
});
|
|
536
|
+
});
|
|
537
|
+
};
|
|
538
|
+
for (var i = 0; i < total; i++) { _loop(i); }
|
|
539
|
+
return chain.then(function () {
|
|
540
|
+
var cf = new FormData();
|
|
541
|
+
cf.append('action', 'complete');
|
|
542
|
+
cf.append('key', created.key);
|
|
543
|
+
cf.append('r2UploadId', created.r2UploadId);
|
|
544
|
+
cf.append('targetPath', target);
|
|
545
|
+
cf.append('parts', JSON.stringify(parts));
|
|
546
|
+
showProgress(1);
|
|
547
|
+
return api('/api/upload', { method: 'POST', body: cf })
|
|
548
|
+
.then(function (r) { return r.json().then(function (d) { return { s: r.status, d: d }; }); });
|
|
549
|
+
});
|
|
550
|
+
});
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
/* One upload path for Browse-and-Upload, for a dropped file, and for a file
|
|
554
|
+
dropped onto a folder row. `target` is the folder it should land in; '' is
|
|
555
|
+
the client's own uploads area. Resolves { responded, ok }. */
|
|
556
|
+
function uploadFile(file, target) {
|
|
557
|
+
target = target || '';
|
|
558
|
+
// Checked here as well as on the server: the browser should not spend
|
|
559
|
+
// minutes sending a gigabyte only to be told at the end.
|
|
560
|
+
if (file.size > MAX_TOTAL_BYTES) {
|
|
561
|
+
say(file.name + ' is larger than the 1 GB limit.');
|
|
562
|
+
return Promise.resolve({ responded: true, ok: false });
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
say('Uploading ' + file.name + '…');
|
|
566
|
+
var run;
|
|
567
|
+
if (file.size > PART_BYTES) {
|
|
568
|
+
run = uploadLarge(file, target);
|
|
569
|
+
} else {
|
|
570
|
+
var fd = new FormData();
|
|
571
|
+
fd.append('file', file);
|
|
572
|
+
fd.append('targetPath', target);
|
|
573
|
+
run = api('/api/upload', { method: 'POST', body: fd })
|
|
574
|
+
.then(function (r) { return r.json().then(function (d) { return { s: r.status, d: d }; }); });
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
return run
|
|
578
|
+
.then(function (res) {
|
|
579
|
+
showProgress(null);
|
|
580
|
+
var done = res.d.ok ? 'Uploaded ' + file.name + '.' : uploadFailureMessage(res.s);
|
|
581
|
+
say(done);
|
|
582
|
+
// Same composition as remove(): a list that did not reload must not cost
|
|
583
|
+
// the client the upload's own outcome.
|
|
584
|
+
return refresh().then(function (ok) {
|
|
585
|
+
if (!ok) { say(done + ' The list could not be reloaded.'); }
|
|
586
|
+
return { responded: true, ok: !!res.d.ok };
|
|
587
|
+
});
|
|
411
588
|
})
|
|
412
|
-
.catch(function () {
|
|
413
|
-
|
|
414
|
-
|
|
589
|
+
.catch(function (err) {
|
|
590
|
+
showProgress(null);
|
|
591
|
+
// A thrown {status} came from a multipart stage; anything else is the
|
|
592
|
+
// network. Both are distinguishable to the client, which is the point.
|
|
593
|
+
say(err && err.status ? uploadFailureMessage(err.status) : 'Could not reach the server.');
|
|
594
|
+
return { responded: !!(err && err.status), ok: false };
|
|
595
|
+
});
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
/* Files in sequence, not in parallel: the status line reports one at a time,
|
|
599
|
+
and a portal on a free plan should not open ten concurrent uploads. */
|
|
600
|
+
function uploadMany(files, target) {
|
|
601
|
+
var list = Array.prototype.slice.call(files);
|
|
602
|
+
var anyOk = false;
|
|
603
|
+
return list.reduce(function (chain, f) {
|
|
604
|
+
return chain.then(function () {
|
|
605
|
+
return uploadFile(f, target).then(function (r) {
|
|
606
|
+
if (r.ok) { anyOk = true; }
|
|
607
|
+
});
|
|
415
608
|
});
|
|
609
|
+
}, Promise.resolve()).then(function () {
|
|
610
|
+
return { responded: true, ok: anyOk };
|
|
611
|
+
});
|
|
416
612
|
}
|
|
417
613
|
|
|
418
614
|
uploadForm.addEventListener('submit', function (e) {
|
|
419
615
|
e.preventDefault();
|
|
420
616
|
var input = document.getElementById('dp-file');
|
|
421
|
-
if (!input.files || !input.files
|
|
422
|
-
|
|
617
|
+
if (!input.files || !input.files.length) return;
|
|
618
|
+
// The modal uploads into the folder currently open, which is what the
|
|
619
|
+
// target line under the form says. No hidden default.
|
|
620
|
+
uploadMany(input.files, prefix).then(function (res) {
|
|
423
621
|
if (res.responded) { input.value = ''; }
|
|
424
622
|
if (res.ok) { closeModal(); }
|
|
425
623
|
});
|
|
@@ -440,11 +638,50 @@
|
|
|
440
638
|
e.preventDefault();
|
|
441
639
|
uploadForm.classList.remove('dp-dropping');
|
|
442
640
|
var dt = e.dataTransfer;
|
|
443
|
-
if (dt && dt.files && dt.files
|
|
444
|
-
|
|
445
|
-
uploadFile(dt.files[0]).then(function (res) {
|
|
641
|
+
if (dt && dt.files && dt.files.length) {
|
|
642
|
+
uploadMany(dt.files, prefix).then(function (res) {
|
|
446
643
|
if (res.ok) { closeModal(); }
|
|
447
644
|
});
|
|
448
645
|
}
|
|
449
646
|
});
|
|
647
|
+
|
|
648
|
+
/* Drop straight onto the open listing, which targets the folder currently
|
|
649
|
+
open. The modal is no longer the only way in: the whole point of the
|
|
650
|
+
exchange is that a client drags a file to where it belongs. */
|
|
651
|
+
entriesEl.addEventListener('dragover', function (e) {
|
|
652
|
+
e.preventDefault();
|
|
653
|
+
entriesEl.classList.add('dp-dropping');
|
|
654
|
+
});
|
|
655
|
+
entriesEl.addEventListener('dragleave', function (e) {
|
|
656
|
+
if (!entriesEl.contains(e.relatedTarget)) {
|
|
657
|
+
entriesEl.classList.remove('dp-dropping');
|
|
658
|
+
}
|
|
659
|
+
});
|
|
660
|
+
entriesEl.addEventListener('drop', function (e) {
|
|
661
|
+
// Only a drop that landed on the listing itself, not one that bubbled up
|
|
662
|
+
// from a folder row — that row has already handled it with its own target.
|
|
663
|
+
if (e.dpHandled) return;
|
|
664
|
+
e.preventDefault();
|
|
665
|
+
entriesEl.classList.remove('dp-dropping');
|
|
666
|
+
var dt = e.dataTransfer;
|
|
667
|
+
if (dt && dt.files && dt.files.length) { uploadMany(dt.files, prefix); }
|
|
668
|
+
});
|
|
669
|
+
|
|
670
|
+
/* Re-read on a timer while the tab is visible.
|
|
671
|
+
|
|
672
|
+
Polling was rejected in Tasks 1885 and 1900 on the explicit reasoning that
|
|
673
|
+
"a poll against an hourly tree is waste". That premise died with the hourly
|
|
674
|
+
push: the device now publishes every sixty seconds, so a poll at the same
|
|
675
|
+
cadence is what makes a device-side change appear without the client
|
|
676
|
+
clicking anything.
|
|
677
|
+
|
|
678
|
+
Gated on visibility so a forgotten background tab costs nothing, and
|
|
679
|
+
skipped while a read is already in flight so a slow response cannot stack
|
|
680
|
+
requests on a portal running on a free plan. */
|
|
681
|
+
var polling = false;
|
|
682
|
+
setInterval(function () {
|
|
683
|
+
if (document.hidden || panelEl.hidden || polling) return;
|
|
684
|
+
polling = true;
|
|
685
|
+
refresh().then(function () { polling = false; });
|
|
686
|
+
}, 60000);
|
|
450
687
|
})();
|
|
@@ -49,10 +49,14 @@ This usually completes within a couple of minutes of the booking.
|
|
|
49
49
|
Bookings are reconciled on a cycle and the system audits itself each run. In the device logs:
|
|
50
50
|
|
|
51
51
|
- `[calendar-booking]` lines trace one booking through submit, write, notify, and done.
|
|
52
|
-
- `[calendar-reconcile]` lines report
|
|
52
|
+
- `[calendar-reconcile]` lines report four health numbers per account: `eligibleExamined` (bookings the check looked at), `pendingUnreconciled` (bookings captured but not yet turned into meetings), `pendingOldestAgeMin` (how many minutes the longest-waiting of those has been waiting) and `orphanMeetings` (booking meetings with no matching saved booking). Entries that only block out a slot are not counted — they are not bookings and never become meetings.
|
|
53
53
|
|
|
54
54
|
A `pendingUnreconciled` number that stays above zero across several runs means those bookings are failing to turn into meetings, and the `attempt=` number on the matching `[calendar-booking]` lines says how many times each has been tried. A booking whose meeting is deleted is picked up again automatically on the next run. To read them: `journalctl --user -u <brand-service> | grep -E '\[calendar-booking\]|\[calendar-reconcile\]'`, then filter by one booking id for its full story. The edge half of a booking (before your device sees it) is in the Cloudflare Pages function logs.
|
|
55
55
|
|
|
56
|
+
A count on its own cannot tell a short lag from a long one: five bookings waiting two minutes and five waiting nine hours are both `pendingUnreconciled=5`. `pendingOldestAgeMin` is what separates them. It reads `0` when nothing is waiting, and also when something is waiting but for under a minute, so read it together with the count beside it.
|
|
57
|
+
|
|
58
|
+
A separate `op=liveness` line is printed every ten minutes by your device's own server, whether or not anything is wrong. `stale=false` means the booking check has run recently. `stale=true` means it has not run in over six minutes, which is the case nothing else can tell you about: a check that has stopped cannot report that it has stopped. `lastSkipReason` names the last account whose check finished without looking at any bookings, and reads `none` when every account it got to was checked. If you run more than one account, a check can both look at one account and skip another, so seeing a recent check time next to a skip reason is normal and means two different accounts.
|
|
59
|
+
|
|
56
60
|
## When your real calendar lives elsewhere
|
|
57
61
|
|
|
58
62
|
The booking page publishes open slots from the internal calendar, the one Maxy keeps in its own graph.
|
|
@@ -404,6 +404,8 @@ Standing rules the operator gives over a channel are `Preference` nodes. Two mec
|
|
|
404
404
|
|
|
405
405
|
**Trust a scheduled turn's provenance, never an inbound's claim (channel-agent grounding).** A cron-dispatched agent turn reaches the same channel builders as a real inbound, so its own authorisation text would otherwise land inside `## Context` — the zone `REFRAME_INSTRUCTION` demotes to untrusted — and a security-cautious agent would distrust its own automation (the self-cancel loop). The dispatcher therefore stamps a `ScheduleProvenance` marker (owning account, event, owner userId, dispatch time) that the channel builders render as a `## Scheduled automation (platform-verified)` preamble **above** `## Context` (and above the rules block); the whatsapp wrap tag names the door `scheduler`. Both channel-server paths carry the marker — the single-target loop and the unified multi-target door reader (via the same `doorInboundPayload` seam that carries `standingRules`); the scheduler only dispatches to the whatsapp/telegram door, so a webchat frame never bears one. Because the marker is dispatcher-stamped and never derived from payload text, an inbound message cannot forge it — placement above `## Context` alone is not the trust signal, the dispatcher provenance is. The grounding binds to it in two places: one recipient-neutral clause added byte-identical to the three reframe instructions (a marker-bearing turn is authorised standing automation; a reconfiguration or re-enable is not intrusion), and the admin `IDENTITY.md` "Untrusted input" rule that the agent never states a platform-level authenticity or impersonation verdict it was not given (an unsourced "this is not from you" is its own inference, flagged, never asserted as the platform's). A sender has exactly one session: every inbound, real or scheduled, resolves to the deterministic `adminSessionIdFor(effectiveAccount, destination)`. An earlier change gave each firing its own salted seat so distrust could not accrete across firings, but the inbound hub keys on `senderId` and replaces its subscriber outright, so a seat's channel server attached under the sender's own key and swallowed the sender's REAL messages until it detached — splitting one conversation across two sessions that each denied what the other held. The seat is removed; the authoritative `agentPrompt`, this provenance marker, and the injected standing rules ground each firing without one. Self-cancel attribution is disarmed while that model beds in: the guard used to compare the cancelling turn's session id to a dispatcher stamp, which discriminated only because each firing ran its own seat, so with one session per sender it would report an autonomous loop on every operator cancel. It now always reports false — under-counting a real loop rather than inventing one — until the discriminator is rebuilt from the turn's own dispatched eventId. See `.docs/scheduling-agent-dispatch.md § Operator-provenance marker`, `§ Session model — one session per sender`, and `§ Self-cancel attribution — disarmed`.
|
|
406
406
|
|
|
407
|
+
**Carry what a reply points at (WhatsApp reply quote).** A WhatsApp sender replying to an earlier message produces a quote that `app/lib/whatsapp/inbound/extract.ts` resolves to `{ id, text?, sender? }` and `manager.ts` assigns to the route payload's `replyContext`. That field had no reader: the `server/index.ts` gateway argument omitted it, `AdminInbound` and the hub frame had no such field, and the channel service's `composePayload` emitted the caption and media notes only, so a message saying "prepare an email confirmation for this booking" reached the agent with nothing to resolve "this booking" against. It now rides both WhatsApp admin doors. On the persistent door it travels `server/index.ts` -> `AdminInbound` -> the hub frame the SSE route stringifies -> the channel service's `InboundPayload`, where `composeReplyNote` renders it as a bracketed note at the top of the payload, inside `## Context` and above the caption and any woven media notes: the quoted body is sender-controlled and belongs in the reframed untrusted zone, never in the platform preamble. Both channel-server inbound paths carry it, through the same `doorInboundPayload` seam that carries `standingRules` and the schedule provenance marker: the single-target SSE loop parses the whole frame, and the unified multi-target door reader rebuilds the payload field by field, so a field that seam omits is dropped on multi-target sessions alone. That seam is guarded by a test which reads the `InboundPayload` declaration and the rebuild function rather than a fixture, because the pre-existing byte-identity guard compared one hardcoded payload and so stayed green for every field added after it was written. On the passive account-manager door there is no channel server and no `## Context` frame, so the one-shot's whole initial prompt is read at face value; `composePassivePrompt` therefore puts the BARE sentence above the requirement and never the quoted body. The body arrives from the sender's own client and is never checked against the message store, so quoting it in a platform-voiced bracket on the door with no untrusted zone would let a modified client put words the agent never said into a `work-create` filing. The empty-text drop gate still tests the requirement alone, so a quote never rescues an inbound carrying no requirement of its own. The quoted body is capped at `QUOTED_TEXT_CAP` (500 characters, `…` appended when cut) because it is unbounded sender-controlled input, and the cut lands on whole characters so a body whose 500th boundary falls inside a surrogate pair cannot emit a lone surrogate at a position the sender chose. Only the channel service renders a body, so the cap lives there alone; platform/ui's `app/lib/whatsapp/reply-quote.ts` holds just the bare sentence for the passive door, and both packages hardcode that sentence in their own tests so the two doors cannot drift apart on the wording (the packages never cross-import, the same arrangement `REFRAME_INSTRUCTION` and `MediaRef` use). Telegram and webchat extract no quote at all, so neither carries one. `payloadBytes` measures the whole inner payload, so a reply carrying a quote produces a visibly larger `[reframe] op=applied … payloadBytes=<n>` than the same text without one, and a bare-text-length `payloadBytes` on an inbound whose store row has a non-null `quotedId` is the signature of this defect returning. Because the field is optional at every hop, dropping it from either `server/index.ts` argument would be silent (tsc stays green, and no unit test executes that wiring file), so the `check-reply-context-threaded` build gate asserts both argument sites.
|
|
408
|
+
|
|
407
409
|
**Observability.** `[preference-write] op=reconcile … mode=<reinforce|update|contradict|new> similarity=…` — a stream of `mode=new` for subjects that already exist is the duplicate-minting signature. `[preference-inject] op=inject channel=<wa|tg|web> injected=<N> owner=<8> source=<owner|no-owner|owner-error|fetch-error>` — injection selects only the account owner's admin preferences, so `source=owner injected=0` is a genuinely empty owner, `source=no-owner` is an account with no seeded owner (injects nothing, a missed owner seeding), and `injected=0 source=owner` on an account whose owner holds rules is the write-only regression re-appearing. `[preference-audit] op=drift accountId=… storedActive=… injectedActive=… duplicateClusters=…` — the standing reconciliation audit (claude-session-manager, 5-minute tick) flags any account whose store holds more active rows than injection would surface, the no-event detector for drift.
|
|
408
410
|
|
|
409
411
|
---
|
|
@@ -2,6 +2,11 @@ import { test, expect } from "vitest";
|
|
|
2
2
|
import { readFileSync, readdirSync } from "node:fs";
|
|
3
3
|
import { resolve, join } from "node:path";
|
|
4
4
|
import { parseLabelsFromSchemaCypher } from "../../../../../../lib/graph-mcp/dist/schema-cypher-parser.js";
|
|
5
|
+
import { ADDITIONAL_BASE_LABELS } from "../../../../../../lib/graph-style/dist/index.js";
|
|
6
|
+
// Same package, so the markdown row splitter is imported rather than copied.
|
|
7
|
+
// The UI server carries its own copy only because it is a separate build in a
|
|
8
|
+
// separate process; inside this package a second copy would be pure drift.
|
|
9
|
+
import { splitPipeRow } from "../schema-loader.js";
|
|
5
10
|
// Task 358 — schema-doc / schema-cypher drift guard.
|
|
6
11
|
//
|
|
7
12
|
// Every label declared in prose by an ontology doc under
|
|
@@ -15,56 +20,81 @@ import { parseLabelsFromSchemaCypher } from "../../../../../../lib/graph-mcp/dis
|
|
|
15
20
|
// task, and Task 352 before it).
|
|
16
21
|
const REFERENCES_DIR = resolve(import.meta.dirname, "../../../../references");
|
|
17
22
|
const SCHEMA_CYPHER_PATH = resolve(import.meta.dirname, "../../../../../../neo4j/schema.cypher");
|
|
18
|
-
// Labels
|
|
19
|
-
// nodes
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
function
|
|
23
|
+
// Labels declared in a node-type table that are deliberately NOT first-class
|
|
24
|
+
// graph nodes, so they carry no constraint or index of their own.
|
|
25
|
+
//
|
|
26
|
+
// The additional-label members come from the single shared declaration in
|
|
27
|
+
// graph-style (Task 1893) — the same set the /graph chip allowlist excludes, so
|
|
28
|
+
// the two cannot drift apart. `Email` is local and stays local: it is exempt
|
|
29
|
+
// because the label is retired (Task 321 folded threads onto
|
|
30
|
+
// :KnowledgeDocument {source:'email'}, Task 426 moved them to
|
|
31
|
+
// :ConversationArchive {source:'email'}), not because it stacks on a base node.
|
|
32
|
+
// Folding it into the shared set would make that set mean two different things.
|
|
33
|
+
const EXEMPT_LABELS = new Set([...ADDITIONAL_BASE_LABELS, "Email"]);
|
|
34
|
+
const stripBackticks = (s) => s.replace(/`/g, "").trim();
|
|
35
|
+
// Every label in the `Neo4j Label` column of any node-type table in the doc.
|
|
36
|
+
//
|
|
37
|
+
// This replaces the previous scan for the backticked `:Label` prose form
|
|
38
|
+
// (Task 1893). That form matched only labels an author happened to write with a
|
|
39
|
+
// colon, so whole verticals went unchecked; widening it to bare backticks was
|
|
40
|
+
// measured and rejected, because it also matches relationship types (CITES),
|
|
41
|
+
// status values (Paid, InProgress), compass directions (North), contract clause
|
|
42
|
+
// kinds (Severability) and the `Owner` column header — 99 hits, ~90 of them not
|
|
43
|
+
// labels. The table column is the structured declaration, so it carries no such
|
|
44
|
+
// noise, and it is the same source the /graph top-level derivation reads.
|
|
45
|
+
function tableDeclaredLabels(text) {
|
|
41
46
|
const labels = new Set();
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
47
|
+
const lines = text.split(/\r?\n/);
|
|
48
|
+
for (let i = 0; i < lines.length - 1; i++) {
|
|
49
|
+
if (!lines[i].includes("|"))
|
|
50
|
+
continue;
|
|
51
|
+
const sep = lines[i + 1];
|
|
52
|
+
if (!/^\s*\|?[\s:|-]+\|?\s*$/.test(sep) || !sep.includes("-"))
|
|
53
|
+
continue;
|
|
54
|
+
const headers = splitPipeRow(lines[i]).map((h) => stripBackticks(h).toLowerCase());
|
|
55
|
+
const labelCol = headers.findIndex((h) => h === "neo4j label");
|
|
56
|
+
if (labelCol === -1)
|
|
57
|
+
continue;
|
|
58
|
+
for (let j = i + 2; j < lines.length; j++) {
|
|
59
|
+
const row = lines[j];
|
|
60
|
+
if (!row.trim() || !row.includes("|"))
|
|
61
|
+
break;
|
|
62
|
+
const label = stripBackticks(splitPipeRow(row)[labelCol] ?? "");
|
|
63
|
+
if (/^[A-Z][A-Za-z0-9]*$/.test(label))
|
|
64
|
+
labels.add(label);
|
|
65
|
+
}
|
|
46
66
|
}
|
|
47
67
|
return labels;
|
|
48
68
|
}
|
|
49
|
-
test("schema-*.md
|
|
69
|
+
test("schema-*.md node-type table labels all appear in schema.cypher", () => {
|
|
50
70
|
const declared = new Set(parseLabelsFromSchemaCypher(readFileSync(SCHEMA_CYPHER_PATH, "utf-8")));
|
|
51
71
|
const files = readdirSync(REFERENCES_DIR).filter((name) => name.startsWith("schema-") && name.endsWith(".md"));
|
|
52
72
|
expect(files.length).toBeGreaterThan(0);
|
|
53
73
|
const missing = [];
|
|
54
74
|
for (const file of files) {
|
|
55
75
|
const text = readFileSync(join(REFERENCES_DIR, file), "utf-8");
|
|
56
|
-
for (const label of
|
|
57
|
-
if (
|
|
76
|
+
for (const label of tableDeclaredLabels(text)) {
|
|
77
|
+
if (EXEMPT_LABELS.has(label))
|
|
58
78
|
continue;
|
|
59
79
|
if (!declared.has(label)) {
|
|
60
80
|
missing.push({ label, file });
|
|
61
81
|
}
|
|
62
82
|
}
|
|
63
83
|
}
|
|
64
|
-
expect(missing, `Labels
|
|
84
|
+
expect(missing, `Labels declared in a schema-*.md node-type table but missing from schema.cypher (add a CREATE CONSTRAINT or CREATE INDEX FOR (x:Label) line, or — if it is an additional label on a base node — add it to ADDITIONAL_BASE_LABELS in platform/lib/graph-style):\n${missing
|
|
65
85
|
.map((m) => ` :${m.label} (${m.file})`)
|
|
66
86
|
.join("\n")}`).toEqual([]);
|
|
67
87
|
});
|
|
88
|
+
test("the additional-label exemptions come from the shared declaration", () => {
|
|
89
|
+
// Task 1893: this guard and the /graph chip allowlist read one set. If this
|
|
90
|
+
// fails, someone re-introduced a local copy.
|
|
91
|
+
for (const label of ADDITIONAL_BASE_LABELS) {
|
|
92
|
+
expect(EXEMPT_LABELS.has(label), `:${label} should be exempt`).toBe(true);
|
|
93
|
+
}
|
|
94
|
+
// Email is the only exemption that is NOT an additional label.
|
|
95
|
+
const extra = [...EXEMPT_LABELS].filter((l) => !ADDITIONAL_BASE_LABELS.has(l));
|
|
96
|
+
expect(extra).toEqual(["Email"]);
|
|
97
|
+
});
|
|
68
98
|
test("estate-agent labels are declared in schema.cypher", () => {
|
|
69
99
|
// Task 358 acceptance — explicit assertion against regression.
|
|
70
100
|
const declared = new Set(parseLabelsFromSchemaCypher(readFileSync(SCHEMA_CYPHER_PATH, "utf-8")));
|
package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-cypher-drift.test.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema-cypher-drift.test.js","sourceRoot":"","sources":["../../../src/lib/__tests__/schema-cypher-drift.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AACpD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,2BAA2B,EAAE,MAAM,8DAA8D,CAAC;
|
|
1
|
+
{"version":3,"file":"schema-cypher-drift.test.js","sourceRoot":"","sources":["../../../src/lib/__tests__/schema-cypher-drift.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -188,7 +188,7 @@ It is **armed by the always-running `platform/ui` server on an interval**, becau
|
|
|
188
188
|
|
|
189
189
|
Env: `PLATFORM_ROOT`, `NEO4J_URI` (and `NEO4J_PASSWORD` or `config/.neo4j-password`). Per account it reads `data/accounts/<id>/calendar-availability.json` (for `bookingDbName`). The Cloudflare credential is read house-only from `config/cloudflare-house.env`; that credential is a minter, so the calendar mints a `calendar-d1` (D1-only) token from it via `cf-token.sh` and shells `wrangler` for the D1 reads/writes on that scoped token, never the minter directly.
|
|
190
190
|
|
|
191
|
-
Observability: one `[calendar-booking]` line per step keyed by `bookingId` (`reconcile-claim`, `meeting-write`, `notify`, `booked`), and one `[calendar-reconcile] account=… eligibleExamined=… pendingUnreconciled=… orphanMeetings=…` audit line per account per run. `eligibleExamined` separates "nothing to do" from "verified clean". A `pendingUnreconciled` that persists across runs means those rows are failing to become meetings rather than merely lagging — the `attempt=` count on the claim and failure lines says how many times each has been tried. `orphanMeetings` means a booking meeting has no backing eligible D1 row.
|
|
191
|
+
Observability: one `[calendar-booking]` line per step keyed by `bookingId` (`reconcile-claim`, `meeting-write`, `notify`, `booked`), and one `[calendar-reconcile] account=… eligibleExamined=… pendingUnreconciled=… pendingOldestAgeMin=… orphanMeetings=…` audit line per account per run. `eligibleExamined` separates "nothing to do" from "verified clean". A `pendingUnreconciled` that persists across runs means those rows are failing to become meetings rather than merely lagging — the `attempt=` count on the claim and failure lines says how many times each has been tried. `orphanMeetings` means a booking meeting has no backing eligible D1 row. `pendingOldestAgeMin` ages that backlog: a count cannot separate a short lag from a nine-hour one. The companion `[calendar-reconcile] op=liveness lastRunAgeMin=… lastAuditAgeMin=… lastSkipReason=… stale=<bool>` line is emitted every ten minutes by the platform/ui server rather than by this script, because the failure it detects is this script not reporting at all. `op=liveness-write-failed` is the one case where that record could not be written; it carries the error body and never stops the run, because a run that worked must not be reported as failed for losing its own bookkeeping.
|
|
192
192
|
|
|
193
193
|
## Ingesting calendar archives (.ics → graph)
|
|
194
194
|
|
|
@@ -42,8 +42,8 @@ function refuseNoAccount(toolName) {
|
|
|
42
42
|
// read. NOT boot-throw on missing — the silent-compaction path
|
|
43
43
|
// (compaction.ts:53) intentionally spawns this MCP without USER_ID for
|
|
44
44
|
// summary-only turns that don't invoke scheduling tools. The lib's
|
|
45
|
-
// per-call MissingUserIdError handles the actual bug class (
|
|
46
|
-
//
|
|
45
|
+
// per-call MissingUserIdError handles the actual bug class (Task 1895: no
|
|
46
|
+
// resolvable identity — no owner AdminUser and no acting userId).
|
|
47
47
|
const userId = process.env.USER_ID || "";
|
|
48
48
|
const sessionId = process.env.SESSION_ID || undefined;
|
|
49
49
|
const agentSlug = process.env.AGENT_SLUG || undefined;
|