titan-agent 5.4.2 → 5.5.6
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/README.md +1 -1
- package/dist/agent/agent.js +9 -5
- package/dist/agent/agent.js.map +1 -1
- package/dist/agent/agentLoop.js +7 -3
- package/dist/agent/agentLoop.js.map +1 -1
- package/dist/agent/checkpoint.js +2 -2
- package/dist/agent/checkpoint.js.map +1 -1
- package/dist/agent/commandPost.js +3 -3
- package/dist/agent/commandPost.js.map +1 -1
- package/dist/agent/goalProposer.js +2 -2
- package/dist/agent/goalProposer.js.map +1 -1
- package/dist/agent/goals.js +3 -3
- package/dist/agent/goals.js.map +1 -1
- package/dist/agent/peerAdvise.js +1 -1
- package/dist/agent/peerAdvise.js.map +1 -1
- package/dist/agent/planner.js +4 -4
- package/dist/agent/planner.js.map +1 -1
- package/dist/agent/userProfile.js +2 -2
- package/dist/agent/userProfile.js.map +1 -1
- package/dist/cli/doctor.js +33 -0
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/onboard.js +4 -4
- package/dist/cli/onboard.js.map +1 -1
- package/dist/config/config.js +3 -3
- package/dist/config/config.js.map +1 -1
- package/dist/config/schema.js +8 -1
- package/dist/config/schema.js.map +1 -1
- package/dist/gateway/routes/adminRouter.js +500 -0
- package/dist/gateway/routes/adminRouter.js.map +1 -0
- package/dist/gateway/routes/agents.js +231 -0
- package/dist/gateway/routes/agents.js.map +1 -0
- package/dist/gateway/routes/agentsRouter.js +32 -0
- package/dist/gateway/routes/agentsRouter.js.map +1 -0
- package/dist/gateway/routes/checkpoints.js +41 -0
- package/dist/gateway/routes/checkpoints.js.map +1 -0
- package/dist/gateway/routes/commandPost.js +755 -0
- package/dist/gateway/routes/commandPost.js.map +1 -0
- package/dist/gateway/routes/companies.js +166 -0
- package/dist/gateway/routes/companies.js.map +1 -0
- package/dist/gateway/routes/files.js +295 -0
- package/dist/gateway/routes/files.js.map +1 -0
- package/dist/gateway/routes/hardwareRouter.js +151 -0
- package/dist/gateway/routes/hardwareRouter.js.map +1 -0
- package/dist/gateway/routes/mcpRouter.js +88 -0
- package/dist/gateway/routes/mcpRouter.js.map +1 -0
- package/dist/gateway/routes/mesh.js +464 -0
- package/dist/gateway/routes/mesh.js.map +1 -0
- package/dist/gateway/routes/metricsRouter.js +131 -0
- package/dist/gateway/routes/metricsRouter.js.map +1 -0
- package/dist/gateway/routes/organism.js +82 -0
- package/dist/gateway/routes/organism.js.map +1 -0
- package/dist/gateway/routes/paperclip.js +101 -0
- package/dist/gateway/routes/paperclip.js.map +1 -0
- package/dist/gateway/routes/sessions.js +227 -0
- package/dist/gateway/routes/sessions.js.map +1 -0
- package/dist/gateway/routes/skills.js +295 -0
- package/dist/gateway/routes/skills.js.map +1 -0
- package/dist/gateway/routes/socialRouter.js +145 -0
- package/dist/gateway/routes/socialRouter.js.map +1 -0
- package/dist/gateway/routes/systemRouter.js +220 -0
- package/dist/gateway/routes/systemRouter.js.map +1 -0
- package/dist/gateway/routes/teamsRecipes.js +297 -0
- package/dist/gateway/routes/teamsRecipes.js.map +1 -0
- package/dist/gateway/routes/tests.js +401 -0
- package/dist/gateway/routes/tests.js.map +1 -0
- package/dist/gateway/routes/traces.js +33 -0
- package/dist/gateway/routes/traces.js.map +1 -0
- package/dist/gateway/routes/voiceRouter.js +770 -0
- package/dist/gateway/routes/voiceRouter.js.map +1 -0
- package/dist/gateway/routes/watchRouter.js +131 -0
- package/dist/gateway/routes/watchRouter.js.map +1 -0
- package/dist/gateway/server.js +1179 -7379
- package/dist/gateway/server.js.map +1 -1
- package/dist/mcp/registry.js +2 -2
- package/dist/mcp/registry.js.map +1 -1
- package/dist/memory/episodic.js +2 -2
- package/dist/memory/episodic.js.map +1 -1
- package/dist/memory/learning.js +3 -3
- package/dist/memory/learning.js.map +1 -1
- package/dist/memory/memory.js +3 -3
- package/dist/memory/memory.js.map +1 -1
- package/dist/organism/drives.js +2 -2
- package/dist/organism/drives.js.map +1 -1
- package/dist/providers/errorTaxonomy.js +13 -0
- package/dist/providers/errorTaxonomy.js.map +1 -1
- package/dist/providers/ollama.js +3 -1
- package/dist/providers/ollama.js.map +1 -1
- package/dist/providers/openai_compat.js +4 -3
- package/dist/providers/openai_compat.js.map +1 -1
- package/dist/providers/router.js +13 -0
- package/dist/providers/router.js.map +1 -1
- package/dist/safety/fixOscillation.js +15 -0
- package/dist/safety/fixOscillation.js.map +1 -1
- package/dist/safety/killSwitch.js +2 -2
- package/dist/safety/killSwitch.js.map +1 -1
- package/dist/safety/selfRepair.js +7 -3
- package/dist/safety/selfRepair.js.map +1 -1
- package/dist/skills/builtin/agent_debate.js +2 -2
- package/dist/skills/builtin/agent_debate.js.map +1 -1
- package/dist/skills/builtin/apply_patch.js +3 -3
- package/dist/skills/builtin/apply_patch.js.map +1 -1
- package/dist/skills/builtin/shell.js +2 -2
- package/dist/skills/builtin/shell.js.map +1 -1
- package/dist/skills/builtin/voice_control.js +49 -0
- package/dist/skills/builtin/voice_control.js.map +1 -0
- package/dist/skills/builtin/widget_gallery.js +6 -1
- package/dist/skills/builtin/widget_gallery.js.map +1 -1
- package/dist/skills/registry.js +15 -4
- package/dist/skills/registry.js.map +1 -1
- package/dist/storage/JsonStorage.js +4 -4
- package/dist/storage/JsonStorage.js.map +1 -1
- package/dist/utils/constants.js +1 -1
- package/dist/utils/constants.js.map +1 -1
- package/dist/utils/helpers.js +3 -1
- package/dist/utils/helpers.js.map +1 -1
- package/dist/utils/lifecycle.js +86 -0
- package/dist/utils/lifecycle.js.map +1 -0
- package/dist/voice/bridge.js +136 -0
- package/dist/voice/bridge.js.map +1 -0
- package/docs/COO-MASTER-PLAN-2026-05-02.md +474 -0
- package/docs/HANDOFF/2026-04-29.md +141 -0
- package/docs/HANDOFF-2026-04-30.md +144 -0
- package/docs/HANDOFF-2026-05-03.md +114 -0
- package/docs/adr/2026-04-29-widget-pipeline-traceability.md +49 -0
- package/docs/agent-memory/README.md +45 -0
- package/docs/agent-memory/commands.md +100 -0
- package/docs/agent-memory/context-tree.md +101 -0
- package/docs/agent-memory/current-state.md +54 -0
- package/docs/agent-memory/decisions.md +78 -0
- package/docs/agent-memory/known-issues.md +76 -0
- package/docs/agent-memory/reflections.md +52 -0
- package/docs/agent-memory/skills-candidates.md +80 -0
- package/docs/superpowers/plans/2026-04-29-comprehensive-audit.md +256 -0
- package/docs/superpowers/plans/2026-04-29-comprehensive-test-plan.md +396 -0
- package/docs/superpowers/plans/2026-04-29-fix-all-prs.md +251 -0
- package/docs/superpowers/plans/2026-04-29-gitnexus-gap-remediation.md +969 -0
- package/package.json +5 -2
- package/ui/dist/assets/{AuditPanel-CM6Wg9hO.js → AuditPanel-VzSndmDN.js} +2 -2
- package/ui/dist/assets/{AutonomyPanel-CESx3ANg.js → AutonomyPanel-BiFouzAV.js} +2 -2
- package/ui/dist/assets/AutopilotPanel-fjOfM668.js +1 -0
- package/ui/dist/assets/{AutoresearchPanel-DR47NqT5.js → AutoresearchPanel-CVCxzAH3.js} +2 -2
- package/ui/dist/assets/BackupPanel-CHVTG--q.js +1 -0
- package/ui/dist/assets/{BrowserPanel-C15x9bLn.js → BrowserPanel-D5mvMKFU.js} +2 -2
- package/ui/dist/assets/CPActivity-B12mt35m.js +1 -0
- package/ui/dist/assets/CPAgentDetail-DsdShc-1.js +1 -0
- package/ui/dist/assets/CPAgents-j_7C-oQV.js +1 -0
- package/ui/dist/assets/CPApprovals-BShKSX9X.js +1 -0
- package/ui/dist/assets/CPCosts-CKPlhBDs.js +1 -0
- package/ui/dist/assets/CPDashboard-11c0nkxK.js +1 -0
- package/ui/dist/assets/CPFiles-BhLEOnXy.js +1 -0
- package/ui/dist/assets/CPGoals-Bi3t1b2P.js +1 -0
- package/ui/dist/assets/CPInbox-Bbr7khp6.js +11 -0
- package/ui/dist/assets/CPIssueDetail-DSdgNK8r.js +1 -0
- package/ui/dist/assets/CPIssues-DDEVKhX6.js +1 -0
- package/ui/dist/assets/CPLayout-DgPOfyGv.js +17 -0
- package/ui/dist/assets/CPOrg-Df73RrRJ.js +8 -0
- package/ui/dist/assets/CPRuns-ByioAz8w.js +1 -0
- package/ui/dist/assets/{CPSocial-nb-j7sOE.js → CPSocial-Dlnr_w1X.js} +2 -2
- package/ui/dist/assets/ChannelsPanel-DQjQCTK5.js +1 -0
- package/ui/dist/assets/CheckpointsPanel-C4vKjlAJ.js +1 -0
- package/ui/dist/assets/CommandPostHub-C9pp5Giq.js +24 -0
- package/ui/dist/assets/CronPanel-C6bzUfrD.js +1 -0
- package/ui/dist/assets/DaemonPanel-BA5Tb_UO.js +1 -0
- package/ui/dist/assets/{DataTable-B2Ma8hfi.js → DataTable-CH7IYJJh.js} +1 -1
- package/ui/dist/assets/{EmptyState-CcKyk5Yn.js → EmptyState-jU6yNDnF.js} +1 -1
- package/ui/dist/assets/{EvalHarnessPanel-BqtMc1ZM.js → EvalHarnessPanel-DnYqredY.js} +2 -2
- package/ui/dist/assets/EvalPanel-ChO7CD1r.js +1 -0
- package/ui/dist/assets/{FilesPanel-3QKvrWPo.js → FilesPanel-CaUkv2is.js} +2 -2
- package/ui/dist/assets/FleetPanel-DC_5uj0N.js +1 -0
- package/ui/dist/assets/{HomelabPanel-DhrjTX9m.js → HomelabPanel-CE5PGRpL.js} +2 -2
- package/ui/dist/assets/InfraView-C-uSlvb9.js +2 -0
- package/ui/dist/assets/InlineEditableField-BMQjiE6-.js +1 -0
- package/ui/dist/assets/Input-Bu_b3qmY.js +1 -0
- package/ui/dist/assets/IntegrationsPanel-DsYpAq43.js +1 -0
- package/ui/dist/assets/IntelligenceView-DUdIO1K7.js +2 -0
- package/ui/dist/assets/LearningPanel-UpQZC-mA.js +1 -0
- package/ui/dist/assets/LogsPanel-ClXJ4fcr.js +1 -0
- package/ui/dist/assets/McpPanel-JKgtIERQ.js +1 -0
- package/ui/dist/assets/{MemoryGraphPanel-Bzvjmzvk.js → MemoryGraphPanel-Bo2OrvA6.js} +2 -2
- package/ui/dist/assets/MemoryWikiPanel-BqJ1AmYm.js +11 -0
- package/ui/dist/assets/{MeshPanel-C3LJSlht.js → MeshPanel-BJVGYvwk.js} +2 -2
- package/ui/dist/assets/Modal-CAAooiZU.js +1 -0
- package/ui/dist/assets/NvidiaPanel-BtCg3G4w.js +1 -0
- package/ui/dist/assets/OrganismPanel-DgrTTzcF.js +1 -0
- package/ui/dist/assets/OverviewPanel-rVav1Hox.js +1 -0
- package/ui/dist/assets/{PageHeader-BimceqQo.js → PageHeader-CnZtP8ek.js} +1 -1
- package/ui/dist/assets/PaperclipPanel-C-FKdhiF.js +1 -0
- package/ui/dist/assets/{PersonasPanel-L1j78p6H.js → PersonasPanel-BmlxokfB.js} +1 -1
- package/ui/dist/assets/RecipesPanel-BNKKChis.js +1 -0
- package/ui/dist/assets/SecurityPanel-I7JRHiNy.js +1 -0
- package/ui/dist/assets/SelfImprovePanel-u9h0Lt3p.js +1 -0
- package/ui/dist/assets/{SelfProposalsPanel-lNmiDThB.js → SelfProposalsPanel-DKl9iBjM.js} +2 -2
- package/ui/dist/assets/SessionsPanel-BhRiWI_g.js +1 -0
- package/ui/dist/assets/{SessionsTab-JQbltWww.js → SessionsTab-Bk08wyeY.js} +1 -1
- package/ui/dist/assets/SettingsPanel-haLfmG2k.js +1 -0
- package/ui/dist/assets/SettingsView--gi3fxI8.js +2 -0
- package/ui/dist/assets/{SkeletonLoader-atQtpcF5.js → SkeletonLoader-B5v09EF_.js} +1 -1
- package/ui/dist/assets/{SkillsPanel-DlFs2ih7.js → SkillsPanel-BlAHFLcQ.js} +1 -1
- package/ui/dist/assets/SomaView-CExtS3zw.js +5 -0
- package/ui/dist/assets/{StatCard-DciE_Iqc.js → StatCard-BIsyMybM.js} +1 -1
- package/ui/dist/assets/{StatusBadge-BtfSPoW2.js → StatusBadge-D5nU7El8.js} +1 -1
- package/ui/dist/assets/Tabs-BBYZrBI8.js +1 -0
- package/ui/dist/assets/TeamsPanel-LPXJg823.js +1 -0
- package/ui/dist/assets/TelemetryPanel-EqpRBmOI.js +1 -0
- package/ui/dist/assets/TitanCanvas-BCbWnLMd.js +985 -0
- package/ui/dist/assets/ToolsView-CeP0Zz-N.js +2 -0
- package/ui/dist/assets/{Tooltip-70UK0E2I.js → Tooltip-BSO2XVpF.js} +1 -1
- package/ui/dist/assets/TraceViewer-BKI7o5B0.js +1 -0
- package/ui/dist/assets/TrainingPanel-c-RhjdE1.js +1 -0
- package/ui/dist/assets/VoiceOverlay-D-gc58b0.js +27 -0
- package/ui/dist/assets/VramPanel-C6xc7zgd.js +1 -0
- package/ui/dist/assets/{WatchView-C-sGFpVy.js → WatchView-dqBVCVH0.js} +1 -1
- package/ui/dist/assets/WorkTab-CBoLNrTM.js +1 -0
- package/ui/dist/assets/{WorkflowsPanel-CvgQU1xI.js → WorkflowsPanel-BAnSTOYe.js} +2 -2
- package/ui/dist/assets/approvalHeadline-DB9SgR-9.js +1 -0
- package/ui/dist/assets/{arrow-left-DwqHtJiU.js → arrow-left-5chqas7J.js} +1 -1
- package/ui/dist/assets/briefcase-D4vLzudp.js +6 -0
- package/ui/dist/assets/{chart-column-BtNO6sRy.js → chart-column-CdFlBpoP.js} +1 -1
- package/ui/dist/assets/check-Bpm1IONe.js +6 -0
- package/ui/dist/assets/chevron-down-D7OLjvuD.js +6 -0
- package/ui/dist/assets/chevron-right-aQEw2mUW.js +6 -0
- package/ui/dist/assets/chevron-up-C5g6pEj8.js +6 -0
- package/ui/dist/assets/{circle-check-big-DZRE_MbN.js → circle-check-big-fPhEdP88.js} +1 -1
- package/ui/dist/assets/clock-CTsgP_Sn.js +6 -0
- package/ui/dist/assets/{dollar-sign-aVG3a5eL.js → dollar-sign-CudFVYFc.js} +1 -1
- package/ui/dist/assets/{download-BxiWJU4G.js → download-DZRxDn67.js} +1 -1
- package/ui/dist/assets/external-link-BZ0y_Ahx.js +6 -0
- package/ui/dist/assets/{eye-off-CkgfFYhm.js → eye-off-BmJF0YYx.js} +1 -1
- package/ui/dist/assets/folder-DA43TRCm.js +11 -0
- package/ui/dist/assets/{funnel-PkLdxKyC.js → funnel-J3mULzrz.js} +1 -1
- package/ui/dist/assets/{git-branch-BM-Gw95X.js → git-branch-oHibJqDq.js} +1 -1
- package/ui/dist/assets/{index-D0RJ8701.css → index-BR0vfkIi.css} +1 -1
- package/ui/dist/assets/{index-CahJbWSR.js → index-DzwowwSI.js} +20 -20
- package/ui/dist/assets/{layers-BuGf4FIJ.js → layers-DsyEyu7z.js} +1 -1
- package/ui/dist/assets/{legacy-CR6o4t-y.js → legacy-8ITl64sV.js} +1 -1
- package/ui/dist/assets/{lightbulb-n8gc_XAL.js → lightbulb-C54Ske-p.js} +1 -1
- package/ui/dist/assets/list-todo-Cnd4rdoK.js +6 -0
- package/ui/dist/assets/loader-circle-1YOBsoQp.js +6 -0
- package/ui/dist/assets/network-DbGDAdrn.js +6 -0
- package/ui/dist/assets/{pause-DCV52koX.js → pause-CYhO_uQo.js} +1 -1
- package/ui/dist/assets/{play-CcJ9BnCh.js → play-DVY9c5Ck.js} +1 -1
- package/ui/dist/assets/{plug-CfWBXfCl.js → plug-BcXjlPUL.js} +1 -1
- package/ui/dist/assets/plus-Csu2v9GN.js +6 -0
- package/ui/dist/assets/{proxy-CzZDfLmm.js → proxy-DxS2_9D7.js} +1 -1
- package/ui/dist/assets/rotate-ccw-Co-_W04j.js +6 -0
- package/ui/dist/assets/save-Btx-kpoW.js +6 -0
- package/ui/dist/assets/search-0hXTwEZR.js +6 -0
- package/ui/dist/assets/send-TEpapzQR.js +6 -0
- package/ui/dist/assets/shield-check-DjBJXZUr.js +6 -0
- package/ui/dist/assets/{square-DJpUhlxi.js → square-OweUvjP-.js} +1 -1
- package/ui/dist/assets/{target-DWcmM_9m.js → target-BRW80Xer.js} +1 -1
- package/ui/dist/assets/terminal-BtiqJ628.js +16 -0
- package/ui/dist/assets/{toggle-right-YusFQ69L.js → toggle-right-CKtSrl28.js} +1 -1
- package/ui/dist/assets/{trash-2-CK7yQ55V.js → trash-2-DgWrHVax.js} +1 -1
- package/ui/dist/assets/{trending-up-DGjFyubC.js → trending-up-MpIrE4j6.js} +1 -1
- package/ui/dist/assets/{trophy-uQv_NgDB.js → trophy-CECuZNhX.js} +1 -1
- package/ui/dist/assets/users-dZgv4ePG.js +16 -0
- package/ui/dist/assets/wrench-CDz3xYve.js +11 -0
- package/ui/dist/index.html +2 -2
- package/ui/dist/assets/AutopilotPanel-DtEet1hJ.js +0 -1
- package/ui/dist/assets/BackupPanel-BGP8p3l3.js +0 -1
- package/ui/dist/assets/CPAgents-DYUtPzSq.js +0 -1
- package/ui/dist/assets/CPDashboard-Bf0-SyCh.js +0 -6
- package/ui/dist/assets/CPFiles-CxgxjQcO.js +0 -1
- package/ui/dist/assets/CPGoals-BsmCMTvT.js +0 -1
- package/ui/dist/assets/CPInbox-tMSbmQ9H.js +0 -11
- package/ui/dist/assets/ChannelsPanel-DP5C2OKd.js +0 -1
- package/ui/dist/assets/CheckpointsPanel-DlranVLZ.js +0 -1
- package/ui/dist/assets/CommandPostHub-BgxIa4Ev.js +0 -29
- package/ui/dist/assets/CronPanel-LoT5yKwJ.js +0 -1
- package/ui/dist/assets/DaemonPanel-DBGMqaE_.js +0 -1
- package/ui/dist/assets/EvalPanel-Bc33j0pN.js +0 -1
- package/ui/dist/assets/FleetPanel-CSsXuQYj.js +0 -1
- package/ui/dist/assets/InfraView-CR6HyrL6.js +0 -2
- package/ui/dist/assets/InlineEditableField-CnvF-yFR.js +0 -1
- package/ui/dist/assets/Input-GTHp2Rkr.js +0 -1
- package/ui/dist/assets/IntegrationsPanel-CymCRE3T.js +0 -1
- package/ui/dist/assets/IntelligenceView-C1IHxJRC.js +0 -2
- package/ui/dist/assets/LearningPanel-DOCES3lH.js +0 -1
- package/ui/dist/assets/LogsPanel-BLnAqEaZ.js +0 -1
- package/ui/dist/assets/McpPanel-ChUzmr3z.js +0 -1
- package/ui/dist/assets/MemoryWikiPanel-Dwk3Aqwd.js +0 -11
- package/ui/dist/assets/NvidiaPanel-CeZK_-CV.js +0 -1
- package/ui/dist/assets/OrganismPanel-BB6YOiQV.js +0 -1
- package/ui/dist/assets/OverviewPanel-BmtBhQnv.js +0 -1
- package/ui/dist/assets/PaperclipPanel-C-brgwA3.js +0 -1
- package/ui/dist/assets/RecipesPanel-34lCfynJ.js +0 -1
- package/ui/dist/assets/SecurityPanel-CBTPWLj6.js +0 -1
- package/ui/dist/assets/SelfImprovePanel-BrPbFHhG.js +0 -1
- package/ui/dist/assets/SessionsPanel-DAEYIn83.js +0 -1
- package/ui/dist/assets/SettingsPanel-CzRROAYQ.js +0 -1
- package/ui/dist/assets/SettingsView-CN7ii2uw.js +0 -2
- package/ui/dist/assets/SomaView-Ba642Oqb.js +0 -5
- package/ui/dist/assets/TeamsPanel-DKQ5z2Qe.js +0 -1
- package/ui/dist/assets/TelemetryPanel-B6KAc55Q.js +0 -1
- package/ui/dist/assets/TitanCanvas-C-s0A-lv.js +0 -1092
- package/ui/dist/assets/ToolsView-Dei0KMP0.js +0 -2
- package/ui/dist/assets/TraceViewer-BniolyBx.js +0 -1
- package/ui/dist/assets/TrainingPanel-Bz4CTPGW.js +0 -1
- package/ui/dist/assets/VoiceOverlay-CmNCrLcd.js +0 -37
- package/ui/dist/assets/VramPanel-Xh_OtRDR.js +0 -1
- package/ui/dist/assets/WorkTab-BjLNmgIK.js +0 -1
|
@@ -0,0 +1,969 @@
|
|
|
1
|
+
# TITAN v5.4.3 — GitNexus Gap Remediation Plan
|
|
2
|
+
|
|
3
|
+
> **For agentic workers:** Use `superpowers:executing-plans` or `superpowers:subagent-driven-development` task-by-task. Each task is independent enough to ship on its own. Run tasks in order within a stream, but streams can interleave.
|
|
4
|
+
|
|
5
|
+
**Goal:** Fix all four structural gaps GitNexus identified in the TITAN knowledge graph, making the widget pipeline traceable, search functional, gateway modular, and voice connected.
|
|
6
|
+
|
|
7
|
+
**Architecture:**
|
|
8
|
+
- **Widget stream:** Trace `gallery_get` → `SandboxRuntime.post` → `handleIframeRequest` → `handleRender` as a single Process in the graph (fixes proxy bug visibility)
|
|
9
|
+
- **Search stream:** Rebuild LadybugDB FTS tables with write access, enable `--embeddings` for semantic search
|
|
10
|
+
- **Gateway stream:** Extract `/api/paperclip`, `/api/checkpoints`, `/api/companies`, `/api/traces` into sub-routers mounted by `src/gateway/server.ts`
|
|
11
|
+
- **Voice stream:** Add a lightweight TS bridge (`src/voice/bridge.ts`) that the agent can call into, making `TitanAgent` visible to the core graph
|
|
12
|
+
|
|
13
|
+
**Tech Stack:** TypeScript, Express 4, LadybugDB (Kuzu), GitNexus CLI, Vitest
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Stream A: Fix Widget Template → Sandbox Bridge
|
|
18
|
+
|
|
19
|
+
### A1: Reproduce the `titan.api.call` proxy bug
|
|
20
|
+
|
|
21
|
+
**Files:**
|
|
22
|
+
- Read: `src/skills/builtin/widget_gallery.ts`, `ui/src/titan2/sandbox/SandboxRuntime.ts`
|
|
23
|
+
- Create: `tests/sandbox/widget-proxy-repro.test.ts`
|
|
24
|
+
|
|
25
|
+
- [ ] **Step 1: Write failing test for proxy bug**
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
29
|
+
import SandboxRuntime from '../../ui/src/titan2/sandbox/SandboxRuntime';
|
|
30
|
+
|
|
31
|
+
describe('widget proxy bug', () => {
|
|
32
|
+
it('should route titan.api.call through postMessage correctly', async () => {
|
|
33
|
+
const runtime = new SandboxRuntime('test-container', { debug: false });
|
|
34
|
+
runtime.init();
|
|
35
|
+
|
|
36
|
+
// Simulate what a widget does when it calls titan.api.call
|
|
37
|
+
const widgetCode = `
|
|
38
|
+
async function onAnalyzeClick() {
|
|
39
|
+
const result = await titan.api.call('/api/stock/analyze', { ticker: 'AAPL' });
|
|
40
|
+
return result;
|
|
41
|
+
}
|
|
42
|
+
`;
|
|
43
|
+
|
|
44
|
+
await runtime.whenReady();
|
|
45
|
+
const response = await runtime.post('execute', { code: widgetCode });
|
|
46
|
+
|
|
47
|
+
// Currently returns "No response." because the proxy handler is missing in handleIframeRequest
|
|
48
|
+
expect(response).not.toBe('No response.');
|
|
49
|
+
expect(response).toBeDefined();
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
- [ ] **Step 2: Run test to confirm failure**
|
|
55
|
+
|
|
56
|
+
Run: `npx vitest run tests/sandbox/widget-proxy-repro.test.ts`
|
|
57
|
+
Expected: FAIL — `AssertionError: expected 'No response.' not to be 'No response.'` (or timeout/crash)
|
|
58
|
+
|
|
59
|
+
- [ ] **Step 3: Commit repro test**
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
git add tests/sandbox/widget-proxy-repro.test.ts
|
|
63
|
+
git commit -m "test(sandbox): repro for titan.api.call proxy bug"
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### A2: Fix `handleIframeRequest` to proxy `titan.api.call`
|
|
67
|
+
|
|
68
|
+
**Files:**
|
|
69
|
+
- Modify: `ui/src/titan2/sandbox/SandboxRuntime.ts:handleIframeRequest#3` (line ~382)
|
|
70
|
+
- Test: `tests/sandbox/widget-proxy-repro.test.ts` (update existing)
|
|
71
|
+
|
|
72
|
+
- [ ] **Step 4: Add proxy handler in `handleIframeRequest`**
|
|
73
|
+
|
|
74
|
+
In `ui/src/titan2/sandbox/SandboxRuntime.ts`, inside `handleIframeRequest(event)`, find the `switch(data.type)` block and add:
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
case 'titan.api.call': {
|
|
78
|
+
const { endpoint, body } = data.payload;
|
|
79
|
+
// Strip any accidental double /api/ prefix
|
|
80
|
+
const cleanPath = endpoint.replace(/^\/api\//, '/');
|
|
81
|
+
const url = cleanPath.startsWith('/')
|
|
82
|
+
? `${window.location.origin}/api${cleanPath}`
|
|
83
|
+
: endpoint;
|
|
84
|
+
|
|
85
|
+
const token = localStorage.getItem('titan-token');
|
|
86
|
+
const res = await fetch(url, {
|
|
87
|
+
method: 'POST',
|
|
88
|
+
headers: {
|
|
89
|
+
'Content-Type': 'application/json',
|
|
90
|
+
...(token ? { 'Authorization': `Bearer ${token}` } : {})
|
|
91
|
+
},
|
|
92
|
+
body: JSON.stringify(body)
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
const json = await res.json().catch(() => ({}));
|
|
96
|
+
this.post('titan.api.call.response', {
|
|
97
|
+
id: data.id,
|
|
98
|
+
status: res.status,
|
|
99
|
+
body: json
|
|
100
|
+
});
|
|
101
|
+
break;
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
- [ ] **Step 5: Handle proxy response on widget side**
|
|
106
|
+
|
|
107
|
+
In the same file, add to `render()` or `execute()` the promise-tracking for widget callbacks:
|
|
108
|
+
|
|
109
|
+
```typescript
|
|
110
|
+
private pendingApiCalls: Map<string, { resolve: Function; reject: Function }> = new Map();
|
|
111
|
+
|
|
112
|
+
// In the constructor or init:
|
|
113
|
+
this.messageHandler = (data: any) => {
|
|
114
|
+
if (data.type === 'titan.api.call.response') {
|
|
115
|
+
const pending = this.pendingApiCalls.get(data.payload.id);
|
|
116
|
+
if (pending) {
|
|
117
|
+
if (data.payload.status >= 200 && data.payload.status < 300) {
|
|
118
|
+
pending.resolve(data.payload.body);
|
|
119
|
+
} else {
|
|
120
|
+
pending.reject(new Error(`API call failed: ${data.payload.status}`));
|
|
121
|
+
}
|
|
122
|
+
this.pendingApiCalls.delete(data.payload.id);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
// ... existing handler
|
|
126
|
+
};
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
- [ ] **Step 6: Update test to verify fix**
|
|
130
|
+
|
|
131
|
+
Replace the assertion in `widget-proxy-repro.test.ts`:
|
|
132
|
+
|
|
133
|
+
```typescript
|
|
134
|
+
expect(response.body).toBeTruthy();
|
|
135
|
+
expect(response.status).toBe(200);
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
- [ ] **Step 7: Run test to confirm pass**
|
|
139
|
+
|
|
140
|
+
Run: `npx vitest run tests/sandbox/widget-proxy-repro.test.ts`
|
|
141
|
+
Expected: PASS
|
|
142
|
+
|
|
143
|
+
- [ ] **Step 8: Commit fix**
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
git add ui/src/titan2/sandbox/SandboxRuntime.ts tests/sandbox/widget-proxy-repro.test.ts
|
|
147
|
+
git commit -m "fix(sandbox): proxy titan.api.call through postMessage in widget iframe"
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### A3: Fix Pomodoro UI generation hang (SSE timeout)
|
|
151
|
+
|
|
152
|
+
**Files:**
|
|
153
|
+
- Modify: `ui/src/titan2/canvas/TitanCanvas.tsx`
|
|
154
|
+
- Read: `assets/widget-templates/productivity/pomodoro-25-5.json`
|
|
155
|
+
- Create: `tests/sandbox/pomodoro-sse-timeout.test.ts`
|
|
156
|
+
|
|
157
|
+
- [ ] **Step 9: Write failing test for Pomodoro timeout**
|
|
158
|
+
|
|
159
|
+
```typescript
|
|
160
|
+
import { describe, it, expect } from 'vitest';
|
|
161
|
+
|
|
162
|
+
describe('pomodoro sse hang', () => {
|
|
163
|
+
it('should not hang for >30s when generating Pomodoro widget', async () => {
|
|
164
|
+
const start = Date.now();
|
|
165
|
+
|
|
166
|
+
// Simulate the canvas requesting a pomodoro widget render
|
|
167
|
+
const widgetPrompt = 'Create a pomodoro timer with 25-minute work sessions';
|
|
168
|
+
|
|
169
|
+
// This currently uses SSE streaming which hangs
|
|
170
|
+
const result = await simulateWidgetGeneration(widgetPrompt);
|
|
171
|
+
|
|
172
|
+
const elapsed = Date.now() - start;
|
|
173
|
+
expect(elapsed).toBeLessThan(35000); // Direct curl is 33s; target <35s as baseline
|
|
174
|
+
expect(result).toBeDefined();
|
|
175
|
+
}, 40000);
|
|
176
|
+
});
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
- [ ] **Step 10: Run test to confirm hang/failure**
|
|
180
|
+
|
|
181
|
+
Run: `npx vitest run tests/sandbox/pomodoro-sse-timeout.test.ts`
|
|
182
|
+
Expected: FAIL (timeout or >35s)
|
|
183
|
+
|
|
184
|
+
- [ ] **Step 11: Commit repro test**
|
|
185
|
+
|
|
186
|
+
```bash
|
|
187
|
+
git add tests/sandbox/pomodoro-sse-timeout.test.ts
|
|
188
|
+
git commit -m "test(sandbox): repro for Pomodoro SSE generation hang"
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
- [ ] **Step 12: Add SSE timeout guard in `TitanCanvas.spawnWidget()`**
|
|
192
|
+
|
|
193
|
+
In `ui/src/titan2/canvas/TitanCanvas.tsx`, find `spawnWidget()` (~line 420) and add:
|
|
194
|
+
|
|
195
|
+
```typescript
|
|
196
|
+
private async spawnWidget(prompt: string) {
|
|
197
|
+
const startTime = Date.now();
|
|
198
|
+
const MAX_GENERATION_MS = 30000; // 30s hard cap
|
|
199
|
+
|
|
200
|
+
try {
|
|
201
|
+
const controller = new AbortController();
|
|
202
|
+
const timeoutId = setTimeout(() => {
|
|
203
|
+
controller.abort();
|
|
204
|
+
console.warn('[TitanCanvas] Widget generation timed out after 30s');
|
|
205
|
+
}, MAX_GENERATION_MS);
|
|
206
|
+
|
|
207
|
+
const response = await fetch('/api/message', {
|
|
208
|
+
method: 'POST',
|
|
209
|
+
headers: { 'Accept': 'text/event-stream' },
|
|
210
|
+
body: JSON.stringify({ content: prompt, stream: true }),
|
|
211
|
+
signal: controller.signal
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
clearTimeout(timeoutId);
|
|
215
|
+
|
|
216
|
+
if (!response.ok || Date.now() - startTime > MAX_GENERATION_MS) {
|
|
217
|
+
// Fall back to direct non-SSE request
|
|
218
|
+
console.log('[TitanCanvas] Falling back to direct widget generation');
|
|
219
|
+
return this.fallbackWidgetGeneration(prompt);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// ... existing SSE parsing logic
|
|
223
|
+
} catch (err) {
|
|
224
|
+
if (err instanceof DOMException && err.name === 'AbortError') {
|
|
225
|
+
return this.fallbackWidgetGeneration(prompt);
|
|
226
|
+
}
|
|
227
|
+
throw err;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
private fallbackWidgetGeneration(prompt: string) {
|
|
232
|
+
// Use a simple synchronous fetch instead of SSE
|
|
233
|
+
return fetch('/api/message', {
|
|
234
|
+
method: 'POST',
|
|
235
|
+
headers: { 'Content-Type': 'application/json' },
|
|
236
|
+
body: JSON.stringify({ content: prompt, stream: false })
|
|
237
|
+
}).then(r => r.json());
|
|
238
|
+
}
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
- [ ] **Step 13: Run test to confirm <35s**
|
|
242
|
+
|
|
243
|
+
Run: `npx vitest run tests/sandbox/pomodoro-sse-timeout.test.ts`
|
|
244
|
+
Expected: PASS (<35s)
|
|
245
|
+
|
|
246
|
+
- [ ] **Step 14: Commit fix**
|
|
247
|
+
|
|
248
|
+
```bash
|
|
249
|
+
git add ui/src/titan2/canvas/TitanCanvas.tsx
|
|
250
|
+
git commit -m "fix(canvas): add 30s SSE timeout + direct fallback for widget generation"
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
### A4: Trace widget pipeline as GitNexus Process
|
|
254
|
+
|
|
255
|
+
**Files:**
|
|
256
|
+
- Read: `ui/src/titan2/canvas/TitanCanvas.tsx`, `ui/src/titan2/sandbox/SandboxRuntime.ts`, `src/skills/builtin/widget_gallery.ts`
|
|
257
|
+
- Create: `docs/adr/2026-04-29-widget-pipeline-traceability.md`
|
|
258
|
+
|
|
259
|
+
- [ ] **Step 15: Document the widget pipeline as a Process**
|
|
260
|
+
|
|
261
|
+
```markdown
|
|
262
|
+
# ADR-2026-04-29: Widget Pipeline Traceability
|
|
263
|
+
|
|
264
|
+
## Context
|
|
265
|
+
GitNexus Process traces do not capture `gallery_get` → `SandboxRuntime.render` → `handleRender` as a single execution flow.
|
|
266
|
+
|
|
267
|
+
## Decision
|
|
268
|
+
Refactor widget spawning into a named async function `runWidgetPipeline()` so GitNexus traces the full flow.
|
|
269
|
+
|
|
270
|
+
## Steps in Pipeline
|
|
271
|
+
1. User prompt → `TitanCanvas.spawnWidget()`
|
|
272
|
+
2. Gallery search: `widget_gallery.gallery_search(prompt)`
|
|
273
|
+
3. Gallery get: `widget_gallery.gallery_get(name)`
|
|
274
|
+
4. Render: `SandboxRuntime.render(componentSource)`
|
|
275
|
+
5. Mount: `ReactDOM.createRoot(iframeDoc).render(<Widget />)`
|
|
276
|
+
6. Intercept: `handleIframeRequest` proxies `titan.api.call`
|
|
277
|
+
7. Response: `postMessage('titan.api.call.response')` resolves widget promise
|
|
278
|
+
8. Destroy: `SandboxRuntime.destroy()` on widget removal
|
|
279
|
+
|
|
280
|
+
## Code Change
|
|
281
|
+
Extract steps 1-5 into `async function runWidgetPipeline(prompt, container)` in `TitanCanvas.tsx`.
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
- [ ] **Step 16: Extract `runWidgetPipeline()` in `TitanCanvas.tsx`**
|
|
285
|
+
|
|
286
|
+
Find `spawnWidget()` and extract:
|
|
287
|
+
|
|
288
|
+
```typescript
|
|
289
|
+
async function runWidgetPipeline(
|
|
290
|
+
prompt: string,
|
|
291
|
+
container: HTMLElement,
|
|
292
|
+
gallery: WidgetGallery,
|
|
293
|
+
runtimeFactory: (el: HTMLElement) => SandboxRuntime
|
|
294
|
+
): Promise<SandboxRuntime> {
|
|
295
|
+
// Step 2: Gallery search
|
|
296
|
+
const searchResult = await gallery.search(prompt);
|
|
297
|
+
if (!searchResult.length) {
|
|
298
|
+
throw new Error(`No widget template found for: ${prompt}`);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Step 3: Gallery get
|
|
302
|
+
const template = await gallery.get(searchResult[0].name, {});
|
|
303
|
+
|
|
304
|
+
// Step 4: Render
|
|
305
|
+
const runtime = runtimeFactory(container);
|
|
306
|
+
runtime.init();
|
|
307
|
+
await runtime.whenReady();
|
|
308
|
+
|
|
309
|
+
// Step 5: Mount
|
|
310
|
+
await runtime.render('jsx', template.source);
|
|
311
|
+
|
|
312
|
+
return runtime;
|
|
313
|
+
}
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
Then call `runWidgetPipeline()` from `spawnWidget()`.
|
|
317
|
+
|
|
318
|
+
- [ ] **Step 17: Commit pipeline extraction**
|
|
319
|
+
|
|
320
|
+
```bash
|
|
321
|
+
git add ui/src/titan2/canvas/TitanCanvas.tsx
|
|
322
|
+
git commit -m "refactor(canvas): extract runWidgetPipeline for GitNexus traceability"
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
- [ ] **Step 18: Re-index after A-stream changes**
|
|
326
|
+
|
|
327
|
+
Run: `node /opt/homebrew/lib/node_modules/gitnexus/dist/cli/index.js analyze --force --name TITAN`
|
|
328
|
+
Expected: New Process traces `runWidgetPipeline` → `gallery_search` → `gallery_get` → `SandboxRuntime.render`
|
|
329
|
+
|
|
330
|
+
- [ ] **Step 19: Commit ADR**
|
|
331
|
+
|
|
332
|
+
```bash
|
|
333
|
+
git add docs/adr/2026-04-29-widget-pipeline-traceability.md
|
|
334
|
+
git commit -m "docs(adr): widget pipeline traceability and GitNexus Process mapping"
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
---
|
|
338
|
+
|
|
339
|
+
## Stream B: Rebuild FTS Search & Enable Embeddings
|
|
340
|
+
|
|
341
|
+
### B1: Rebuild FTS index with write access
|
|
342
|
+
|
|
343
|
+
**Files:**
|
|
344
|
+
- Read: `.gitnexus/meta.json`
|
|
345
|
+
- Modify: `.gitnexus/meta.json` (add fts_enabled flag)
|
|
346
|
+
- Create: `scripts/rebuild-gitnexus-fts.ts`
|
|
347
|
+
|
|
348
|
+
- [ ] **Step 1: Identify why FTS is read-only**
|
|
349
|
+
|
|
350
|
+
Run: `node /opt/homebrew/lib/node_modules/gitnexus/dist/cli/index.js cypher "CALL show_tables()" 2>&1 | grep -i fts`
|
|
351
|
+
Expected: Shows `file_fts`, `function_fts`, `class_fts`, `method_fts`, `interface_fts` exist but empty.
|
|
352
|
+
|
|
353
|
+
- [ ] **Step 2: Write FTS rebuild script**
|
|
354
|
+
|
|
355
|
+
```typescript
|
|
356
|
+
// scripts/rebuild-gitnexus-fts.ts
|
|
357
|
+
import { execSync } from 'child_process';
|
|
358
|
+
import fs from 'fs';
|
|
359
|
+
import path from 'path';
|
|
360
|
+
|
|
361
|
+
const REPO = '/Users/michaelelliott/Desktop/TitanBot/TITAN-main';
|
|
362
|
+
const GITNEXUS = '/opt/homebrew/lib/node_modules/gitnexus/dist/cli/index.js';
|
|
363
|
+
|
|
364
|
+
// Force full re-index with embeddings + FTS
|
|
365
|
+
console.log('Rebuilding GitNexus index with FTS + embeddings...');
|
|
366
|
+
|
|
367
|
+
try {
|
|
368
|
+
// 1. Clean existing index
|
|
369
|
+
console.log('Step 1: Cleaning old index...');
|
|
370
|
+
execSync(`node ${GITNEXUS} clean --force`, { cwd: REPO, stdio: 'inherit' });
|
|
371
|
+
} catch (e) {
|
|
372
|
+
console.log('Clean may have failed for new repo, continuing...');
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// 2. Re-analyze with embeddings
|
|
376
|
+
console.log('Step 2: Re-analyzing with embeddings...');
|
|
377
|
+
execSync(`node ${GITNEXUS} analyze --embeddings --name TITAN`, { cwd: REPO, stdio: 'inherit', timeout: 600000 });
|
|
378
|
+
|
|
379
|
+
// 3. Mark FTS as enabled
|
|
380
|
+
const metaPath = path.join(REPO, '.gitnexus', 'meta.json');
|
|
381
|
+
const meta = JSON.parse(fs.readFileSync(metaPath, 'utf-8'));
|
|
382
|
+
meta.fts_enabled = true;
|
|
383
|
+
meta.embeddings_enabled = true;
|
|
384
|
+
fs.writeFileSync(metaPath, JSON.stringify(meta, null, 2));
|
|
385
|
+
|
|
386
|
+
console.log('Done! FTS + embeddings enabled.');
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
- [ ] **Step 3: Run rebuild script**
|
|
390
|
+
|
|
391
|
+
```bash
|
|
392
|
+
npx tsx scripts/rebuild-gitnexus-fts.ts
|
|
393
|
+
```
|
|
394
|
+
Expected: Clean → analyze → FTS tables built → meta.json updated.
|
|
395
|
+
Time: ~5-10 minutes with embeddings.
|
|
396
|
+
|
|
397
|
+
- [ ] **Step 4: Verify search works**
|
|
398
|
+
|
|
399
|
+
```bash
|
|
400
|
+
node /opt/homebrew/lib/node_modules/gitnexus/dist/cli/index.js cypher "CALL fts_queries.show_fts_index_info()" 2>&1 | head -20
|
|
401
|
+
```
|
|
402
|
+
Expected: No read-only error; shows index stats.
|
|
403
|
+
|
|
404
|
+
- [ ] **Step 5: Run a semantic query**
|
|
405
|
+
|
|
406
|
+
```bash
|
|
407
|
+
node /opt/homebrew/lib/node_modules/gitnexus/dist/cli/index.js query "authentication middleware"
|
|
408
|
+
```
|
|
409
|
+
Expected: Returns processes, symbols, definitions (non-empty).
|
|
410
|
+
|
|
411
|
+
- [ ] **Step 6: Commit script and meta update**
|
|
412
|
+
|
|
413
|
+
```bash
|
|
414
|
+
git add scripts/rebuild-gitnexus-fts.ts .gitnexus/meta.json
|
|
415
|
+
git commit -m "feat(gitnexus): rebuild FTS + enable embeddings for semantic search"
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
### B2: Add gitnexus sync to build pipeline
|
|
419
|
+
|
|
420
|
+
**Files:**
|
|
421
|
+
- Modify: `package.json` scripts section
|
|
422
|
+
- Modify: `.github/workflows/eval-gate.yml` (if it exists and builds)
|
|
423
|
+
|
|
424
|
+
- [ ] **Step 7: Add `gitnexus:rebuild` script**
|
|
425
|
+
|
|
426
|
+
In `package.json`, add to `scripts`:
|
|
427
|
+
|
|
428
|
+
```json
|
|
429
|
+
"gitnexus:rebuild": "node /opt/homebrew/lib/node_modules/gitnexus/dist/cli/index.js analyze --embeddings --name TITAN",
|
|
430
|
+
"gitnexus:query": "node /opt/homebrew/lib/node_modules/gitnexus/dist/cli/index.js query",
|
|
431
|
+
"gitnexus:status": "node /opt/homebrew/lib/node_modules/gitnexus/dist/cli/index.js status"
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
- [ ] **Step 8: Add pre-build sync check**
|
|
435
|
+
|
|
436
|
+
In `package.json` scripts, add a `prebuild` check:
|
|
437
|
+
|
|
438
|
+
```json
|
|
439
|
+
"prebuild": "node -e \"const fs=require('fs'); const m=JSON.parse(fs.readFileSync('.gitnexus/meta.json')); if (!m.fts_enabled || Date.now()-new Date(m.indexedAt)>86400000) { console.warn('GitNexus index stale. Run: npm run gitnexus:rebuild'); process.exit(1); }\""
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
- [ ] **Step 9: Commit pipeline integration**
|
|
443
|
+
|
|
444
|
+
```bash
|
|
445
|
+
git add package.json
|
|
446
|
+
git commit -m "build: add gitnexus rebuild + stale-index guard to pipeline"
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
---
|
|
450
|
+
|
|
451
|
+
## Stream C: Extract Gateway Sub-Routers
|
|
452
|
+
|
|
453
|
+
### C1: Extract `/api/paperclip/*` routes
|
|
454
|
+
|
|
455
|
+
**Files:**
|
|
456
|
+
- Create: `src/gateway/routes/paperclip.ts`
|
|
457
|
+
- Modify: `src/gateway/server.ts` (remove paperclip routes)
|
|
458
|
+
- Test: `tests/gateway/paperclip-routes.test.ts`
|
|
459
|
+
|
|
460
|
+
- [ ] **Step 1: Read current paperclip routes from `server.ts`**
|
|
461
|
+
|
|
462
|
+
Search in `src/gateway/server.ts` for all `/api/paperclip` routes. Typical pattern:
|
|
463
|
+
|
|
464
|
+
```typescript
|
|
465
|
+
app.get('/api/paperclip/status', ...);
|
|
466
|
+
app.post('/api/paperclip/start', ...);
|
|
467
|
+
app.post('/api/paperclip/stop', ...);
|
|
468
|
+
app.post('/api/paperclip/reset', ...);
|
|
469
|
+
app.get('/api/paperclip/*', ...);
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
- [ ] **Step 2: Create `paperclip.ts` sub-router**
|
|
473
|
+
|
|
474
|
+
```typescript
|
|
475
|
+
// src/gateway/routes/paperclip.ts
|
|
476
|
+
import { Router, Request, Response } from 'express';
|
|
477
|
+
import { rateLimit } from '../middleware/rateLimit';
|
|
478
|
+
import { log } from '../../utils/logger';
|
|
479
|
+
|
|
480
|
+
const router = Router();
|
|
481
|
+
|
|
482
|
+
router.get('/status', rateLimit(60000, 30), async (req: Request, res: Response) => {
|
|
483
|
+
try {
|
|
484
|
+
const status = await getPaperclipStatus();
|
|
485
|
+
res.json(status);
|
|
486
|
+
} catch (err) {
|
|
487
|
+
log.error('Paperclip status failed:', err);
|
|
488
|
+
res.status(500).json({ error: 'Failed to get paperclip status' });
|
|
489
|
+
}
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
router.post('/start', rateLimit(60000, 10), async (req: Request, res: Response) => {
|
|
493
|
+
// ... existing start logic
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
router.post('/stop', rateLimit(60000, 10), async (req: Request, res: Response) => {
|
|
497
|
+
// ... existing stop logic
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
router.post('/reset', rateLimit(60000, 10), async (req: Request, res: Response) => {
|
|
501
|
+
// ... existing reset logic
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
router.get('/*', rateLimit(60000, 30), async (req: Request, res: Response) => {
|
|
505
|
+
// ... catchall
|
|
506
|
+
});
|
|
507
|
+
|
|
508
|
+
export default router;
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
- [ ] **Step 3: Mount router in `server.ts`**
|
|
512
|
+
|
|
513
|
+
In `src/gateway/server.ts`, replace inline routes with:
|
|
514
|
+
|
|
515
|
+
```typescript
|
|
516
|
+
import paperclipRouter from './routes/paperclip';
|
|
517
|
+
// ... after app init
|
|
518
|
+
app.use('/api/paperclip', paperclipRouter);
|
|
519
|
+
```
|
|
520
|
+
|
|
521
|
+
Remove the original `app.get('/api/paperclip/...')` and `app.post('/api/paperclip/...')` blocks.
|
|
522
|
+
|
|
523
|
+
- [ ] **Step 4: Write test for paperclip router isolation**
|
|
524
|
+
|
|
525
|
+
```typescript
|
|
526
|
+
import { describe, it, expect } from 'vitest';
|
|
527
|
+
import request from 'supertest';
|
|
528
|
+
import express from 'express';
|
|
529
|
+
import paperclipRouter from '../../../src/gateway/routes/paperclip';
|
|
530
|
+
|
|
531
|
+
const app = express();
|
|
532
|
+
app.use('/api/paperclip', paperclipRouter);
|
|
533
|
+
|
|
534
|
+
describe('paperclip router', () => {
|
|
535
|
+
it('should return 200 for /api/paperclip/status', async () => {
|
|
536
|
+
const res = await request(app).get('/api/paperclip/status');
|
|
537
|
+
expect(res.status).toBe(200);
|
|
538
|
+
expect(res.body).toBeDefined();
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
it('should rate-limit start/stop/reset', async () => {
|
|
542
|
+
// Make 11 requests to /start within 60s
|
|
543
|
+
const promises = Array(11).fill(null).map(() =>
|
|
544
|
+
request(app).post('/api/paperclip/start')
|
|
545
|
+
);
|
|
546
|
+
const responses = await Promise.all(promises);
|
|
547
|
+
const rateLimited = responses.filter(r => r.status === 429);
|
|
548
|
+
expect(rateLimited.length).toBeGreaterThan(0);
|
|
549
|
+
});
|
|
550
|
+
});
|
|
551
|
+
```
|
|
552
|
+
|
|
553
|
+
- [ ] **Step 5: Run test**
|
|
554
|
+
|
|
555
|
+
```bash
|
|
556
|
+
npx vitest run tests/gateway/paperclip-routes.test.ts
|
|
557
|
+
```
|
|
558
|
+
Expected: PASS
|
|
559
|
+
|
|
560
|
+
- [ ] **Step 6: Commit extraction**
|
|
561
|
+
|
|
562
|
+
```bash
|
|
563
|
+
git add src/gateway/routes/paperclip.ts tests/gateway/paperclip-routes.test.ts
|
|
564
|
+
git add src/gateway/server.ts
|
|
565
|
+
git commit -m "refactor(gateway): extract /api/paperclip routes to sub-router"
|
|
566
|
+
```
|
|
567
|
+
|
|
568
|
+
### C2: Extract `/api/checkpoints/*` routes
|
|
569
|
+
|
|
570
|
+
**Files:**
|
|
571
|
+
- Create: `src/gateway/routes/checkpoints.ts`
|
|
572
|
+
- Modify: `src/gateway/server.ts`
|
|
573
|
+
- Test: `tests/gateway/checkpoints-routes.test.ts`
|
|
574
|
+
|
|
575
|
+
- [ ] **Step 7-12:** (Same pattern as C1 — read, create router, extract, test, commit)
|
|
576
|
+
|
|
577
|
+
Copy C1 steps but for:
|
|
578
|
+
- Routes: `GET /api/checkpoints`, `GET /api/checkpoints/:sessionId`
|
|
579
|
+
- Rate limit: `rateLimit(60000, 30)`
|
|
580
|
+
|
|
581
|
+
### C3: Extract `/api/companies/*` routes
|
|
582
|
+
|
|
583
|
+
**Files:**
|
|
584
|
+
- Create: `src/gateway/routes/companies.ts`
|
|
585
|
+
- Modify: `src/gateway/server.ts`
|
|
586
|
+
- Test: `tests/gateway/companies-routes.test.ts`
|
|
587
|
+
|
|
588
|
+
- [ ] **Step 13-18:** (Same pattern as C1)
|
|
589
|
+
|
|
590
|
+
Routes: `GET /api/companies`, `GET /api/companies/:id`, `POST /api/companies`, etc.
|
|
591
|
+
|
|
592
|
+
### C4: Extract `/api/traces/*` routes
|
|
593
|
+
|
|
594
|
+
**Files:**
|
|
595
|
+
- Create: `src/gateway/routes/traces.ts`
|
|
596
|
+
- Modify: `src/gateway/server.ts`
|
|
597
|
+
- Test: `tests/gateway/traces-routes.test.ts`
|
|
598
|
+
|
|
599
|
+
- [ ] **Step 19-24:** (Same pattern as C1)
|
|
600
|
+
|
|
601
|
+
Routes: `GET /api/traces`, `GET /api/traces/:traceId`, `POST /api/traces`
|
|
602
|
+
|
|
603
|
+
### C5: Verify gateway monolith is decomposed
|
|
604
|
+
|
|
605
|
+
- [ ] **Step 25: Check `server.ts` route count**
|
|
606
|
+
|
|
607
|
+
```bash
|
|
608
|
+
node -e "const fs=require('fs'); const s=fs.readFileSync('src/gateway/server.ts','utf-8'); const matches=s.match(/app\.(get|post|put|delete)\(/g); console.log('Remaining inline routes:', matches ? matches.length : 0);"
|
|
609
|
+
```
|
|
610
|
+
Expected: < 10 (only top-level mounts like `/api`, `/`, `/legacy`, etc.)
|
|
611
|
+
|
|
612
|
+
- [ ] **Step 26: Commit final cleanup**
|
|
613
|
+
|
|
614
|
+
```bash
|
|
615
|
+
git add src/gateway/server.ts
|
|
616
|
+
git commit -m "refactor(gateway): mount all sub-routers, reduce server.ts to composition root"
|
|
617
|
+
```
|
|
618
|
+
|
|
619
|
+
---
|
|
620
|
+
|
|
621
|
+
## Stream D: Bridge Voice Agent into Core
|
|
622
|
+
|
|
623
|
+
### D1: Create TypeScript bridge to `TitanAgent`
|
|
624
|
+
|
|
625
|
+
**Files:**
|
|
626
|
+
- Read: `titan-voice-agent/agent.py` (extract API surface)
|
|
627
|
+
- Create: `src/voice/bridge.ts`
|
|
628
|
+
- Create: `tests/voice/bridge.test.ts`
|
|
629
|
+
|
|
630
|
+
- [ ] **Step 1: Read `agent.py` API surface**
|
|
631
|
+
|
|
632
|
+
Run: `head -80 titan-voice-agent/agent.py`
|
|
633
|
+
Key functions to bridge: `__init__`, `start`, `stop`, `process_audio`, `get_status`
|
|
634
|
+
|
|
635
|
+
- [ ] **Step 2: Write `bridge.ts`**
|
|
636
|
+
|
|
637
|
+
```typescript
|
|
638
|
+
// src/voice/bridge.ts
|
|
639
|
+
import { spawn, ChildProcess } from 'child_process';
|
|
640
|
+
import { log } from '../utils/logger';
|
|
641
|
+
|
|
642
|
+
interface VoiceAgentOptions {
|
|
643
|
+
pythonPath?: string;
|
|
644
|
+
agentScript?: string;
|
|
645
|
+
model?: string;
|
|
646
|
+
device?: string;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
interface AgentStatus {
|
|
650
|
+
running: boolean;
|
|
651
|
+
uptime: number;
|
|
652
|
+
lastError?: string;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
export class TitanAgentBridge {
|
|
656
|
+
private proc: ChildProcess | null = null;
|
|
657
|
+
private status: AgentStatus = { running: false, uptime: 0 };
|
|
658
|
+
private startTime = 0;
|
|
659
|
+
|
|
660
|
+
constructor(private options: VoiceAgentOptions = {}) {}
|
|
661
|
+
|
|
662
|
+
async start(): Promise<void> {
|
|
663
|
+
const python = this.options.pythonPath || process.env.TITAN_PYTHON_PATH || 'python3';
|
|
664
|
+
const script = this.options.agentScript || './titan-voice-agent/agent.py';
|
|
665
|
+
|
|
666
|
+
this.proc = spawn(python, [script, '--mode', 'server'], {
|
|
667
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
668
|
+
env: { ...process.env, TITAN_VOICE_MODEL: this.options.model || 'default' }
|
|
669
|
+
});
|
|
670
|
+
|
|
671
|
+
this.proc.stdout?.on('data', (data) => {
|
|
672
|
+
log.info('[TitanAgent]', data.toString().trim());
|
|
673
|
+
});
|
|
674
|
+
|
|
675
|
+
this.proc.stderr?.on('data', (data) => {
|
|
676
|
+
log.error('[TitanAgent]', data.toString().trim());
|
|
677
|
+
this.status.lastError = data.toString().trim();
|
|
678
|
+
});
|
|
679
|
+
|
|
680
|
+
this.proc.on('close', (code) => {
|
|
681
|
+
log.warn(`[TitanAgent] exited with code ${code}`);
|
|
682
|
+
this.status.running = false;
|
|
683
|
+
});
|
|
684
|
+
|
|
685
|
+
this.startTime = Date.now();
|
|
686
|
+
this.status.running = true;
|
|
687
|
+
|
|
688
|
+
// Wait for ready signal
|
|
689
|
+
await new Promise<void>((resolve, reject) => {
|
|
690
|
+
const timeout = setTimeout(() => reject(new Error('TitanAgent start timeout')), 10000);
|
|
691
|
+
|
|
692
|
+
const onReady = (data: Buffer) => {
|
|
693
|
+
if (data.toString().includes('ready')) {
|
|
694
|
+
clearTimeout(timeout);
|
|
695
|
+
this.proc?.stdout?.off('data', onReady);
|
|
696
|
+
resolve();
|
|
697
|
+
}
|
|
698
|
+
};
|
|
699
|
+
|
|
700
|
+
this.proc?.stdout?.on('data', onReady);
|
|
701
|
+
});
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
async processAudio(audioBuffer: Buffer): Promise<string> {
|
|
705
|
+
if (!this.proc?.stdin?.writable) {
|
|
706
|
+
throw new Error('TitanAgent not running');
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
// Send audio to python process, get transcript back
|
|
710
|
+
const requestId = Math.random().toString(36).slice(2);
|
|
711
|
+
const payload = JSON.stringify({ type: 'audio', requestId, data: audioBuffer.toString('base64') });
|
|
712
|
+
|
|
713
|
+
this.proc.stdin.write(payload + '\n');
|
|
714
|
+
|
|
715
|
+
return new Promise((resolve, reject) => {
|
|
716
|
+
const timeout = setTimeout(() => reject(new Error('Audio processing timeout')), 30000);
|
|
717
|
+
|
|
718
|
+
const handler = (data: Buffer) => {
|
|
719
|
+
const lines = data.toString().split('\n');
|
|
720
|
+
for (const line of lines) {
|
|
721
|
+
if (!line.trim()) continue;
|
|
722
|
+
try {
|
|
723
|
+
const response = JSON.parse(line);
|
|
724
|
+
if (response.requestId === requestId) {
|
|
725
|
+
clearTimeout(timeout);
|
|
726
|
+
this.proc?.stdout?.off('data', handler);
|
|
727
|
+
resolve(response.transcript || response.text || '');
|
|
728
|
+
return;
|
|
729
|
+
}
|
|
730
|
+
} catch {
|
|
731
|
+
// Not JSON, ignore
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
};
|
|
735
|
+
|
|
736
|
+
this.proc?.stdout?.on('data', handler);
|
|
737
|
+
});
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
getStatus(): AgentStatus {
|
|
741
|
+
return {
|
|
742
|
+
...this.status,
|
|
743
|
+
uptime: this.status.running ? Date.now() - this.startTime : 0
|
|
744
|
+
};
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
async stop(): Promise<void> {
|
|
748
|
+
if (this.proc) {
|
|
749
|
+
this.proc.kill('SIGTERM');
|
|
750
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
751
|
+
if (!this.proc.killed) {
|
|
752
|
+
this.proc.kill('SIGKILL');
|
|
753
|
+
}
|
|
754
|
+
this.status.running = false;
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
```
|
|
759
|
+
|
|
760
|
+
- [ ] **Step 3: Write test for bridge**
|
|
761
|
+
|
|
762
|
+
```typescript
|
|
763
|
+
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
|
764
|
+
import { TitanAgentBridge } from '../../../src/voice/bridge';
|
|
765
|
+
|
|
766
|
+
describe('TitanAgentBridge', () => {
|
|
767
|
+
let bridge: TitanAgentBridge;
|
|
768
|
+
|
|
769
|
+
beforeAll(async () => {
|
|
770
|
+
bridge = new TitanAgentBridge({
|
|
771
|
+
pythonPath: 'python3',
|
|
772
|
+
agentScript: './titan-voice-agent/agent.py'
|
|
773
|
+
});
|
|
774
|
+
|
|
775
|
+
// Skip if python agent not available
|
|
776
|
+
try {
|
|
777
|
+
await bridge.start();
|
|
778
|
+
} catch (e) {
|
|
779
|
+
console.warn('Skipping voice bridge tests — TitanAgent not available');
|
|
780
|
+
}
|
|
781
|
+
});
|
|
782
|
+
|
|
783
|
+
afterAll(async () => {
|
|
784
|
+
if (bridge.getStatus().running) {
|
|
785
|
+
await bridge.stop();
|
|
786
|
+
}
|
|
787
|
+
});
|
|
788
|
+
|
|
789
|
+
it('should report running status after start', () => {
|
|
790
|
+
const status = bridge.getStatus();
|
|
791
|
+
expect(status.running).toBe(true);
|
|
792
|
+
expect(status.uptime).toBeGreaterThan(0);
|
|
793
|
+
});
|
|
794
|
+
|
|
795
|
+
it('should return transcript for empty audio (mock)', async () => {
|
|
796
|
+
const mockAudio = Buffer.from(Array(16000).fill(0)); // 1s of silence at 16kHz
|
|
797
|
+
const transcript = await bridge.processAudio(mockAudio);
|
|
798
|
+
expect(typeof transcript).toBe('string');
|
|
799
|
+
});
|
|
800
|
+
});
|
|
801
|
+
```
|
|
802
|
+
|
|
803
|
+
- [ ] **Step 4: Run test**
|
|
804
|
+
|
|
805
|
+
```bash
|
|
806
|
+
npx vitest run tests/voice/bridge.test.ts
|
|
807
|
+
```
|
|
808
|
+
Expected: PASS (or SKIP if TitanAgent not installed)
|
|
809
|
+
|
|
810
|
+
- [ ] **Step 5: Commit bridge**
|
|
811
|
+
|
|
812
|
+
```bash
|
|
813
|
+
git add src/voice/bridge.ts tests/voice/bridge.test.ts
|
|
814
|
+
git commit -m "feat(voice): add TypeScript bridge to TitanAgent python process"
|
|
815
|
+
```
|
|
816
|
+
|
|
817
|
+
### D2: Expose voice via agent tool
|
|
818
|
+
|
|
819
|
+
**Files:**
|
|
820
|
+
- Create: `src/skills/builtin/voice_control.ts`
|
|
821
|
+
- Modify: `src/agent/toolRunner.ts` (add voice tool registration)
|
|
822
|
+
|
|
823
|
+
- [ ] **Step 6: Create `voice_control` skill**
|
|
824
|
+
|
|
825
|
+
```typescript
|
|
826
|
+
// src/skills/builtin/voice_control.ts
|
|
827
|
+
import { TitanAgentBridge } from '../../voice/bridge';
|
|
828
|
+
import { log } from '../../utils/logger';
|
|
829
|
+
|
|
830
|
+
let bridge: TitanAgentBridge | null = null;
|
|
831
|
+
|
|
832
|
+
export async function startVoiceAgent(model?: string): Promise<string> {
|
|
833
|
+
bridge = new TitanAgentBridge({ model });
|
|
834
|
+
await bridge.start();
|
|
835
|
+
return `Voice agent started with model: ${model || 'default'}`;
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
export async function stopVoiceAgent(): Promise<string> {
|
|
839
|
+
if (bridge) {
|
|
840
|
+
await bridge.stop();
|
|
841
|
+
bridge = null;
|
|
842
|
+
return 'Voice agent stopped';
|
|
843
|
+
}
|
|
844
|
+
return 'Voice agent not running';
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
export async function getVoiceStatus(): Promise<object> {
|
|
848
|
+
return bridge?.getStatus() || { running: false };
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
export async function processVoiceAudio(base64Audio: string): Promise<string> {
|
|
852
|
+
if (!bridge) {
|
|
853
|
+
throw new Error('Voice agent not started. Call start_voice_agent first.');
|
|
854
|
+
}
|
|
855
|
+
const buffer = Buffer.from(base64Audio, 'base64');
|
|
856
|
+
return bridge.processAudio(buffer);
|
|
857
|
+
}
|
|
858
|
+
```
|
|
859
|
+
|
|
860
|
+
- [ ] **Step 7: Register voice tools in `toolRunner.ts`**
|
|
861
|
+
|
|
862
|
+
In `src/agent/toolRunner.ts`, add:
|
|
863
|
+
|
|
864
|
+
```typescript
|
|
865
|
+
import { startVoiceAgent, stopVoiceAgent, getVoiceStatus, processVoiceAudio } from '../skills/builtin/voice_control';
|
|
866
|
+
|
|
867
|
+
// In tool registration map:
|
|
868
|
+
const tools = {
|
|
869
|
+
// ... existing tools
|
|
870
|
+
start_voice_agent: { handler: startVoiceAgent, params: ['model'] },
|
|
871
|
+
stop_voice_agent: { handler: stopVoiceAgent, params: [] },
|
|
872
|
+
get_voice_status: { handler: getVoiceStatus, params: [] },
|
|
873
|
+
process_voice_audio: { handler: processVoiceAudio, params: ['base64Audio'] },
|
|
874
|
+
};
|
|
875
|
+
```
|
|
876
|
+
|
|
877
|
+
- [ ] **Step 8: Write test for voice tool integration**
|
|
878
|
+
|
|
879
|
+
```typescript
|
|
880
|
+
import { describe, it, expect } from 'vitest';
|
|
881
|
+
import { startVoiceAgent, stopVoiceAgent, getVoiceStatus } from '../../../src/skills/builtin/voice_control';
|
|
882
|
+
|
|
883
|
+
describe('voice_control skill', () => {
|
|
884
|
+
it('should start and stop voice agent', async () => {
|
|
885
|
+
const startResult = await startVoiceAgent('test-model');
|
|
886
|
+
expect(startResult).toContain('Voice agent started');
|
|
887
|
+
|
|
888
|
+
const status = await getVoiceStatus();
|
|
889
|
+
expect(status.running).toBe(true);
|
|
890
|
+
|
|
891
|
+
const stopResult = await stopVoiceAgent();
|
|
892
|
+
expect(stopResult).toContain('stopped');
|
|
893
|
+
|
|
894
|
+
const afterStatus = await getVoiceStatus();
|
|
895
|
+
expect(afterStatus.running).toBe(false);
|
|
896
|
+
});
|
|
897
|
+
});
|
|
898
|
+
```
|
|
899
|
+
|
|
900
|
+
- [ ] **Step 9: Run test**
|
|
901
|
+
|
|
902
|
+
```bash
|
|
903
|
+
npx vitest run tests/skills/voice-control.test.ts
|
|
904
|
+
```
|
|
905
|
+
Expected: PASS
|
|
906
|
+
|
|
907
|
+
- [ ] **Step 10: Commit voice tool integration**
|
|
908
|
+
|
|
909
|
+
```bash
|
|
910
|
+
git add src/skills/builtin/voice_control.ts src/agent/toolRunner.ts tests/skills/voice-control.test.ts
|
|
911
|
+
git commit -m "feat(agent): expose voice agent as start_voice_agent / stop_voice_agent tools"
|
|
912
|
+
```
|
|
913
|
+
|
|
914
|
+
### D3: Re-index to include voice in graph
|
|
915
|
+
|
|
916
|
+
- [ ] **Step 11: Re-index after D-stream**
|
|
917
|
+
|
|
918
|
+
```bash
|
|
919
|
+
node /opt/homebrew/lib/node_modules/gitnexus/dist/cli/index.js analyze --force --name TITAN
|
|
920
|
+
```
|
|
921
|
+
|
|
922
|
+
- [ ] **Step 12: Verify voice is connected**
|
|
923
|
+
|
|
924
|
+
```bash
|
|
925
|
+
node /opt/homebrew/lib/node_modules/gitnexus/dist/cli/index.js cypher "MATCH (f:Function) WHERE f.filePath CONTAINS 'voice/bridge' RETURN f.name, f.filePath"
|
|
926
|
+
```
|
|
927
|
+
Expected: Shows `TitanAgentBridge.start`, `processAudio`, `getStatus`, `stop`
|
|
928
|
+
|
|
929
|
+
```bash
|
|
930
|
+
node /opt/homebrew/lib/node_modules/gitnexus/dist/cli/index.js cypher "MATCH (f:Function)-[:CodeRelation {type:'CALLS'}]->(t:Function) WHERE f.filePath CONTAINS 'voice' RETURN f.name, t.name LIMIT 10"
|
|
931
|
+
```
|
|
932
|
+
Expected: Shows `processVoiceAudio` → `bridge.processAudio`, etc.
|
|
933
|
+
|
|
934
|
+
---
|
|
935
|
+
|
|
936
|
+
## Self-Review Checklist
|
|
937
|
+
|
|
938
|
+
- [ ] **Spec coverage:** All 4 gaps from GitNexus are covered (widget bridge, search, gateway monolith, voice orphan)
|
|
939
|
+
- [ ] **Placeholder scan:** Zero TBD, TODO, "later", "appropriate" — every step has exact code, exact paths, exact commands
|
|
940
|
+
- [ ] **Type consistency:** `TitanAgentBridge` defined in D2 and used in D2 Steps 6-10 without rename
|
|
941
|
+
- [ ] **Test coverage:** Every stream has tests (A: 2 test files, B: rebuild script validation, C: 4 route test files, D: 2 test files)
|
|
942
|
+
- [ ] **No destructive ops:** `--force` only on `.gitnexus/` rebuild; source code is refactored, not deleted
|
|
943
|
+
|
|
944
|
+
---
|
|
945
|
+
|
|
946
|
+
## Execution Order
|
|
947
|
+
|
|
948
|
+
Recommended parallelization:
|
|
949
|
+
|
|
950
|
+
1. **Start A1-A3** (widget proxy + pomodoro fix) — independent, highest user impact
|
|
951
|
+
2. **Start B1** (rebuild FTS) — long-running, do while others compile
|
|
952
|
+
3. **Start C1** (paperclip router) after A1-A3 if they touch `server.ts`
|
|
953
|
+
4. **Start D1** (voice bridge) — independent but needs python env
|
|
954
|
+
5. **Finish A4** (pipeline traceability) — depends on A1-A3
|
|
955
|
+
6. **Finish C2-C5** (remaining routers) — depends on C1 pattern
|
|
956
|
+
7. **Finish D2-D3** (voice tools + re-index) — depends on D1
|
|
957
|
+
|
|
958
|
+
---
|
|
959
|
+
|
|
960
|
+
## How to Run This Plan
|
|
961
|
+
|
|
962
|
+
**Option 1: Subagent-Driven (recommended)**
|
|
963
|
+
Each task dispatched to a fresh subagent with `superpowers:subagent-driven-development`.
|
|
964
|
+
I review between tasks, handle conflicts.
|
|
965
|
+
|
|
966
|
+
**Option 2: Inline Execution**
|
|
967
|
+
Run in this session using `superpowers:executing-plans`, batch execution with checkpoints.
|
|
968
|
+
|
|
969
|
+
Which approach do you want?
|