@rubytech/create-realagent-code 0.1.254 → 0.1.256
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/__tests__/plugin-install.test.js +58 -40
- package/dist/index.js +77 -26
- package/dist/lib/plugin-install.js +31 -29
- package/package.json +1 -1
- package/payload/platform/.docs/search-surface-contract.md +58 -0
- package/payload/platform/config/brand-registry.json +8 -0
- package/payload/platform/config/brand.json +2 -2
- package/payload/platform/lib/embed-client/dist/index.d.ts +4 -0
- package/payload/platform/lib/embed-client/dist/index.d.ts.map +1 -0
- package/payload/platform/lib/embed-client/dist/index.js +53 -0
- package/payload/platform/lib/embed-client/dist/index.js.map +1 -0
- package/payload/platform/lib/embed-client/src/index.ts +53 -0
- package/payload/platform/lib/embed-client/tsconfig.json +8 -0
- package/payload/platform/lib/graph-search/dist/index.d.ts +27 -6
- package/payload/platform/lib/graph-search/dist/index.d.ts.map +1 -1
- package/payload/platform/lib/graph-search/dist/index.js +19 -1
- package/payload/platform/lib/graph-search/dist/index.js.map +1 -1
- package/payload/platform/lib/graph-search/src/__tests__/fulltext-coverage.test.ts +12 -0
- package/payload/platform/lib/graph-search/src/index.ts +28 -6
- package/payload/platform/lib/graph-write/dist/index.d.ts +25 -0
- package/payload/platform/lib/graph-write/dist/index.d.ts.map +1 -1
- package/payload/platform/lib/graph-write/dist/index.js +80 -2
- package/payload/platform/lib/graph-write/dist/index.js.map +1 -1
- package/payload/platform/lib/graph-write/src/index.ts +98 -1
- package/payload/platform/neo4j/schema.cypher +126 -0
- package/payload/platform/package.json +2 -2
- package/payload/platform/plugins/.claude-plugin/marketplace.json +5 -0
- package/payload/platform/plugins/admin/.claude-plugin/plugin.json +1 -1
- package/payload/platform/plugins/admin/PLUGIN.md +3 -6
- package/payload/platform/plugins/admin/mcp/dist/index.js +14 -60
- package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/admin/skills/insight/SKILL.md +24 -0
- package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +84 -31
- package/payload/platform/plugins/docs/PLUGIN.md +1 -0
- package/payload/platform/plugins/docs/references/admin-ui.md +1 -1
- package/payload/platform/plugins/docs/references/deployment.md +18 -5
- package/payload/platform/plugins/docs/references/graph.md +2 -0
- package/payload/platform/plugins/docs/references/internals.md +12 -1
- package/payload/platform/plugins/docs/references/memory-guide.md +4 -0
- package/payload/platform/plugins/docs/references/neo4j.md +2 -2
- package/payload/platform/plugins/docs/references/platform.md +1 -1
- package/payload/platform/plugins/docs/references/plugins-guide.md +1 -1
- package/payload/platform/plugins/docs/references/session-retrospective.md +5 -18
- package/payload/platform/plugins/docs/references/slides.md +31 -0
- package/payload/platform/plugins/docs/references/voice-mirror-guide.md +1 -1
- package/payload/platform/plugins/email/PLUGIN.md +2 -2
- package/payload/platform/plugins/email/mcp/dist/index.js +8 -0
- package/payload/platform/plugins/email/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/email/mcp/dist/lib/attachment-resolve.d.ts +19 -0
- package/payload/platform/plugins/email/mcp/dist/lib/attachment-resolve.d.ts.map +1 -0
- package/payload/platform/plugins/email/mcp/dist/lib/attachment-resolve.js +64 -0
- package/payload/platform/plugins/email/mcp/dist/lib/attachment-resolve.js.map +1 -0
- package/payload/platform/plugins/email/mcp/dist/lib/smtp.d.ts +4 -0
- package/payload/platform/plugins/email/mcp/dist/lib/smtp.d.ts.map +1 -1
- package/payload/platform/plugins/email/mcp/dist/lib/smtp.js +1 -0
- package/payload/platform/plugins/email/mcp/dist/lib/smtp.js.map +1 -1
- package/payload/platform/plugins/email/mcp/dist/tools/email-reply.d.ts +1 -0
- package/payload/platform/plugins/email/mcp/dist/tools/email-reply.d.ts.map +1 -1
- package/payload/platform/plugins/email/mcp/dist/tools/email-reply.js +6 -1
- package/payload/platform/plugins/email/mcp/dist/tools/email-reply.js.map +1 -1
- package/payload/platform/plugins/email/mcp/dist/tools/email-send.d.ts +1 -0
- package/payload/platform/plugins/email/mcp/dist/tools/email-send.d.ts.map +1 -1
- package/payload/platform/plugins/email/mcp/dist/tools/email-send.js +7 -1
- package/payload/platform/plugins/email/mcp/dist/tools/email-send.js.map +1 -1
- package/payload/platform/plugins/email/references/email-reference.md +4 -0
- package/payload/platform/plugins/memory/PLUGIN.md +1 -2
- package/payload/platform/plugins/memory/mcp/dist/index.js +6 -44
- package/payload/platform/plugins/memory/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/document-sectioniser.test.d.ts +2 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/document-sectioniser.test.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/document-sectioniser.test.js +41 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/document-sectioniser.test.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/graph-write-embed-net.test.d.ts +2 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/graph-write-embed-net.test.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/graph-write-embed-net.test.js +90 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/graph-write-embed-net.test.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/vector-indexed-labels-drift.test.d.ts +2 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/vector-indexed-labels-drift.test.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/vector-indexed-labels-drift.test.js +27 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/vector-indexed-labels-drift.test.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/document-sectioniser.d.ts +10 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/document-sectioniser.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/document-sectioniser.js +47 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/document-sectioniser.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/embeddings.d.ts +1 -2
- package/payload/platform/plugins/memory/mcp/dist/lib/embeddings.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/embeddings.js +5 -28
- package/payload/platform/plugins/memory/mcp/dist/lib/embeddings.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/build-text-representation-bound.test.d.ts +2 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/build-text-representation-bound.test.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/build-text-representation-bound.test.js +20 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/build-text-representation-bound.test.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/conversation-archive-preference-embed.test.d.ts +2 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/conversation-archive-preference-embed.test.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/conversation-archive-preference-embed.test.js +67 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/conversation-archive-preference-embed.test.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/embeddings-cap.test.d.ts +2 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/embeddings-cap.test.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/embeddings-cap.test.js +34 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/embeddings-cap.test.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-archive-section-writer.test.d.ts +2 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-archive-section-writer.test.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-archive-section-writer.test.js +61 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-archive-section-writer.test.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-archive-write.test.js +23 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-archive-write.test.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-reindex.test.d.ts +2 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-reindex.test.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-reindex.test.js +87 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-reindex.test.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-search-fields.test.js +3 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-search-fields.test.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-search-threshold.test.d.ts +2 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-search-threshold.test.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-search-threshold.test.js +34 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-search-threshold.test.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/conversation-archive-derive-insights.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/conversation-archive-derive-insights.js +19 -4
- package/payload/platform/plugins/memory/mcp/dist/tools/conversation-archive-derive-insights.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-archive-write.d.ts +33 -6
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-archive-write.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-archive-write.js +280 -10
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-archive-write.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-reindex.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-reindex.js +76 -37
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-reindex.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-search.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-search.js +11 -2
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-search.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-typed-edge-pass.d.ts +4 -4
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-typed-edge-pass.js +3 -3
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.js +20 -2
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/profile-update.js +6 -3
- package/payload/platform/plugins/memory/mcp/dist/tools/profile-update.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/vitest.config.ts +5 -0
- package/payload/platform/plugins/memory/references/schema-base.md +1 -1
- package/payload/platform/plugins/memory/references/schema-construction.md +72 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/ics-graph-ingest.d.ts.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/lib/ics-graph-ingest.js +15 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/ics-graph-ingest.js.map +1 -1
- package/payload/platform/plugins/slides/.claude-plugin/plugin.json +8 -0
- package/payload/platform/plugins/slides/LICENSE +21 -0
- package/payload/platform/plugins/slides/PLUGIN.md +18 -0
- package/payload/platform/plugins/slides/PROVENANCE.md +40 -0
- package/payload/platform/plugins/slides/commands/add-slide.md +29 -0
- package/payload/platform/plugins/slides/commands/slides-claus.md +39 -0
- package/payload/platform/plugins/slides/commands/slides-new-component.md +39 -0
- package/payload/platform/plugins/slides/commands/slides-outline.md +43 -0
- package/payload/platform/plugins/slides/commands/slides-review.md +52 -0
- package/payload/platform/plugins/slides/commands/slides-theme.md +64 -0
- package/payload/platform/plugins/slides/commands/slides.md +59 -0
- package/payload/platform/plugins/slides/skills/deck-system/REFERENCE.md +581 -0
- package/payload/platform/plugins/slides/skills/deck-system/SKILL.md +607 -0
- package/payload/platform/plugins/slides/skills/deck-system/STORYTELLING-board.md +426 -0
- package/payload/platform/plugins/slides/skills/deck-system/STORYTELLING-claus.md +185 -0
- package/payload/platform/plugins/slides/skills/deck-system/STORYTELLING-mbb.md +450 -0
- package/payload/platform/plugins/slides/skills/deck-system/STORYTELLING-product-launch.md +579 -0
- package/payload/platform/plugins/slides/skills/deck-system/STORYTELLING-sales.md +464 -0
- package/payload/platform/plugins/slides/skills/deck-system/STORYTELLING-sequoia.md +489 -0
- package/payload/platform/plugins/slides/skills/deck-system/STORYTELLING.md +273 -0
- package/payload/platform/plugins/slides/skills/deck-system/deck-craft.html +1371 -0
- package/payload/platform/plugins/slides/skills/deck-system/deck-solid.html +1667 -0
- package/payload/platform/plugins/slides/skills/deck-system/deck.html +1359 -0
- package/payload/platform/plugins/url-get/.claude-plugin/plugin.json +1 -1
- package/payload/platform/plugins/url-get/PLUGIN.md +26 -21
- package/payload/platform/plugins/url-get/mcp/dist/index.js +3 -3
- package/payload/platform/plugins/url-get/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/url-get/mcp/dist/tools/url-get.d.ts +1 -2
- package/payload/platform/plugins/url-get/mcp/dist/tools/url-get.d.ts.map +1 -1
- package/payload/platform/plugins/url-get/mcp/dist/tools/url-get.js +20 -40
- package/payload/platform/plugins/url-get/mcp/dist/tools/url-get.js.map +1 -1
- package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-execute.js +4 -0
- package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-execute.js.map +1 -1
- package/payload/platform/scripts/identity-forbidden-token-check.mjs +0 -1
- package/payload/platform/scripts/setup-account.sh +0 -15
- package/payload/platform/services/claude-session-manager/dist/agent-identity-locator.d.ts +23 -0
- package/payload/platform/services/claude-session-manager/dist/agent-identity-locator.d.ts.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/agent-identity-locator.js +29 -0
- package/payload/platform/services/claude-session-manager/dist/agent-identity-locator.js.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js +0 -2
- package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/http-server.d.ts +5 -0
- package/payload/platform/services/claude-session-manager/dist/http-server.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/http-server.js +32 -2
- package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.js +8 -1
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/public-agent-reachability.d.ts +13 -1
- package/payload/platform/services/claude-session-manager/dist/public-agent-reachability.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/public-agent-reachability.js +26 -2
- package/payload/platform/services/claude-session-manager/dist/public-agent-reachability.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/rc-daemon.js +2 -2
- package/payload/platform/services/claude-session-manager/dist/rc-daemon.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/system-prompt.d.ts +12 -3
- package/payload/platform/services/claude-session-manager/dist/system-prompt.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/system-prompt.js +3 -2
- package/payload/platform/services/claude-session-manager/dist/system-prompt.js.map +1 -1
- package/payload/platform/templates/agents/admin/IDENTITY.md +2 -2
- package/payload/platform/templates/specialists/agents/database-operator.md +3 -7
- package/payload/platform/templates/specialists/agents/typed-edge-classifier.md +1 -1
- package/payload/server/{chunk-M6A6EZD4.js → chunk-76HRO7NX.js} +16 -2
- package/payload/server/maxy-edge.js +1 -1
- package/payload/server/public/assets/AdminShell-T-YknnBn.js +1 -0
- package/payload/server/public/assets/Checkbox-DmDxpqVv.js +1 -0
- package/payload/server/public/assets/admin-COUV-jgt.js +1 -0
- package/payload/server/public/assets/{arc-aUiRP9AS.js → arc-B2CweJq3.js} +1 -1
- package/payload/server/public/assets/architecture-YZFGNWBL-Dnn6Hc65.js +1 -0
- package/payload/server/public/assets/{architectureDiagram-Q4EWVU46-c09loTER.js → architectureDiagram-Q4EWVU46-DP2o-MFV.js} +1 -1
- package/payload/server/public/assets/{blockDiagram-DXYQGD6D-Cjdeyoq1.js → blockDiagram-DXYQGD6D-DO4mcYDJ.js} +1 -1
- package/payload/server/public/assets/{c4Diagram-AHTNJAMY-NY6Wlzo2.js → c4Diagram-AHTNJAMY-Sy1giHbj.js} +1 -1
- package/payload/server/public/assets/channel-CEpR_0rE.js +1 -0
- package/payload/server/public/assets/{chunk-2KRD3SAO-BK3470lx.js → chunk-2KRD3SAO-CKsCYCsN.js} +1 -1
- package/payload/server/public/assets/chunk-336JU56O-C0-P-aUF.js +2 -0
- package/payload/server/public/assets/chunk-426QAEUC-DFjEt3Zb.js +1 -0
- package/payload/server/public/assets/{chunk-4BX2VUAB-BOvVdJLf.js → chunk-4BX2VUAB-B8bqAmBa.js} +1 -1
- package/payload/server/public/assets/{chunk-4TB4RGXK-BXpto3yW.js → chunk-4TB4RGXK-D1k0VSlW.js} +1 -1
- package/payload/server/public/assets/{chunk-55IACEB6-BwZyF7vR.js → chunk-55IACEB6-B-p_QNqz.js} +1 -1
- package/payload/server/public/assets/{chunk-5FUZZQ4R-C403gCUk.js → chunk-5FUZZQ4R-D6U6tV_j.js} +1 -1
- package/payload/server/public/assets/{chunk-5PVQY5BW-CjVzXQEp.js → chunk-5PVQY5BW-CYK76xfs.js} +1 -1
- package/payload/server/public/assets/{chunk-67CJDMHE-D5bhMrtY.js → chunk-67CJDMHE-BC9js-lf.js} +1 -1
- package/payload/server/public/assets/{chunk-7N4EOEYR-Si7Lgrwc.js → chunk-7N4EOEYR-4j2OqKkv.js} +1 -1
- package/payload/server/public/assets/{chunk-AA7GKIK3-DMuHtDqO.js → chunk-AA7GKIK3-Coen-fXN.js} +1 -1
- package/payload/server/public/assets/{chunk-BSJP7CBP-L79XKVcb.js → chunk-BSJP7CBP-CAiOBvec.js} +1 -1
- package/payload/server/public/assets/{chunk-CIAEETIT-C0O7Upmg.js → chunk-CIAEETIT-AJzzpZVb.js} +1 -1
- package/payload/server/public/assets/{chunk-EDXVE4YY-DJcJAsAg.js → chunk-EDXVE4YY-BL4BKozX.js} +1 -1
- package/payload/server/public/assets/{chunk-ENJZ2VHE-CFDNvYu1.js → chunk-ENJZ2VHE-mhAFG8UD.js} +1 -1
- package/payload/server/public/assets/{chunk-FMBD7UC4-C_E43NFJ.js → chunk-FMBD7UC4-H231gZA_.js} +1 -1
- package/payload/server/public/assets/{chunk-FOC6F5B3-D9lWWHAu.js → chunk-FOC6F5B3-Cl3ZZjYG.js} +1 -1
- package/payload/server/public/assets/{chunk-ICPOFSXX-ecLOxGhL.js → chunk-ICPOFSXX-DOEzvzJa.js} +2 -2
- package/payload/server/public/assets/{chunk-K5T4RW27-DuhsNH4c.js → chunk-K5T4RW27-C_ipbUDD.js} +1 -1
- package/payload/server/public/assets/{chunk-KGLVRYIC-B4-A1Abi.js → chunk-KGLVRYIC-CTsDNSCU.js} +1 -1
- package/payload/server/public/assets/{chunk-LIHQZDEY-BxqgHRgT.js → chunk-LIHQZDEY-DvSXhkGf.js} +1 -1
- package/payload/server/public/assets/{chunk-ORNJ4GCN-DEYQ5WaJ.js → chunk-ORNJ4GCN-p574NOI7.js} +1 -1
- package/payload/server/public/assets/{chunk-OYMX7WX6-B7MW66KB.js → chunk-OYMX7WX6-BlEgFM6U.js} +1 -1
- package/payload/server/public/assets/chunk-QZHKN3VN-DpF06ZZQ.js +1 -0
- package/payload/server/public/assets/{chunk-U2HBQHQK-BMawmsyk.js → chunk-U2HBQHQK-B2bDK0jv.js} +1 -1
- package/payload/server/public/assets/{chunk-X2U36JSP-CT6g7pno.js → chunk-X2U36JSP-D69BxKFw.js} +1 -1
- package/payload/server/public/assets/{chunk-XPW4576I-CBfZXZDB.js → chunk-XPW4576I-Dm-PcyUi.js} +1 -1
- package/payload/server/public/assets/{chunk-YZCP3GAM-xeAluiAH.js → chunk-YZCP3GAM-Be8RnXgx.js} +1 -1
- package/payload/server/public/assets/{chunk-ZZ45TVLE-BRN9qUC5.js → chunk-ZZ45TVLE-Ck8PCTa4.js} +1 -1
- package/payload/server/public/assets/classDiagram-6PBFFD2Q-CYbXvKLI.js +1 -0
- package/payload/server/public/assets/classDiagram-v2-HSJHXN6E-DEyHzRhq.js +1 -0
- package/payload/server/public/assets/clone-y8gexbBy.js +1 -0
- package/payload/server/public/assets/{cose-bilkent-S5V4N54A-Br2gjtEO.js → cose-bilkent-S5V4N54A-CmkW2Eaj.js} +1 -1
- package/payload/server/public/assets/{dagre-DTjePoco.js → dagre-Dqp-ns8F.js} +1 -1
- package/payload/server/public/assets/{dagre-KV5264BT-DHBkRke4.js → dagre-KV5264BT-ZgWWXPLc.js} +1 -1
- package/payload/server/public/assets/data-gy6QH9c1.js +1 -0
- package/payload/server/public/assets/{diagram-5BDNPKRD-BIq1-idL.js → diagram-5BDNPKRD-CTX5-ScM.js} +1 -1
- package/payload/server/public/assets/{diagram-G4DWMVQ6-BsIUDzV6.js → diagram-G4DWMVQ6-BovIsO6H.js} +1 -1
- package/payload/server/public/assets/{diagram-MMDJMWI5-CgHSri2i.js → diagram-MMDJMWI5-DcETsQy-.js} +1 -1
- package/payload/server/public/assets/{diagram-TYMM5635-Ce2Wh9ZX.js → diagram-TYMM5635-yyq6peoZ.js} +1 -1
- package/payload/server/public/assets/{erDiagram-SMLLAGMA-BU0Kh6OQ.js → erDiagram-SMLLAGMA-CiNToftB.js} +1 -1
- package/payload/server/public/assets/{flatten-Bo6YRmWl.js → flatten-BtFI066E.js} +1 -1
- package/payload/server/public/assets/{flowDiagram-DWJPFMVM-B0N06MF7.js → flowDiagram-DWJPFMVM-Xnl3SpIM.js} +1 -1
- package/payload/server/public/assets/{ganttDiagram-T4ZO3ILL-BVbx4ARZ.js → ganttDiagram-T4ZO3ILL-C1iyWe0f.js} +1 -1
- package/payload/server/public/assets/gitGraph-7Q5UKJZL-CNs-LD5i.js +1 -0
- package/payload/server/public/assets/{gitGraphDiagram-UUTBAWPF-C-xRJ94t.js → gitGraphDiagram-UUTBAWPF-D97pbMQb.js} +1 -1
- package/payload/server/public/assets/graph-labels-cZu4pK16.js +1 -0
- package/payload/server/public/assets/{graph-g48ZcA5M.js → graph-qz5tFKqU.js} +3 -3
- package/payload/server/public/assets/{graphlib-YmNcoMjY.js → graphlib-Lq8ijgON.js} +1 -1
- package/payload/server/public/assets/info-OMHHGYJF-DsTNigSS.js +1 -0
- package/payload/server/public/assets/infoDiagram-42DDH7IO-C_OarRTA.js +2 -0
- package/payload/server/public/assets/{isEmpty-D6Kr-M1M.js → isEmpty-D6QovjYR.js} +1 -1
- package/payload/server/public/assets/{ishikawaDiagram-UXIWVN3A-DTrq54yC.js → ishikawaDiagram-UXIWVN3A-B8XBdjJn.js} +1 -1
- package/payload/server/public/assets/{journeyDiagram-VCZTEJTY-OZZZMrFX.js → journeyDiagram-VCZTEJTY-CZYbiOaQ.js} +1 -1
- package/payload/server/public/assets/{kanban-definition-6JOO6SKY--w-IP9pN.js → kanban-definition-6JOO6SKY-B1PybFoh.js} +1 -1
- package/payload/server/public/assets/{line-Ckeulv5T.js → line-D-tw3hHp.js} +1 -1
- package/payload/server/public/assets/{linear-DOh_6k2k.js → linear-BHhXD3cd.js} +1 -1
- package/payload/server/public/assets/{mermaid-parser.core-CVRAxYRD.js → mermaid-parser.core-C9RAnysF.js} +2 -2
- package/payload/server/public/assets/{mermaid.core-B-mE18I1.js → mermaid.core-B532LT1r.js} +3 -3
- package/payload/server/public/assets/{mindmap-definition-QFDTVHPH-Bm8mDicL.js → mindmap-definition-QFDTVHPH-DGlgeeTV.js} +1 -1
- package/payload/server/public/assets/{ordinal-BDi6f4xk.js → ordinal-Bl-aM5b9.js} +1 -1
- package/payload/server/public/assets/packet-4T2RLAQJ-DGES22b-.js +1 -0
- package/payload/server/public/assets/pie-ZZUOXDRM-ChKeDbzt.js +1 -0
- package/payload/server/public/assets/{pieDiagram-DEJITSTG-BCmRLgGO.js → pieDiagram-DEJITSTG-DV9FIWko.js} +1 -1
- package/payload/server/public/assets/{public-DknO-g9S.js → public-Bu2_Xi0a.js} +5 -5
- package/payload/server/public/assets/{quadrantDiagram-34T5L4WZ-CniTIUTm.js → quadrantDiagram-34T5L4WZ-Betwya4l.js} +1 -1
- package/payload/server/public/assets/radar-PYXPWWZC-FGG5Fs7N.js +1 -0
- package/payload/server/public/assets/{reduce-CGi9ik8i.js → reduce-BD4xUd2c.js} +1 -1
- package/payload/server/public/assets/{requirementDiagram-MS252O5E-CoxBSj9M.js → requirementDiagram-MS252O5E-Cq3vODdg.js} +1 -1
- package/payload/server/public/assets/{sankeyDiagram-XADWPNL6-BjS-4jzq.js → sankeyDiagram-XADWPNL6-x8krXWcS.js} +1 -1
- package/payload/server/public/assets/{sequenceDiagram-FGHM5R23-B9jVOnPR.js → sequenceDiagram-FGHM5R23-i-_uH-Yl.js} +1 -1
- package/payload/server/public/assets/{stateDiagram-FHFEXIEX-BvOQPzP8.js → stateDiagram-FHFEXIEX-il4KqSgI.js} +1 -1
- package/payload/server/public/assets/stateDiagram-v2-QKLJ7IA2-B6zNJ6Tv.js +1 -0
- package/payload/server/public/assets/{timeline-definition-GMOUNBTQ-CdfgWLo1.js → timeline-definition-GMOUNBTQ-DATdZkA5.js} +1 -1
- package/payload/server/public/assets/treeView-SZITEDCU-VAQQdbtf.js +1 -0
- package/payload/server/public/assets/treemap-W4RFUUIX-DKchO3zI.js +1 -0
- package/payload/server/public/assets/useSelectionMode-A5KItZ2T.js +13 -0
- package/payload/server/public/assets/{brand-D0gNihp7.css → useSelectionMode-C-Ojh7W9.css} +1 -1
- package/payload/server/public/assets/{vennDiagram-DHZGUBPP-JCgpIbj-.js → vennDiagram-DHZGUBPP-BJh9tJTt.js} +1 -1
- package/payload/server/public/assets/wardley-RL74JXVD-CBGtx0bS.js +1 -0
- package/payload/server/public/assets/{wardleyDiagram-NUSXRM2D-Dei3VqHo.js → wardleyDiagram-NUSXRM2D-EMN1Hdfg.js} +1 -1
- package/payload/server/public/assets/{xychartDiagram-5P7HB3ND-DUtIyoIb.js → xychartDiagram-5P7HB3ND-DbUWXa7T.js} +1 -1
- package/payload/server/public/data.html +5 -5
- package/payload/server/public/graph.html +6 -6
- package/payload/server/public/index.html +5 -6
- package/payload/server/public/public.html +4 -5
- package/payload/server/server.js +525 -39
- package/payload/platform/plugins/admin/hooks/__tests__/session-end-retrospective.test.sh +0 -396
- package/payload/platform/plugins/admin/hooks/lib/admin-graph-pass-common.sh +0 -239
- package/payload/platform/plugins/admin/hooks/session-end-retrospective.sh +0 -214
- package/payload/server/public/assets/AdminShell-892Jy_rs.js +0 -1
- package/payload/server/public/assets/Checkbox-Bc2QzX9b.js +0 -1
- package/payload/server/public/assets/admin-D3K13ndi.js +0 -1
- package/payload/server/public/assets/architecture-YZFGNWBL--v-pJPNp.js +0 -1
- package/payload/server/public/assets/brand-CcN3dELF.js +0 -9
- package/payload/server/public/assets/channel-B1IT7to2.js +0 -1
- package/payload/server/public/assets/chunk-336JU56O-CdKRCIeE.js +0 -2
- package/payload/server/public/assets/chunk-426QAEUC-BybuQ3Ve.js +0 -1
- package/payload/server/public/assets/chunk-QZHKN3VN-Bd-GrQM6.js +0 -1
- package/payload/server/public/assets/classDiagram-6PBFFD2Q-rjCize6i.js +0 -1
- package/payload/server/public/assets/classDiagram-v2-HSJHXN6E-BORWOUt0.js +0 -1
- package/payload/server/public/assets/clone-Csqv5U6T.js +0 -1
- package/payload/server/public/assets/data-Br-pdljK.js +0 -1
- package/payload/server/public/assets/gitGraph-7Q5UKJZL-CI0s_tqn.js +0 -1
- package/payload/server/public/assets/graph-labels-BYH-IPCb.js +0 -1
- package/payload/server/public/assets/info-OMHHGYJF-g3gYW7Qm.js +0 -1
- package/payload/server/public/assets/infoDiagram-42DDH7IO-Di6oPQ_-.js +0 -2
- package/payload/server/public/assets/packet-4T2RLAQJ-CT0TB9HI.js +0 -1
- package/payload/server/public/assets/pie-ZZUOXDRM-CXLe7TFF.js +0 -1
- package/payload/server/public/assets/radar-PYXPWWZC-DnPLBl-D.js +0 -1
- package/payload/server/public/assets/stateDiagram-v2-QKLJ7IA2-v4ND10uR.js +0 -1
- package/payload/server/public/assets/treeView-SZITEDCU-C3cb7Xwe.js +0 -1
- package/payload/server/public/assets/treemap-W4RFUUIX-Dc7G3Bgm.js +0 -1
- package/payload/server/public/assets/useSelectionMode-DwsyptOw.js +0 -5
- package/payload/server/public/assets/wardley-RL74JXVD-DtgibWAt.js +0 -1
- /package/payload/server/public/assets/{_baseFor-Cam2PbSt.js → _baseFor-Cs8Y-rGh.js} +0 -0
- /package/payload/server/public/assets/{array-DYRGGQae.js → array-iHZP4KWJ.js} +0 -0
- /package/payload/server/public/assets/{cytoscape.esm-nWsJMTNI.js → cytoscape.esm-BR2GOQ8_.js} +0 -0
- /package/payload/server/public/assets/{defaultLocale-Du1XY3Dp.js → defaultLocale-B9aLeOTg.js} +0 -0
- /package/payload/server/public/assets/{dist-BzAsli7o.js → dist-DB-VPj_8.js} +0 -0
- /package/payload/server/public/assets/{init-B5BXBRcm.js → init-BNFRgqHM.js} +0 -0
- /package/payload/server/public/assets/{katex-HOUACuRw.js → katex-B-EfS3nw.js} +0 -0
- /package/payload/server/public/assets/{path-CNO468J-.js → path-DmWWdwp7.js} +0 -0
- /package/payload/server/public/assets/{rough.esm-DRO6hWPh.js → rough.esm-Ci7Kjt46.js} +0 -0
- /package/payload/server/public/assets/{src-CWiyyVfn.js → src-C1jfwBq0.js} +0 -0
|
@@ -1,396 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# Task 282 — session-end retrospective Stop hook test suite.
|
|
3
|
-
#
|
|
4
|
-
# Listener-mock pattern: a local Python HTTP server stands in for
|
|
5
|
-
# /api/admin/log-ingest and records every POST so the assertions can read
|
|
6
|
-
# every log line the hook emitted.
|
|
7
|
-
#
|
|
8
|
-
# Contract under test:
|
|
9
|
-
# - All log emissions go through POST /api/admin/log-ingest. Hook stderr
|
|
10
|
-
# stays silent on every path EXCEPT the gate-blocked path, which writes
|
|
11
|
-
# the three-pass retrospective instruction block to stderr (Stop-hook
|
|
12
|
-
# contract — that is how the agent receives the instruction).
|
|
13
|
-
# - Every gated-off path emits one `trigger-skipped reason=<r>` line and
|
|
14
|
-
# exits 0:
|
|
15
|
-
# role-not-admin | is-specialist | empty-stdin | missing-transcript |
|
|
16
|
-
# no-intent-match
|
|
17
|
-
# - End-intent triggers: `/end`, `/archive`, `end session`,
|
|
18
|
-
# `archive this session`, applied case-insensitively to the latest
|
|
19
|
-
# real-user message. `tool_result`-only user records are NOT the
|
|
20
|
-
# latest user message. Embedded prose ("I'll end session by 5pm")
|
|
21
|
-
# does NOT trigger.
|
|
22
|
-
# - When the intent matches and the sentinel `tool_use` of
|
|
23
|
-
# `session-retrospective-mark-complete` is absent from the JSONL:
|
|
24
|
-
# exit 2, stderr carries the instruction block,
|
|
25
|
-
# `gate-blocked sessionId=<id> reason=sentinel-absent` emitted.
|
|
26
|
-
# - When the intent matches and the sentinel `tool_use` is present:
|
|
27
|
-
# exit 0, `gate-released sessionId=<id>` emitted.
|
|
28
|
-
# - Re-entry: a second Stop fired after the sentinel call still releases
|
|
29
|
-
# (the sentinel-grep is the re-entry guard).
|
|
30
|
-
# - Per-session scoping: a sentinel in a DIFFERENT session's JSONL does
|
|
31
|
-
# NOT release the active gate.
|
|
32
|
-
|
|
33
|
-
set -u
|
|
34
|
-
|
|
35
|
-
HOOK="$(cd "$(dirname "$0")/.." && pwd)/session-end-retrospective.sh"
|
|
36
|
-
if [[ ! -x "$HOOK" ]]; then
|
|
37
|
-
echo "FAIL: $HOOK not executable" >&2
|
|
38
|
-
exit 1
|
|
39
|
-
fi
|
|
40
|
-
|
|
41
|
-
OP_ID='aaaaaaaa-1111-2222-3333-bbbbbbbbbbbb'
|
|
42
|
-
OTHER_ID='99999999-7777-6666-5555-444444444444'
|
|
43
|
-
|
|
44
|
-
TMPFILES=()
|
|
45
|
-
LISTENER_PIDS=()
|
|
46
|
-
cleanup_test_state() {
|
|
47
|
-
for pid in "${LISTENER_PIDS[@]:-}"; do
|
|
48
|
-
if [[ -n "$pid" ]]; then
|
|
49
|
-
kill "$pid" 2>/dev/null || true
|
|
50
|
-
wait "$pid" 2>/dev/null || true
|
|
51
|
-
fi
|
|
52
|
-
done
|
|
53
|
-
for f in "${TMPFILES[@]:-}"; do
|
|
54
|
-
[[ -n "$f" ]] && rm -f "$f" 2>/dev/null || true
|
|
55
|
-
done
|
|
56
|
-
}
|
|
57
|
-
trap cleanup_test_state EXIT
|
|
58
|
-
|
|
59
|
-
PASS=0
|
|
60
|
-
FAIL=0
|
|
61
|
-
pass() { echo "PASS: $1"; PASS=$((PASS + 1)); }
|
|
62
|
-
fail() { echo "FAIL: $1" >&2; FAIL=$((FAIL + 1)); }
|
|
63
|
-
|
|
64
|
-
start_listener() {
|
|
65
|
-
REQ_LOG=$(mktemp); TMPFILES+=("$REQ_LOG")
|
|
66
|
-
LISTENER_PORT=$((39500 + RANDOM % 100))
|
|
67
|
-
python3 - "$LISTENER_PORT" "$REQ_LOG" <<'PY' &
|
|
68
|
-
import sys, http.server, json
|
|
69
|
-
port = int(sys.argv[1])
|
|
70
|
-
log_path = sys.argv[2]
|
|
71
|
-
class H(http.server.BaseHTTPRequestHandler):
|
|
72
|
-
def log_message(self, *a, **k): pass
|
|
73
|
-
def do_POST(self):
|
|
74
|
-
n = int(self.headers.get('Content-Length','0') or 0)
|
|
75
|
-
body = self.rfile.read(n).decode('utf-8','replace')
|
|
76
|
-
with open(log_path, 'a', encoding='utf-8') as f:
|
|
77
|
-
f.write(self.path + '\t' + body + '\n')
|
|
78
|
-
self.send_response(200)
|
|
79
|
-
self.send_header('Content-Type','application/json')
|
|
80
|
-
self.end_headers()
|
|
81
|
-
self.wfile.write(json.dumps({"ok": True}).encode('utf-8'))
|
|
82
|
-
http.server.HTTPServer(('127.0.0.1', port), H).serve_forever()
|
|
83
|
-
PY
|
|
84
|
-
LISTENER_PIDS+=("$!")
|
|
85
|
-
for _ in $(seq 1 20); do
|
|
86
|
-
if curl -sS --max-time 1 -X POST "http://127.0.0.1:${LISTENER_PORT}/ping" -d '{}' >/dev/null 2>&1; then
|
|
87
|
-
break
|
|
88
|
-
fi
|
|
89
|
-
sleep 0.1
|
|
90
|
-
done
|
|
91
|
-
: > "$REQ_LOG"
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
run_hook() {
|
|
95
|
-
local role="$1"; local specialist="$2"; local stdin_json="$3"
|
|
96
|
-
local stderr_file; stderr_file=$(mktemp); TMPFILES+=("$stderr_file")
|
|
97
|
-
local stdout_file; stdout_file=$(mktemp); TMPFILES+=("$stdout_file")
|
|
98
|
-
printf '%s' "$stdin_json" | \
|
|
99
|
-
MAXY_SESSION_ROLE="$role" \
|
|
100
|
-
MAXY_SPECIALIST="$specialist" \
|
|
101
|
-
MAXY_UI_INTERNAL_PORT="$LISTENER_PORT" \
|
|
102
|
-
bash "$HOOK" >"$stdout_file" 2>"$stderr_file"
|
|
103
|
-
HOOK_RC=$?
|
|
104
|
-
HOOK_STDERR=$(cat "$stderr_file")
|
|
105
|
-
HOOK_STDOUT=$(cat "$stdout_file")
|
|
106
|
-
sleep 0.1
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
ingest_lines() {
|
|
110
|
-
grep -E '^/api/admin/log-ingest ' "$REQ_LOG" 2>/dev/null | python3 -c '
|
|
111
|
-
import sys, json
|
|
112
|
-
for raw in sys.stdin:
|
|
113
|
-
try:
|
|
114
|
-
_, body = raw.rstrip("\n").split("\t", 1)
|
|
115
|
-
d = json.loads(body)
|
|
116
|
-
if isinstance(d, dict) and isinstance(d.get("line"), str):
|
|
117
|
-
print(d["line"])
|
|
118
|
-
except Exception:
|
|
119
|
-
pass
|
|
120
|
-
' || true
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
# Build a JSONL transcript from positional jq-style triples. Each triple is
|
|
124
|
-
# role|text|extra where extra is one of:
|
|
125
|
-
# "" → plain user/assistant text record
|
|
126
|
-
# "tool_use:<NAME>" → assistant record carrying a
|
|
127
|
-
# tool_use block of that name
|
|
128
|
-
# "tool_result" → user record carrying only a
|
|
129
|
-
# tool_result block (not a real
|
|
130
|
-
# user turn)
|
|
131
|
-
write_transcript() {
|
|
132
|
-
local out="$1"; shift
|
|
133
|
-
: > "$out"
|
|
134
|
-
python3 - "$out" "$@" <<'PY'
|
|
135
|
-
import sys, json
|
|
136
|
-
out = sys.argv[1]
|
|
137
|
-
spec = sys.argv[2:]
|
|
138
|
-
with open(out, "w", encoding="utf-8") as f:
|
|
139
|
-
for entry in spec:
|
|
140
|
-
role, text, extra = entry.split("|", 2)
|
|
141
|
-
rec = {"type": role, "timestamp": "2026-05-24T10:00:00.000Z"}
|
|
142
|
-
if role == "user":
|
|
143
|
-
if extra == "tool_result":
|
|
144
|
-
rec["message"] = {
|
|
145
|
-
"role": "user",
|
|
146
|
-
"content": [
|
|
147
|
-
{"type": "tool_result",
|
|
148
|
-
"tool_use_id": "tu_x",
|
|
149
|
-
"content": text or "ok"},
|
|
150
|
-
],
|
|
151
|
-
}
|
|
152
|
-
else:
|
|
153
|
-
rec["message"] = {"role": "user", "content": text}
|
|
154
|
-
else:
|
|
155
|
-
content_blocks = []
|
|
156
|
-
if text:
|
|
157
|
-
content_blocks.append({"type": "text", "text": text})
|
|
158
|
-
if extra.startswith("tool_use:"):
|
|
159
|
-
tool_name = extra.split(":", 1)[1]
|
|
160
|
-
content_blocks.append({
|
|
161
|
-
"type": "tool_use",
|
|
162
|
-
"id": f"toolu_{tool_name}",
|
|
163
|
-
"name": tool_name,
|
|
164
|
-
"input": {},
|
|
165
|
-
})
|
|
166
|
-
rec["message"] = {
|
|
167
|
-
"id": f"msg_{role}_{hash(text) & 0xffff:04x}",
|
|
168
|
-
"role": "assistant",
|
|
169
|
-
"content": content_blocks,
|
|
170
|
-
}
|
|
171
|
-
f.write(json.dumps(rec) + "\n")
|
|
172
|
-
PY
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
envelope_for() {
|
|
176
|
-
python3 -c '
|
|
177
|
-
import json, sys
|
|
178
|
-
print(json.dumps({"session_id": sys.argv[1], "transcript_path": sys.argv[2]}))
|
|
179
|
-
' "$1" "$2"
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
# ---------------------------------------------------------------------------
|
|
183
|
-
# Case 1: role != admin → trigger-skipped reason=role-not-admin
|
|
184
|
-
# ---------------------------------------------------------------------------
|
|
185
|
-
start_listener
|
|
186
|
-
TRANSCRIPT_PUBLIC=$(mktemp); TMPFILES+=("$TRANSCRIPT_PUBLIC")
|
|
187
|
-
write_transcript "$TRANSCRIPT_PUBLIC" "user|/end|"
|
|
188
|
-
ENV_PUBLIC=$(envelope_for "$OP_ID" "$TRANSCRIPT_PUBLIC")
|
|
189
|
-
: > "$REQ_LOG"
|
|
190
|
-
run_hook "public" "" "$ENV_PUBLIC"
|
|
191
|
-
[[ "$HOOK_RC" -eq 0 ]] || fail "case-1 rc=$HOOK_RC"
|
|
192
|
-
[[ -z "$HOOK_STDERR" ]] || fail "case-1 stderr must be empty, got: $HOOK_STDERR"
|
|
193
|
-
if ingest_lines | grep -qE '^trigger-skipped sessionId=.* reason=role-not-admin$'; then
|
|
194
|
-
pass "case-1 role=public → trigger-skipped reason=role-not-admin"
|
|
195
|
-
else
|
|
196
|
-
fail "case-1 expected role-not-admin, got: $(ingest_lines)"
|
|
197
|
-
fi
|
|
198
|
-
|
|
199
|
-
# ---------------------------------------------------------------------------
|
|
200
|
-
# Case 2: MAXY_SPECIALIST set → trigger-skipped is-specialist
|
|
201
|
-
# ---------------------------------------------------------------------------
|
|
202
|
-
: > "$REQ_LOG"
|
|
203
|
-
run_hook "admin" "database-operator" "$ENV_PUBLIC"
|
|
204
|
-
[[ "$HOOK_RC" -eq 0 ]] || fail "case-2 rc=$HOOK_RC"
|
|
205
|
-
[[ -z "$HOOK_STDERR" ]] || fail "case-2 stderr must be empty, got: $HOOK_STDERR"
|
|
206
|
-
if ingest_lines | grep -qE '^trigger-skipped sessionId=.* reason=is-specialist specialist=database-operator$'; then
|
|
207
|
-
pass "case-2 specialist=database-operator → trigger-skipped is-specialist"
|
|
208
|
-
else
|
|
209
|
-
fail "case-2 expected is-specialist, got: $(ingest_lines)"
|
|
210
|
-
fi
|
|
211
|
-
|
|
212
|
-
# ---------------------------------------------------------------------------
|
|
213
|
-
# Case 3: empty stdin → trigger-skipped reason=empty-stdin
|
|
214
|
-
# ---------------------------------------------------------------------------
|
|
215
|
-
: > "$REQ_LOG"
|
|
216
|
-
run_hook "admin" "" ""
|
|
217
|
-
[[ "$HOOK_RC" -eq 0 ]] || fail "case-3 rc=$HOOK_RC"
|
|
218
|
-
if ingest_lines | grep -qE '^trigger-skipped sessionId=.* reason=empty-stdin$'; then
|
|
219
|
-
pass "case-3 empty stdin → trigger-skipped reason=empty-stdin"
|
|
220
|
-
else
|
|
221
|
-
fail "case-3 expected empty-stdin, got: $(ingest_lines)"
|
|
222
|
-
fi
|
|
223
|
-
|
|
224
|
-
# ---------------------------------------------------------------------------
|
|
225
|
-
# Case 4: missing transcript_path → trigger-skipped missing-transcript
|
|
226
|
-
# ---------------------------------------------------------------------------
|
|
227
|
-
: > "$REQ_LOG"
|
|
228
|
-
BAD_ENV=$(python3 -c 'import json,sys; print(json.dumps({"session_id": sys.argv[1]}))' "$OP_ID")
|
|
229
|
-
run_hook "admin" "" "$BAD_ENV"
|
|
230
|
-
[[ "$HOOK_RC" -eq 0 ]] || fail "case-4 rc=$HOOK_RC"
|
|
231
|
-
if ingest_lines | grep -qE "^trigger-skipped sessionId=${OP_ID} reason=missing-transcript$"; then
|
|
232
|
-
pass "case-4 missing transcript → trigger-skipped reason=missing-transcript"
|
|
233
|
-
else
|
|
234
|
-
fail "case-4 expected missing-transcript, got: $(ingest_lines)"
|
|
235
|
-
fi
|
|
236
|
-
|
|
237
|
-
# ---------------------------------------------------------------------------
|
|
238
|
-
# Case 5: no end-intent token → trigger-skipped reason=no-intent-match
|
|
239
|
-
# ---------------------------------------------------------------------------
|
|
240
|
-
TRANSCRIPT_BORING=$(mktemp); TMPFILES+=("$TRANSCRIPT_BORING")
|
|
241
|
-
write_transcript "$TRANSCRIPT_BORING" \
|
|
242
|
-
"user|hello|" \
|
|
243
|
-
"assistant|hi|" \
|
|
244
|
-
"user|what's the weather|"
|
|
245
|
-
ENV_BORING=$(envelope_for "$OP_ID" "$TRANSCRIPT_BORING")
|
|
246
|
-
: > "$REQ_LOG"
|
|
247
|
-
run_hook "admin" "" "$ENV_BORING"
|
|
248
|
-
[[ "$HOOK_RC" -eq 0 ]] || fail "case-5 rc=$HOOK_RC"
|
|
249
|
-
[[ -z "$HOOK_STDERR" ]] || fail "case-5 stderr must be empty"
|
|
250
|
-
if ingest_lines | grep -qE "^trigger-skipped sessionId=${OP_ID} reason=no-intent-match$"; then
|
|
251
|
-
pass "case-5 no intent token → trigger-skipped reason=no-intent-match"
|
|
252
|
-
else
|
|
253
|
-
fail "case-5 expected no-intent-match, got: $(ingest_lines)"
|
|
254
|
-
fi
|
|
255
|
-
|
|
256
|
-
# ---------------------------------------------------------------------------
|
|
257
|
-
# Case 6: each intent token triggers gate-blocked (sentinel absent)
|
|
258
|
-
# ---------------------------------------------------------------------------
|
|
259
|
-
for TOKEN in "/end" "/archive" "end session" "archive this session"; do
|
|
260
|
-
T_FILE=$(mktemp); TMPFILES+=("$T_FILE")
|
|
261
|
-
write_transcript "$T_FILE" "user|hi|" "assistant|hello|" "user|${TOKEN}|"
|
|
262
|
-
ENV_BLOCK=$(envelope_for "$OP_ID" "$T_FILE")
|
|
263
|
-
: > "$REQ_LOG"
|
|
264
|
-
run_hook "admin" "" "$ENV_BLOCK"
|
|
265
|
-
if [[ "$HOOK_RC" -ne 2 ]]; then
|
|
266
|
-
fail "case-6:${TOKEN} expected rc=2, got $HOOK_RC"
|
|
267
|
-
continue
|
|
268
|
-
fi
|
|
269
|
-
if ! ingest_lines | grep -qE "^trigger sessionId=${OP_ID} token=${TOKEN}$"; then
|
|
270
|
-
fail "case-6:${TOKEN} missing trigger line, got: $(ingest_lines)"
|
|
271
|
-
continue
|
|
272
|
-
fi
|
|
273
|
-
if ! ingest_lines | grep -qE "^gate-blocked sessionId=${OP_ID} reason=sentinel-absent$"; then
|
|
274
|
-
fail "case-6:${TOKEN} missing gate-blocked line, got: $(ingest_lines)"
|
|
275
|
-
continue
|
|
276
|
-
fi
|
|
277
|
-
if ! printf '%s' "$HOOK_STDERR" | grep -q "session-retrospective-mark-complete"; then
|
|
278
|
-
fail "case-6:${TOKEN} instruction block missing sentinel tool name on stderr"
|
|
279
|
-
continue
|
|
280
|
-
fi
|
|
281
|
-
if ! printf '%s' "$HOOK_STDERR" | grep -q "database-operator"; then
|
|
282
|
-
fail "case-6:${TOKEN} instruction block missing database-operator reference"
|
|
283
|
-
continue
|
|
284
|
-
fi
|
|
285
|
-
pass "case-6:${TOKEN} triggers gate-blocked exit 2 + instruction stderr"
|
|
286
|
-
done
|
|
287
|
-
|
|
288
|
-
# ---------------------------------------------------------------------------
|
|
289
|
-
# Case 7: intent token + sentinel present → gate-released exit 0
|
|
290
|
-
# ---------------------------------------------------------------------------
|
|
291
|
-
T_RELEASE=$(mktemp); TMPFILES+=("$T_RELEASE")
|
|
292
|
-
write_transcript "$T_RELEASE" \
|
|
293
|
-
"user|hi|" \
|
|
294
|
-
"assistant|hello|" \
|
|
295
|
-
"user|/end|" \
|
|
296
|
-
"assistant|running retrospective|" \
|
|
297
|
-
"assistant||tool_use:mcp__admin__session-retrospective-mark-complete"
|
|
298
|
-
ENV_REL=$(envelope_for "$OP_ID" "$T_RELEASE")
|
|
299
|
-
: > "$REQ_LOG"
|
|
300
|
-
run_hook "admin" "" "$ENV_REL"
|
|
301
|
-
[[ "$HOOK_RC" -eq 0 ]] || fail "case-7 expected rc=0, got $HOOK_RC"
|
|
302
|
-
[[ -z "$HOOK_STDERR" ]] || fail "case-7 stderr must be empty, got: $HOOK_STDERR"
|
|
303
|
-
if ingest_lines | grep -qE "^gate-released sessionId=${OP_ID}$"; then
|
|
304
|
-
pass "case-7 sentinel present → gate-released exit 0"
|
|
305
|
-
else
|
|
306
|
-
fail "case-7 expected gate-released, got: $(ingest_lines)"
|
|
307
|
-
fi
|
|
308
|
-
|
|
309
|
-
# ---------------------------------------------------------------------------
|
|
310
|
-
# Case 8: re-entry — Stop fires twice after sentinel call, both release
|
|
311
|
-
# ---------------------------------------------------------------------------
|
|
312
|
-
: > "$REQ_LOG"
|
|
313
|
-
run_hook "admin" "" "$ENV_REL"
|
|
314
|
-
[[ "$HOOK_RC" -eq 0 ]] || fail "case-8 second-stop rc=$HOOK_RC"
|
|
315
|
-
if ingest_lines | grep -qE "^gate-released sessionId=${OP_ID}$"; then
|
|
316
|
-
pass "case-8 re-entry: second Stop after sentinel still releases"
|
|
317
|
-
else
|
|
318
|
-
fail "case-8 second Stop did not emit gate-released, got: $(ingest_lines)"
|
|
319
|
-
fi
|
|
320
|
-
|
|
321
|
-
# ---------------------------------------------------------------------------
|
|
322
|
-
# Case 9: per-session scoping — sentinel lives in a different session's
|
|
323
|
-
# JSONL; active gate must NOT release
|
|
324
|
-
# ---------------------------------------------------------------------------
|
|
325
|
-
T_OTHER=$(mktemp); TMPFILES+=("$T_OTHER")
|
|
326
|
-
write_transcript "$T_OTHER" \
|
|
327
|
-
"user|/end|" \
|
|
328
|
-
"assistant||tool_use:mcp__admin__session-retrospective-mark-complete"
|
|
329
|
-
# Active gate inspects only T_ACTIVE (no sentinel inside).
|
|
330
|
-
T_ACTIVE=$(mktemp); TMPFILES+=("$T_ACTIVE")
|
|
331
|
-
write_transcript "$T_ACTIVE" "user|/end|"
|
|
332
|
-
ENV_ACTIVE=$(envelope_for "$OP_ID" "$T_ACTIVE")
|
|
333
|
-
: > "$REQ_LOG"
|
|
334
|
-
run_hook "admin" "" "$ENV_ACTIVE"
|
|
335
|
-
[[ "$HOOK_RC" -eq 2 ]] || fail "case-9 expected rc=2 (other session's sentinel must not release), got $HOOK_RC"
|
|
336
|
-
if ingest_lines | grep -qE "^gate-blocked sessionId=${OP_ID} reason=sentinel-absent$"; then
|
|
337
|
-
pass "case-9 per-session scoping: sentinel in another transcript does not release"
|
|
338
|
-
else
|
|
339
|
-
fail "case-9 expected gate-blocked, got: $(ingest_lines)"
|
|
340
|
-
fi
|
|
341
|
-
|
|
342
|
-
# ---------------------------------------------------------------------------
|
|
343
|
-
# Case 10: `tool_result`-only user record is NOT the latest user message;
|
|
344
|
-
# the prior real user message wins.
|
|
345
|
-
# ---------------------------------------------------------------------------
|
|
346
|
-
T_TR_ONLY=$(mktemp); TMPFILES+=("$T_TR_ONLY")
|
|
347
|
-
write_transcript "$T_TR_ONLY" \
|
|
348
|
-
"user|/end|" \
|
|
349
|
-
"assistant||tool_use:Read" \
|
|
350
|
-
"user|noise|tool_result"
|
|
351
|
-
ENV_TR=$(envelope_for "$OP_ID" "$T_TR_ONLY")
|
|
352
|
-
: > "$REQ_LOG"
|
|
353
|
-
run_hook "admin" "" "$ENV_TR"
|
|
354
|
-
[[ "$HOOK_RC" -eq 2 ]] || fail "case-10 expected rc=2 (tool_result must not mask /end), got $HOOK_RC"
|
|
355
|
-
if ingest_lines | grep -qE "^gate-blocked sessionId=${OP_ID} reason=sentinel-absent$"; then
|
|
356
|
-
pass "case-10 tool_result-only user record does not mask latest real user /end"
|
|
357
|
-
else
|
|
358
|
-
fail "case-10 expected gate-blocked, got: $(ingest_lines)"
|
|
359
|
-
fi
|
|
360
|
-
|
|
361
|
-
# ---------------------------------------------------------------------------
|
|
362
|
-
# Case 11: embedded prose does NOT trigger ("I'll end session by 5pm")
|
|
363
|
-
# ---------------------------------------------------------------------------
|
|
364
|
-
T_PROSE=$(mktemp); TMPFILES+=("$T_PROSE")
|
|
365
|
-
write_transcript "$T_PROSE" "user|I'll end session by 5pm|"
|
|
366
|
-
ENV_PROSE=$(envelope_for "$OP_ID" "$T_PROSE")
|
|
367
|
-
: > "$REQ_LOG"
|
|
368
|
-
run_hook "admin" "" "$ENV_PROSE"
|
|
369
|
-
[[ "$HOOK_RC" -eq 0 ]] || fail "case-11 expected rc=0 (prose must not trigger), got $HOOK_RC"
|
|
370
|
-
if ingest_lines | grep -qE "^trigger-skipped sessionId=${OP_ID} reason=no-intent-match$"; then
|
|
371
|
-
pass "case-11 embedded prose 'end session' does not trigger gate"
|
|
372
|
-
else
|
|
373
|
-
fail "case-11 expected no-intent-match, got: $(ingest_lines)"
|
|
374
|
-
fi
|
|
375
|
-
|
|
376
|
-
# ---------------------------------------------------------------------------
|
|
377
|
-
# Case 12: case-insensitive match — `/END` triggers like `/end`
|
|
378
|
-
# ---------------------------------------------------------------------------
|
|
379
|
-
T_CASE=$(mktemp); TMPFILES+=("$T_CASE")
|
|
380
|
-
write_transcript "$T_CASE" "user|/END|"
|
|
381
|
-
ENV_CASE=$(envelope_for "$OP_ID" "$T_CASE")
|
|
382
|
-
: > "$REQ_LOG"
|
|
383
|
-
run_hook "admin" "" "$ENV_CASE"
|
|
384
|
-
[[ "$HOOK_RC" -eq 2 ]] || fail "case-12 expected rc=2 for /END, got $HOOK_RC"
|
|
385
|
-
if ingest_lines | grep -qE "^trigger sessionId=${OP_ID} token=/end$"; then
|
|
386
|
-
pass "case-12 case-insensitive: /END normalises to /end"
|
|
387
|
-
else
|
|
388
|
-
fail "case-12 expected token=/end, got: $(ingest_lines)"
|
|
389
|
-
fi
|
|
390
|
-
|
|
391
|
-
# ---------------------------------------------------------------------------
|
|
392
|
-
# Summary
|
|
393
|
-
# ---------------------------------------------------------------------------
|
|
394
|
-
echo
|
|
395
|
-
echo "session-end-retrospective tests: $PASS passed, $FAIL failed"
|
|
396
|
-
[[ "$FAIL" -eq 0 ]]
|
|
@@ -1,239 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# Shared plumbing for the admin-side hook that orchestrates admin-walked
|
|
3
|
-
# graph writes: `session-end-retrospective.sh` (Stop on operator end-intent).
|
|
4
|
-
# Originally introduced (Task 439) as shared plumbing across two hooks; the
|
|
5
|
-
# per-turn sibling was removed in Task 447 because session-end Pass 3 already
|
|
6
|
-
# performs the graph-completeness sweep. The library is retained — its
|
|
7
|
-
# stdin parse, role / specialist / transcript guards, log-ingest envelope,
|
|
8
|
-
# and post-user tool-name walker are still the single body for the
|
|
9
|
-
# remaining hook.
|
|
10
|
-
#
|
|
11
|
-
# Caller contract:
|
|
12
|
-
# 1. Set `AGP_LOG_TAG` to the hook's identifying tag before sourcing.
|
|
13
|
-
# `session-retrospective` is the current value; the helper itself
|
|
14
|
-
# does not log under its own tag.
|
|
15
|
-
# 2. Source this file.
|
|
16
|
-
# 3. Call `agp_setup_logging`, `agp_read_stdin`, `agp_parse_stdin`,
|
|
17
|
-
# `agp_guard_role_specialist`, `agp_guard_transcript` in that order.
|
|
18
|
-
# Each guard exits 0 from inside the helper when its condition fires
|
|
19
|
-
# (sourced `exit` exits the caller).
|
|
20
|
-
# 4. After the guards return, the hook proceeds with its event-specific
|
|
21
|
-
# body (sentinel-grep + intent token for session-end; post-turn-context
|
|
22
|
-
# fetch + directive envelope for pre-turn).
|
|
23
|
-
#
|
|
24
|
-
# Globals set by the helper (read by callers):
|
|
25
|
-
# INPUT — raw stdin (empty when none piped)
|
|
26
|
-
# SESSION_ID — parsed `session_id` field, empty on parse failure
|
|
27
|
-
# TRANSCRIPT_PATH — parsed `transcript_path` field
|
|
28
|
-
# HOOK_EVENT_NAME — parsed `hook_event_name` field
|
|
29
|
-
# SESSION_ID_REPORTED — `$SESSION_ID` or the literal "unknown"
|
|
30
|
-
# UI_BASE — `http://127.0.0.1:${MAXY_UI_INTERNAL_PORT}`
|
|
31
|
-
# LOG_INGEST_URL — `${UI_BASE}/api/admin/log-ingest`
|
|
32
|
-
#
|
|
33
|
-
# Functions:
|
|
34
|
-
# agp_setup_logging — resolves UI port + URLs, or exits 0 with
|
|
35
|
-
# a stderr loud-fail when the port is unset
|
|
36
|
-
# (fail-open so a missing port never bricks
|
|
37
|
-
# the operator).
|
|
38
|
-
# agp_emit_log <line> — POSTs `{tag, level:"info", line}` to
|
|
39
|
-
# log-ingest; silent on network failure.
|
|
40
|
-
# agp_read_stdin — slurps stdin into `INPUT`, leaves it empty
|
|
41
|
-
# when no input is piped.
|
|
42
|
-
# agp_parse_stdin — populates `SESSION_ID`, `TRANSCRIPT_PATH`,
|
|
43
|
-
# `HOOK_EVENT_NAME`, `SESSION_ID_REPORTED`.
|
|
44
|
-
# agp_guard_role_specialist — exits 0 with `trigger-skipped` log on
|
|
45
|
-
# `role-not-admin` or `is-specialist`.
|
|
46
|
-
# agp_guard_transcript — exits 0 with `trigger-skipped` log on
|
|
47
|
-
# `empty-stdin` (when $1="require-stdin") or
|
|
48
|
-
# `missing-transcript`.
|
|
49
|
-
|
|
50
|
-
if [ -z "${AGP_LOG_TAG:-}" ]; then
|
|
51
|
-
echo "[admin-graph-pass-common] AGP_LOG_TAG not set by caller" >&2
|
|
52
|
-
exit 1
|
|
53
|
-
fi
|
|
54
|
-
|
|
55
|
-
agp_setup_logging() {
|
|
56
|
-
local ui_port="${MAXY_UI_INTERNAL_PORT:-}"
|
|
57
|
-
if [ -z "$ui_port" ]; then
|
|
58
|
-
echo "[${AGP_LOG_TAG}] trigger-skipped sessionId=unknown reason=missing-env env=MAXY_UI_INTERNAL_PORT" >&2
|
|
59
|
-
exit 0
|
|
60
|
-
fi
|
|
61
|
-
UI_BASE="http://127.0.0.1:${ui_port}"
|
|
62
|
-
LOG_INGEST_URL="${UI_BASE}/api/admin/log-ingest"
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
agp_emit_log() {
|
|
66
|
-
local line="$1"
|
|
67
|
-
curl -sS -o /dev/null -X POST \
|
|
68
|
-
-H 'Content-Type: application/json' \
|
|
69
|
-
--max-time 2 \
|
|
70
|
-
--data "$(AGP_TAG="$AGP_LOG_TAG" AGP_LINE="$line" python3 -c '
|
|
71
|
-
import os, json
|
|
72
|
-
print(json.dumps({"tag": os.environ["AGP_TAG"], "level": "info", "line": os.environ["AGP_LINE"]}))
|
|
73
|
-
')" \
|
|
74
|
-
"$LOG_INGEST_URL" 2>/dev/null || true
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
agp_read_stdin() {
|
|
78
|
-
INPUT=""
|
|
79
|
-
if [ ! -t 0 ]; then
|
|
80
|
-
INPUT=$(cat)
|
|
81
|
-
fi
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
agp_parse_stdin() {
|
|
85
|
-
local parsed
|
|
86
|
-
parsed=$(printf '%s' "$INPUT" | python3 -c '
|
|
87
|
-
import sys, json
|
|
88
|
-
try:
|
|
89
|
-
d = json.load(sys.stdin)
|
|
90
|
-
sid = d.get("session_id", "") or ""
|
|
91
|
-
tpath = d.get("transcript_path", "") or ""
|
|
92
|
-
event = d.get("hook_event_name", "") or ""
|
|
93
|
-
print(f"{sid}\t{tpath}\t{event}")
|
|
94
|
-
except Exception:
|
|
95
|
-
print("\t\t")
|
|
96
|
-
' 2>/dev/null)
|
|
97
|
-
SESSION_ID="${parsed%% *}"
|
|
98
|
-
local rest="${parsed#* }"
|
|
99
|
-
TRANSCRIPT_PATH="${rest%% *}"
|
|
100
|
-
HOOK_EVENT_NAME="${rest#* }"
|
|
101
|
-
SESSION_ID_REPORTED="${SESSION_ID:-unknown}"
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
agp_guard_role_specialist() {
|
|
105
|
-
if [ "${MAXY_SESSION_ROLE:-}" != "admin" ]; then
|
|
106
|
-
agp_emit_log "trigger-skipped sessionId=${SESSION_ID_REPORTED} reason=role-not-admin"
|
|
107
|
-
exit 0
|
|
108
|
-
fi
|
|
109
|
-
# Recursion guard. A specialist subagent dispatched via the Task tool
|
|
110
|
-
# (e.g. database-operator running the operator's graph writes) carries
|
|
111
|
-
# `MAXY_SPECIALIST=<name>` on the PTY env. Skip — the hooks only fire
|
|
112
|
-
# for the operator's own admin session.
|
|
113
|
-
#
|
|
114
|
-
# Invariant the PTY spawner is expected to honour: `MAXY_SPECIALIST` is
|
|
115
|
-
# either set to a non-empty specialist name OR absent. An explicit empty
|
|
116
|
-
# string from a wrapper would slip the recursion guard here (`-n ""` is
|
|
117
|
-
# false) — that would be a defect at the spawn site, not here.
|
|
118
|
-
if [ -n "${MAXY_SPECIALIST:-}" ]; then
|
|
119
|
-
agp_emit_log "trigger-skipped sessionId=${SESSION_ID_REPORTED} reason=is-specialist specialist=${MAXY_SPECIALIST}"
|
|
120
|
-
exit 0
|
|
121
|
-
fi
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
# Usage: agp_guard_transcript [require-stdin]
|
|
125
|
-
# Pass `require-stdin` (session-end-retrospective) to emit `empty-stdin` on a
|
|
126
|
-
# blank `INPUT`. Without the flag, parse failure on a non-empty INPUT
|
|
127
|
-
# collapses to `missing-transcript` cleanly.
|
|
128
|
-
agp_guard_transcript() {
|
|
129
|
-
if [ "${1:-}" = "require-stdin" ] && [ -z "$INPUT" ]; then
|
|
130
|
-
agp_emit_log "trigger-skipped sessionId=${SESSION_ID_REPORTED} reason=empty-stdin"
|
|
131
|
-
exit 0
|
|
132
|
-
fi
|
|
133
|
-
if [ -z "$SESSION_ID" ] || [ -z "$TRANSCRIPT_PATH" ] || [ ! -f "$TRANSCRIPT_PATH" ]; then
|
|
134
|
-
agp_emit_log "trigger-skipped sessionId=${SESSION_ID_REPORTED} reason=missing-transcript"
|
|
135
|
-
exit 0
|
|
136
|
-
fi
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
# Walks $TRANSCRIPT_PATH and reports, in one line on stdout:
|
|
140
|
-
# <real-user-present:yes|no>\t<matched-tool-name-or-empty>
|
|
141
|
-
# A "real user" record matches `session-end-retrospective.sh`'s `is_real_user`
|
|
142
|
-
# — `type=user, message.role=user`, content is a string OR a list with at
|
|
143
|
-
# least one non-`tool_result` block. `tool_result`-only user records are tool
|
|
144
|
-
# replies, never turn starts.
|
|
145
|
-
#
|
|
146
|
-
# Every assistant `tool_use` block that appears AFTER the latest real-user
|
|
147
|
-
# record is matched against the alternation passed as `$1` (an extended-regex
|
|
148
|
-
# alternation of literal `tool_use.name` values). The synthetic name
|
|
149
|
-
# `Task:<subagent_type>` is also matched, so callers can release on a
|
|
150
|
-
# `Task` tool_use with `input.subagent_type=<name>` by including
|
|
151
|
-
# `Task:<name>` in the alternation. Returns the first match encountered after
|
|
152
|
-
# the latest real-user record (oldest-first) so the release log carries the
|
|
153
|
-
# matched tool's actual name. Empty match means none of the release patterns
|
|
154
|
-
# appeared since the operator's last turn.
|
|
155
|
-
agp_scan_post_user_toolnames() {
|
|
156
|
-
local pattern="$1"
|
|
157
|
-
TRANSCRIPT_PATH="$TRANSCRIPT_PATH" AGP_PATTERN="$pattern" python3 - <<'PY'
|
|
158
|
-
import os, json, re
|
|
159
|
-
|
|
160
|
-
pattern = re.compile(os.environ["AGP_PATTERN"])
|
|
161
|
-
path = os.environ["TRANSCRIPT_PATH"]
|
|
162
|
-
|
|
163
|
-
def is_real_user(rec):
|
|
164
|
-
if not isinstance(rec, dict) or rec.get("type") != "user":
|
|
165
|
-
return False
|
|
166
|
-
msg = rec.get("message")
|
|
167
|
-
if not isinstance(msg, dict) or msg.get("role") != "user":
|
|
168
|
-
return False
|
|
169
|
-
content = msg.get("content")
|
|
170
|
-
if isinstance(content, str):
|
|
171
|
-
return True
|
|
172
|
-
if isinstance(content, list):
|
|
173
|
-
for b in content:
|
|
174
|
-
if isinstance(b, dict) and b.get("type") != "tool_result":
|
|
175
|
-
return True
|
|
176
|
-
return False
|
|
177
|
-
return True
|
|
178
|
-
|
|
179
|
-
def assistant_tool_use_names(rec):
|
|
180
|
-
if not isinstance(rec, dict) or rec.get("type") != "assistant":
|
|
181
|
-
return []
|
|
182
|
-
msg = rec.get("message", {}) or {}
|
|
183
|
-
if msg.get("role") != "assistant":
|
|
184
|
-
return []
|
|
185
|
-
content = msg.get("content")
|
|
186
|
-
if not isinstance(content, list):
|
|
187
|
-
return []
|
|
188
|
-
names = []
|
|
189
|
-
for b in content:
|
|
190
|
-
if isinstance(b, dict) and b.get("type") == "tool_use":
|
|
191
|
-
n = b.get("name")
|
|
192
|
-
if not isinstance(n, str):
|
|
193
|
-
continue
|
|
194
|
-
names.append(n)
|
|
195
|
-
if n == "Task":
|
|
196
|
-
inp = b.get("input")
|
|
197
|
-
if isinstance(inp, dict):
|
|
198
|
-
st = inp.get("subagent_type")
|
|
199
|
-
if isinstance(st, str) and st:
|
|
200
|
-
names.append(f"Task:{st}")
|
|
201
|
-
return names
|
|
202
|
-
|
|
203
|
-
records = []
|
|
204
|
-
try:
|
|
205
|
-
with open(path, "r", encoding="utf-8") as f:
|
|
206
|
-
for raw in f:
|
|
207
|
-
raw = raw.strip()
|
|
208
|
-
if not raw:
|
|
209
|
-
continue
|
|
210
|
-
try:
|
|
211
|
-
records.append(json.loads(raw))
|
|
212
|
-
except Exception:
|
|
213
|
-
continue
|
|
214
|
-
except Exception:
|
|
215
|
-
print("no\t")
|
|
216
|
-
raise SystemExit(0)
|
|
217
|
-
|
|
218
|
-
latest_user_idx = -1
|
|
219
|
-
for i in range(len(records) - 1, -1, -1):
|
|
220
|
-
if is_real_user(records[i]):
|
|
221
|
-
latest_user_idx = i
|
|
222
|
-
break
|
|
223
|
-
|
|
224
|
-
if latest_user_idx == -1:
|
|
225
|
-
print("no\t")
|
|
226
|
-
raise SystemExit(0)
|
|
227
|
-
|
|
228
|
-
matched = ""
|
|
229
|
-
for r in records[latest_user_idx + 1:]:
|
|
230
|
-
for n in assistant_tool_use_names(r):
|
|
231
|
-
if pattern.fullmatch(n):
|
|
232
|
-
matched = n
|
|
233
|
-
break
|
|
234
|
-
if matched:
|
|
235
|
-
break
|
|
236
|
-
|
|
237
|
-
print(f"yes\t{matched}")
|
|
238
|
-
PY
|
|
239
|
-
}
|